From 6c1ac6b18645395208b5a7fe81ef0d796ef78e3d Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Fri, 1 Mar 2013 14:59:41 -0400 Subject: [PATCH 001/296] First pass at WiFi shield support --- bin/dino | 51 ++++++++++++++------ src/du_ethernet/du_ethernet.ino | 26 ++++++---- src/du_wifi/du_wifi.ino | 84 +++++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 24 deletions(-) create mode 100644 src/du_wifi/du_wifi.ino diff --git a/bin/dino b/bin/dino index 8b2ed790..7e04bd60 100755 --- a/bin/dino +++ b/bin/dino @@ -2,6 +2,10 @@ require "pathname" require "fileutils" +SERIAL = "du" +ETHERNET = "du_ethernet" +WIFI = "du_wifi" + $options = {} $options[:sketch_names] = [] @@ -16,37 +20,50 @@ def usage $stderr.puts "Commands:" $stderr.puts " generate-sketch SKETCH [options]" $stderr.puts - $stderr.puts " Sketches:" + $stderr.puts " Available sketches and options:" + $stderr.puts $stderr.puts " serial" + $stderr.puts " --baud BAUD" + $stderr.puts " --debug" + $stderr.puts $stderr.puts " ethernet" + $stderr.puts " --mac XX:XX:XX:XX:XX:XX" + $stderr.puts " --ip XXX.XXX.XXX.XXX" + $stderr.puts " --port PORT" + $stderr.puts " --debug" $stderr.puts - $stderr.puts " Options:" - $stderr.puts " --baud BAUD" - $stderr.puts " --mac XX:XX:XX:XX:XX:XX" - $stderr.puts " --ip XXX.XXX.XXX.XXX" - $stderr.puts " --port PORT" - $stderr.puts " --debug" + $stderr.puts " wifi" + $stderr.puts " --ssid SSID" + $stderr.puts " --password PASSWORD" + $stderr.puts " --port PORT" + $stderr.puts " --debug" $stderr.puts exit(2) end # Command must be the first argument. $options[:command] = ARGV.shift -usage if $options[:command].match /help/ +usage if $options[:command].match /help|usage/ # Parse the rest loosely. loop do case ARGV[0] when 'serial' - ARGV.shift; $options[:sketch_names] << "du" unless $options[:sketch_names].include? "du" + ARGV.shift; $options[:sketch_names] << SERIAL unless $options[:sketch_names].include? SERIAL when 'ethernet' - ARGV.shift; $options[:sketch_names] << "du_ethernet" unless $options[:sketch_names].include? "du_ethernet" + ARGV.shift; $options[:sketch_names] << ETHERNET unless $options[:sketch_names].include? ETHERNET + when 'wifi' + ARGV.shift; $options[:sketch_names] << WIFI unless $options[:sketch_names].include? WIFI when '--baud' ARGV.shift; $options[:baud] = ARGV.shift - when '--mac' + when '--mac' ARGV.shift; $options[:mac] = ARGV.shift when '--ip' ARGV.shift; $options[:ip] = ARGV.shift + when '--ssid' + ARGV.shift; $options[:ssid] = ARGV.shift + when '--password' + ARGV.shift; $options[:password] = ARGV.shift when '--port' ARGV.shift; $options[:port] = ARGV.shift when '--debug' @@ -75,17 +92,23 @@ $options[:sketch_names].each do |sketch_name| sketch = File.read(src_sketch) # Modify them based on the arguments. - if $options[:baud] + if $options[:baud] && sketch_name == SERIAL sketch.gsub! "115200", $options[:baud] end - if $options[:mac] + if $options[:mac] && sketch_name == ETHERNET octets = $options[:mac].split(':') bytes = octets.map { |o| "0x#{o.upcase}" } sketch.gsub! "{ 0xDE, 0xAD, 0xBE, 0x30, 0x31, 0x32 }", bytes.inspect.gsub("[", "{").gsub("]", "}").gsub("\"", "") end - if $options[:ip] + if $options[:ip] && sketch_name == ETHERNET sketch.gsub! "192,168,0,77", $options[:ip].gsub(".", ",") end + if $options[:ssid] && sketch_name == WIFI + sketch.gsub! "yourNetwork", $options[:ssid] + end + if $options[:password] && sketch_name == WIFI + sketch.gsub! "yourPassword", $options[:password] + end if $options[:port] sketch.gsub! "int port = 3466", "int port = #{$options[:port]}" end diff --git a/src/du_ethernet/du_ethernet.ino b/src/du_ethernet/du_ethernet.ino index fac9ba82..072c2556 100644 --- a/src/du_ethernet/du_ethernet.ino +++ b/src/du_ethernet/du_ethernet.ino @@ -13,7 +13,8 @@ EthernetClient client; char responseBuffer[65]; -// Dino.h doesn't handle TXRX. Setup a callback to receive the responses and buffer them. +// Dino.h doesn't handle TXRX. +// Setup a callback to buffer responses for writing. void bufferResponse(char *response) { if (strlen(responseBuffer) > 56 ) { writeResponses(); @@ -32,23 +33,28 @@ void writeResponses() { responseBuffer[0] = '\0'; } +void printEthernetStatus() { + // Print ethernet status. + Serial.print("IP Address: "); + Serial.println(Ethernet.localIP()); + Serial.print("Port: "); + Serial.println(port); +} + void setup() { + // Start serial for debugging. + Serial.begin(9600); + // Explicitly disable the SD card. pinMode(4,OUTPUT); digitalWrite(4,HIGH); - + // Start up the network connection and server. Ethernet.begin(mac, ip); server.begin(); - - // Start serial for debugging. - Serial.begin(9600); - Serial.print("Dino::TCP started at "); - Serial.print(Ethernet.localIP()); - Serial.print(" on port "); - Serial.println(port); - + printEthernetStatus(); + // Attach the write callback. dino.setupWrite(writeCallback); } diff --git a/src/du_wifi/du_wifi.ino b/src/du_wifi/du_wifi.ino new file mode 100644 index 00000000..706b5ff2 --- /dev/null +++ b/src/du_wifi/du_wifi.ino @@ -0,0 +1,84 @@ +#include "Dino.h" +#include +#include + +// Configure your WiFi options here. MAC address and IP address are not configurable. +int port = 3466; +char ssid [] = "yourNetwork"; +char pass [] = "yourPassword"; +int keyIndex = 0; +int status = WL_IDLE_STATUS; + +Dino dino; +WiFiServer server(port); +WiFiClient client; +char responseBuffer[65]; + + +// Dino.h doesn't handle TXRX. +// Setup a callback to buffer responses for writing. +void bufferResponse(char *response) { + if (strlen(responseBuffer) > 56 ) { + writeResponses(); + strcpy(responseBuffer, response); + } else { + strcat(responseBuffer, response); + } + strcat(responseBuffer, "\n"); +} +void (*writeCallback)(char *str) = bufferResponse; + +// Write the buffered responses to the client. +void writeResponses() { + if (responseBuffer[0] != '\0') + client.write(responseBuffer); + responseBuffer[0] = '\0'; +} + +void printWifiStatus() { + Serial.print("SSID: "); + Serial.println(WiFi.SSID()); + Serial.print("Signal Strength (RSSI):"); + Serial.print(WiFi.RSSI()); + Serial.println(" dBm"); + Serial.print("IP Address: "); + Serial.println(WiFi.localIP()); + Serial.print("Port: "); + Serial.println(port); +} + + +void setup() { + // Start serial for debugging. + Serial.begin(9600); + + // Try to connect to the specified network. + while ( status != WL_CONNECTED) { + Serial.print("Attempting to connect to SSID: "); + Serial.println(ssid); + status = WiFi.begin(ssid, pass); + delay(10000); + } + + // Start the server. + server.begin(); + printWifiStatus(); + + // Attach the write callback. + dino.setupWrite(writeCallback); +} + +void loop() { + // Listen for connections. + client = server.available(); + + // Handle a connection. + if (client) { + while (client.connected()) { + while (client.available()) dino.parse(client.read()); + dino.updateListeners(); + writeResponses(); + } + } + client.stop(); +} From 7f5fa3c5ee338ce7b354b5ae951ac5c3a376c946 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Tue, 26 Mar 2013 02:12:46 -0400 Subject: [PATCH 002/296] Parse arbitrary length messages sent to the Arduino. --- lib/dino/board.rb | 13 +++++------ lib/dino/tx_rx/base.rb | 2 +- spec/lib/board_spec.rb | 40 ++++++++++++++------------------- src/lib/Dino.cpp | 51 +++++++++++++++++++++++++++++++++--------- src/lib/Dino.h | 26 +++++++++++---------- 5 files changed, 78 insertions(+), 54 deletions(-) diff --git a/lib/dino/board.rb b/lib/dino/board.rb index ad72eece..d04c33ee 100644 --- a/lib/dino/board.rb +++ b/lib/dino/board.rb @@ -17,12 +17,12 @@ def analog_divider=(value) unless [1, 2, 4, 8, 16, 32, 64, 128].include? value puts "Analog divider must be in 1, 2, 4, 8, 16, 32, 64, 128" else - write "9700#{normalize_value(value)}" + write "97.0.#{normalize_value(value)}" end end def heart_rate=(value) - write "9800#{normalize_value(value)}" + write "98.0.#{normalize_value(value)}" end def start_read @@ -33,9 +33,8 @@ def stop_read @io.close_read end - def write(msg, opts = {}) - formatted_msg = opts.delete(:no_wrap) ? msg : "!#{msg}." - @io.write(formatted_msg) + def write(msg) + @io.write("#{msg}\n") end def update(pin, msg) @@ -68,7 +67,7 @@ def remove_analog_hardware(part) def set_pin_mode(pin, mode, pullup=nil) pin, value = normalize_pin(pin), normalize_value(mode == :out ? 0 : 1) - write("00#{pin}#{value}") + write("00.#{pin}.#{value}") set_pullup(pin, pullup) if mode == :in end @@ -91,7 +90,7 @@ def set_pullup(pin, pullup) PIN_COMMANDS.each_key do |command| define_method(command) do |pin, value=nil| cmd = normalize_cmd(PIN_COMMANDS[command]) - write "#{cmd}#{normalize_pin(pin)}#{normalize_value(value)}" + write "#{cmd}.#{normalize_pin(pin)}.#{normalize_value(value)}" end end diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index 1e2def91..f43119d5 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -37,7 +37,7 @@ def handshake flush_read 100.times do begin - write("!9000000.") + write("90.00.000\n") Timeout::timeout(0.1) do line = gets.to_s if line.match /ACK:/ diff --git a/spec/lib/board_spec.rb b/spec/lib/board_spec.rb index 8f954a6a..bbc79e27 100644 --- a/spec/lib/board_spec.rb +++ b/spec/lib/board_spec.rb @@ -83,9 +83,9 @@ def io_mock(methods = {}) it 'should set the mode for the given pin to "in" and add a digital listener' do subject - subject.should_receive(:write).with("0012001") - subject.should_receive(:write).with("0112000") - subject.should_receive(:write).with("0512000") + subject.should_receive(:write).with("00.12.001") + subject.should_receive(:write).with("01.12.000") + subject.should_receive(:write).with("05.12.000") subject.add_digital_hardware(mock1 = mock(:part1, pin: 12, pullup: nil)) end end @@ -108,9 +108,9 @@ def io_mock(methods = {}) it 'should set the mode for the given pin to "in" and add an analog listener' do subject - subject.should_receive(:write).with("0012001") - subject.should_receive(:write).with("0112000") - subject.should_receive(:write).with("0612000") + subject.should_receive(:write).with("00.12.001") + subject.should_receive(:write).with("01.12.000") + subject.should_receive(:write).with("06.12.000") subject.add_analog_hardware(mock1 = mock(:part1, pin: 12, pullup: nil)) end end @@ -145,75 +145,69 @@ def io_mock(methods = {}) board.write('message').should == true end - it 'should wrap the message in a ! and a . by default' do - io_mock.should_receive(:write).with('!hello.') + it 'should append a new line to hte message' do + io_mock.should_receive(:write).with("hello\n") subject.write('hello') end - - it 'should not wrap the message if no_wrap is set to true' do - board = Board.new(io_mock) - io_mock.should_receive(:write).with('hello') - board.write('hello', no_wrap: true) - end end describe '#digital_write' do it 'should append a append a write to the pin and value' do - io_mock.should_receive(:write).with('!0101003.') + io_mock.should_receive(:write).with("01.01.003\n") subject.digital_write(01, 003) end end describe '#digital_read' do it 'should tell the board to read once from the given pin' do - io_mock.should_receive(:write).with('!0213000.') + io_mock.should_receive(:write).with("02.13.000\n") subject.digital_read(13) end end describe '#analog_write' do it 'should append a append a write to the pin and value' do - io_mock.should_receive(:write).with('!0301003.') + io_mock.should_receive(:write).with("03.01.003\n") subject.analog_write(01, 003) end end describe '#analog_read' do it 'should tell the board to read once from the given pin' do - io_mock.should_receive(:write).with('!0413000.') + io_mock.should_receive(:write).with("04.13.000\n") subject.analog_read(13) end end describe '#digital_listen' do it 'should tell the board to continuously read from the given pin' do - io_mock.should_receive(:write).with('!0513000.') + io_mock.should_receive(:write).with("05.13.000\n") subject.digital_listen(13) end end describe '#analog_listen' do it 'should tell the board to continuously read from the given pin' do - io_mock.should_receive(:write).with('!0613000.') + io_mock.should_receive(:write).with("06.13.000\n") subject.analog_listen(13) end end describe '#stop_listener' do it 'should tell the board to stop sending values for the given pin' do - io_mock.should_receive(:write).with('!0713000.') + io_mock.should_receive(:write).with("07.13.000\n") subject.stop_listener(13) end end describe '#set_pin_mode' do it 'should send a value of 0 if the pin mode is set to out' do - io_mock.should_receive(:write).with('!0013000.') + io_mock.should_receive(:write).with("00.13.000\n") subject.set_pin_mode(13, :out) end it 'should send a value of 1 if the pin mode is set to in' do - io_mock.should_receive(:write).with('!0013001.') + io_mock.should_receive(:write).with("00.13.001\n") subject.set_pin_mode(13, :in) end end diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 5c439f9c..7b786210 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -6,28 +6,56 @@ #include "Dino.h" Dino::Dino(){ + messageFragments[0] = cmdStr; + messageFragments[1] = pinStr; + messageFragments[2] = valStr; + messageFragments[3] = auxMsg; reset(); } void Dino::parse(char c) { - if (c == '!') index = 0; // Reset request - else if (c == '.') process(); // End request and process - else request[index++] = c; // Append to request + // Handle escaped newlines. + if (backslash) { + if (c != '\n') append('\\'); + append(c); + backslash = false; + } + + // If EOL process and reset. + else if (c == '\n') { + append('\0'); + process(); + fragmentIndex = 0; + charIndex = 0; + } + + // If fragment delimiter, terminate current fragment and move to next. + else if (c == '.') { + append('\0'); + if (fragmentIndex < 3) fragmentIndex++; + charIndex = 0; + } + + // Catch backslash so we can escape the next character. + else if (c == '\\') backslash = true; + + // Else just append the character. + else append(c); } -void Dino::process() { - response[0] = '\0'; - // Parse the request. - strncpy(cmdStr, request, 2); cmdStr[2] = '\0'; - strncpy(pinStr, request + 2, 2); pinStr[2] = '\0'; - strncpy(valStr, request + 4, 3); valStr[3] = '\0'; +void Dino::append(char c) { + messageFragments[fragmentIndex][charIndex++] = c; +} + +void Dino::process() { cmd = atoi(cmdStr); pin = atoi(pinStr); val = atoi(valStr); + response[0] = '\0'; #ifdef debug - Serial.print("Received request - "); Serial.println(request); + // Serial.print("Received request - "); Serial.println(request); Serial.print("Command - "); Serial.println(cmdStr); Serial.print("Pin - "); Serial.println(pinStr); Serial.print("Value - "); Serial.println(valStr); @@ -229,7 +257,8 @@ void Dino::reset() { for (int i = 0; i < 22; i++) digitalListenerValues[i] = 2; for (int i = 0; i < 22; i++) analogListeners[i] = false; lastUpdate = micros(); - index = 0; + fragmentIndex = 0; + charIndex = 0; #ifdef debug Serial.println("Reset the board to defaults. pin "); #endif diff --git a/src/lib/Dino.h b/src/lib/Dino.h index b05aeffc..e53d2184 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -33,24 +33,25 @@ class Dino { unsigned int loopCount; unsigned int analogDivider; - // Storage for enough analog and digital listeners for UNO or Nano board. - // Correspond to raw pin number by array index, and store boolean. false == disabled. + // Listeners correspond to raw pin number by array index, and store boolean. false == disabled. boolean analogListeners[PIN_COUNT]; boolean digitalListeners[PIN_COUNT]; // Keep track of the last read values for digital listeners. Only write responses when changed. byte digitalListenerValues[PIN_COUNT]; + + // Parsed message storage. + char cmdStr[5]; int cmd; + char pinStr[5]; int pin; + char valStr[5]; int val; + char auxMsg[256]; - // Request storage. - char request[8]; - int index; - char cmdStr[3]; - byte cmd; - char pinStr[3]; - byte pin; - char valStr[4]; - int val; - + // Parser state storage. + char *messageFragments[4]; + byte fragmentIndex; + int charIndex; + boolean backslash; + // Value and response storage. int rval; char response[8]; @@ -75,6 +76,7 @@ class Dino { void setHeartRate (); // Internal functions. + void append (char c); long timeSince (long event); void updateDigitalListeners (); void updateAnalogListeners (); From 79197d0a9b9a748aa0d29e28c012833799ea1ae9 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Tue, 26 Mar 2013 02:58:33 -0400 Subject: [PATCH 003/296] Fix a parser error. --- src/lib/Dino.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 7b786210..ec98f9cd 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -30,10 +30,15 @@ void Dino::parse(char c) { } // If fragment delimiter, terminate current fragment and move to next. + // Unless we're in the extended message, then just append. else if (c == '.') { - append('\0'); - if (fragmentIndex < 3) fragmentIndex++; - charIndex = 0; + if (fragmentIndex < 3) { + append('\0'); + fragmentIndex++; + charIndex = 0; + } else { + append(c); + } } // Catch backslash so we can escape the next character. @@ -43,7 +48,6 @@ void Dino::parse(char c) { else append(c); } - void Dino::append(char c) { messageFragments[fragmentIndex][charIndex++] = c; } @@ -55,7 +59,6 @@ void Dino::process() { response[0] = '\0'; #ifdef debug - // Serial.print("Received request - "); Serial.println(request); Serial.print("Command - "); Serial.println(cmdStr); Serial.print("Pin - "); Serial.println(pinStr); Serial.print("Value - "); Serial.println(valStr); From b4c02b8123652cb4be589bbdf7ff5f7a44e2712e Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Tue, 26 Mar 2013 14:45:58 -0400 Subject: [PATCH 004/296] Add Message class --- examples/bench.rb | 110 +++++++++++++++++++++++++++++++++++++++ examples/benchmark.rb | 69 ++++++++++++++++++++++++ examples/load.rb | 93 +++++++++++++++++++++++++++++++++ lib/dino.rb | 1 + lib/dino/board.rb | 69 +++++++++--------------- lib/dino/message.rb | 32 ++++++++++++ lib/dino/tx_rx/base.rb | 2 +- spec/lib/board_spec.rb | 91 ++++++++++++++++---------------- spec/lib/message_spec.rb | 38 ++++++++++++++ 9 files changed, 412 insertions(+), 93 deletions(-) create mode 100644 examples/bench.rb create mode 100644 examples/benchmark.rb create mode 100644 examples/load.rb create mode 100644 lib/dino/message.rb create mode 100644 spec/lib/message_spec.rb diff --git a/examples/bench.rb b/examples/bench.rb new file mode 100644 index 00000000..14cf0d93 --- /dev/null +++ b/examples/bench.rb @@ -0,0 +1,110 @@ +# +# This is a simple example to blink an led +# every half a second +# + +require File.expand_path('../../lib/dino', __FILE__) + +board = Dino::Board.new(Dino::TxRx::Serial.new) + +# Keep a led blinking. +led = Dino::Components::Led.new(pin: '13', board: board) +led_thread = Thread.new do + [:on, :off].cycle do |switch| + led.send(switch) + sleep 0.5 + end +end + +# Keep a servo moving. +servo = Dino::Components::Servo.new(pin: 9, board: board) +servo_thread = Thread.new do + [0, 90].cycle do |pos| + servo.position = pos + sleep 0.3 + end +end + +# Keep a servo moving. +servo2 = Dino::Components::Servo.new(pin: 10, board: board) +servo_thread2 = Thread.new do + [0, 90].cycle do |pos| + servo2.position = pos + sleep 0.5 + end +end + +# Keep a servo moving. +servo3 = Dino::Components::Servo.new(pin: 11, board: board) +servo_thread3 = Thread.new do + [0, 90].cycle do |pos| + servo3.position = pos + sleep 0.5 + end +end + + + +$counter_analog = 0 +counter_digital = 0 +test_time = 5 + +puts "Starting tests..." + +# Ranges of digital and analog pin numbers to read. +digital_range = 2..5 +analog_range = 0..5 + +digital_range.each do |digital_max| + analog_range.each do |analog_max| + + # Define ranges for this test test. + digital_test_range = digital_range.min..digital_max + analog_test_range = analog_range.min..analog_max + + # Setup hardware + analog_test_range.each do |pin| + eval "$sensor#{pin} = Dino::Components::Sensor.new(pin: 'A#{pin}', board: board)" + eval "$sensor#{pin}.when_data_received { $counter_analog = $counter_analog + 1 }" + end + digital_test_range.each do |pin| + eval "$digital#{pin} = Dino::Components::Button.new(pin: '#{pin}', board: board, pullup: true)" + eval "$digital#{pin}.up { counter_digital = counter_digital + 1 }" + eval "$digital#{pin}.down { counter_digital = counter_digital + 1 }" + end + + # Pre-test measurement + state1_digital = counter_digital + state1_analog = $counter_analog + + # Wait + sleep test_time + + # Post-test measurement + state2_digital = counter_digital + state2_analog = $counter_analog + + # Calculation + diff_digital = state2_digital - state1_digital + diff_analog = state2_analog - state1_analog + + # Print + print "#{digital_test_range.count}D/#{analog_test_range.count}A Components: " + print "#{diff_digital/(test_time * digital_test_range.count)}Hz" + print "/" + print "#{diff_analog/(test_time * analog_test_range.count)}Hz per component | " + puts "#{(diff_digital + diff_analog)/(test_time)} responses/s overall" + + # Remove hardware + analog_test_range.each do |pin| + board.remove_analog_hardware(eval "$sensor#{pin}") + end + digital_test_range.each do |pin| + board.remove_digital_hardware(eval "$digital#{pin}") + end + end +end + +servo_thread.kill +led_thread.kill + diff --git a/examples/benchmark.rb b/examples/benchmark.rb new file mode 100644 index 00000000..fb6e7313 --- /dev/null +++ b/examples/benchmark.rb @@ -0,0 +1,69 @@ +# +# This is a simple example to blink an led +# every half a second +# + +require File.expand_path('../../lib/dino', __FILE__) +txrx = Dino::TxRx.new +# txrx = Dino::TxRx::TCP.new("192.168.0.77") +# txrx = Dino::TxRx::TCP.new("192.168.0.143", 3001) # ; txrx.io; sleep 5 +board = Dino::Board.new(txrx) + +txrx.write("!9800003.") + +# Warm up +sleep 2 + +counter_analog = 0 +counter_digital = 0 +count_analog_responses = Proc.new { counter_analog = counter_analog + 1 } +test_time = 4 + +puts "Starting tests..." + +(5..9).each do |digital_count| + (0..5).each do |analog_count| + + # Setup hardware + (0..analog_count).each do |pin| + eval "$sensor#{pin} = Dino::Components::Sensor.new(pin: 'A#{pin}', board: board)" + eval "$sensor#{pin}.when_data_received(count_analog_responses)" + end + (5..digital_count).each do |pin| + eval "$digital#{pin} = Dino::Components::Button.new(pin: '#{pin}', board: board, pullup: true)" + eval "$digital#{pin}.up { counter_digital = counter_digital + 1 }" + eval "$digital#{pin}.down { counter_digital = counter_digital + 1 }" + end + + # Pre-test measurement + state1_digital = counter_digital + state1_analog = counter_analog + + # Wait + sleep test_time + + # Post-test measurement + state2_digital = counter_digital + state2_analog = counter_analog + + # Calculation + diff_digital = state2_digital - state1_digital + diff_analog = state2_analog - state1_analog + + # Print + print "#{digital_count -4}D/#{analog_count + 1}A Components: " + print "#{diff_digital/(test_time * (digital_count - 4))}Hz" + print "/" + print "#{diff_analog/(test_time * (analog_count + 1))}Hz per component | " + puts "#{(diff_digital + diff_analog)/(test_time)} responses/s overall" + + # Remove hardware + (0..analog_count).each do |pin| + board.remove_analog_hardware(eval "$sensor#{pin}") + end + (5..digital_count).each do |pin| + board.remove_digital_hardware(eval "$digital#{pin}") + end + end +end + diff --git a/examples/load.rb b/examples/load.rb new file mode 100644 index 00000000..e70cb96e --- /dev/null +++ b/examples/load.rb @@ -0,0 +1,93 @@ +#c +# This is a simple example to blink an led +# every half a second +# + +require File.expand_path('../../lib/dino', __FILE__) +# txrx = Dino::TxRx.new +# txrx = Dino::TxRx::TCP.new("192.168.0.77") +# txrx = Dino::TxRx::TCP.new("192.168.0.143", 3001) # ; txrx.io; sleep 5 +# board = Dino::Board.new(txrx) + +# txrx.write("!9800010.") + + + +board = Dino::Board.new(Dino::TxRx::Serial.new) #(device: "/dev/tty.usbserial-A9015AK7")) +servo = Dino::Components::Servo.new(pin: 9, board: board) +led = Dino::Components::Led.new(pin: '13', board: board) + +# board.heart_rate = 10 +# board.analog_divider = 8 + +counter_analog = 0 +counter_digital = 0 +test_time = 2 + +analog_count = 5 +digital_count = 6 + +# led.pulse(0, 128) + +# Setup hardware +(0..5).each do |pin| + eval "$sensor#{pin} = Dino::Components::Sensor.new(pin: 'A#{pin}', board: board)" + eval "$sensor#{pin}.when_data_received { counter_analog = counter_analog + 1 }" +end +(5..9).each do |pin| + eval "$digital#{pin} = Dino::Components::Button.new(pin: '#{pin}', board: board, pullup: true)" + eval "$digital#{pin}.up { counter_digital = counter_digital + 1 }" + eval "$digital#{pin}.down { counter_digital = counter_digital + 1 }" +end + + + + +puts "Starting test..." +start_time = Time.now + +loop do + + 40.times do + led.off + led.on + end + + # [0, 90].each do |pos| + # servo.position = pos + # end + + # Pre-test measurement + state1_digital = counter_digital + state1_analog = counter_analog + + # Wait + sleep test_time + + # Post-test measurement + state2_digital = counter_digital + state2_analog = counter_analog + + # Calculation + diff_digital = state2_digital - state1_digital + diff_analog = state2_analog - state1_analog + + # Print + print "#{digital_count -1}D/#{analog_count + 1}A Components: " + print "#{diff_digital/(test_time * (digital_count - 1))}Hz" + print "/" + print "#{diff_analog/(test_time * (analog_count + 1))}Hz per component | " + puts "#{(diff_digital + diff_analog)/(test_time)} responses/s overall" + + print "Elapsed time: " + puts Time.now - start_time + +end + +# Remove hardware +(0..5).each do |pin| + board.remove_analog_hardware(eval "$sensor#{pin}") +end +(5..10).each do |pin| + board.remove_digital_hardware(eval "$digital#{pin}") +end diff --git a/lib/dino.rb b/lib/dino.rb index 9172c7c0..c7c3dfb1 100644 --- a/lib/dino.rb +++ b/lib/dino.rb @@ -1,5 +1,6 @@ require 'dino/board_not_found' require 'dino/version' +require 'dino/message' require 'dino/tx_rx' require 'dino/board' require 'dino/components' diff --git a/lib/dino/board.rb b/lib/dino/board.rb index d04c33ee..150a2cbc 100644 --- a/lib/dino/board.rb +++ b/lib/dino/board.rb @@ -2,6 +2,7 @@ module Dino class Board attr_reader :digital_hardware, :analog_hardware, :analog_zero LOW, HIGH = 000, 255 + DIVIDERS = [1, 2, 4, 8, 16, 32, 64, 128] def initialize(io) @io, @digital_hardware, @analog_hardware = io, [], [] @@ -14,15 +15,15 @@ def handshake end def analog_divider=(value) - unless [1, 2, 4, 8, 16, 32, 64, 128].include? value - puts "Analog divider must be in 1, 2, 4, 8, 16, 32, 64, 128" + unless DIVIDERS.include? value + puts "Analog divider must be in #{DIVIDERS.inspect}" else - write "97.0.#{normalize_value(value)}" + write Dino::Message.encode(command: 97, value: value) end end def heart_rate=(value) - write "98.0.#{normalize_value(value)}" + write Dino::Message.encode(command: 98, value: value) end def start_read @@ -34,12 +35,12 @@ def stop_read end def write(msg) - @io.write("#{msg}\n") + @io.write(msg) end def update(pin, msg) (@digital_hardware + @analog_hardware).each do |part| - part.update(msg) if normalize_pin(pin) == normalize_pin(part.pin) + part.update(msg) if convert_pin(pin) == convert_pin(part.pin) end end @@ -55,7 +56,7 @@ def remove_digital_hardware(part) end def add_analog_hardware(part) - set_pin_mode(part.pin, :in) + set_pin_mode(part.pin, :in, part.pullup) analog_listen(part.pin) @analog_hardware << part end @@ -66,58 +67,36 @@ def remove_analog_hardware(part) end def set_pin_mode(pin, mode, pullup=nil) - pin, value = normalize_pin(pin), normalize_value(mode == :out ? 0 : 1) - write("00.#{pin}.#{value}") + pin, value = convert_pin(pin), mode == :out ? 0 : 1 + write Dino::Message.encode(command: 0, pin: pin, value: value) set_pullup(pin, pullup) if mode == :in end def set_pullup(pin, pullup) + pin = convert_pin(pin) pullup ? digital_write(pin, HIGH) : digital_write(pin, LOW) end - + PIN_COMMANDS = { - digital_write: '01', - digital_read: '02', - analog_write: '03', - analog_read: '04', - digital_listen: '05', - analog_listen: '06', - stop_listener: '07', - servo_toggle: '08', - servo_write: '09' + digital_write: '1', + digital_read: '2', + analog_write: '3', + analog_read: '4', + digital_listen: '5', + analog_listen: '6', + stop_listener: '7', + servo_toggle: '8', + servo_write: '9' } PIN_COMMANDS.each_key do |command| define_method(command) do |pin, value=nil| - cmd = normalize_cmd(PIN_COMMANDS[command]) - write "#{cmd}.#{normalize_pin(pin)}.#{normalize_value(value)}" - end - end - - def normalize_pin(pin) - if pin.to_s.match /\Aa/i - int_pin = @analog_zero + pin.to_s.gsub(/\Aa/i, '').to_i - else - int_pin = pin + write Dino::Message.encode(command: PIN_COMMANDS[command], pin: convert_pin(pin), value: value) end - raise Exception.new('pin number must be in 0-99') if int_pin.to_i > 99 - return normalize(int_pin, 2) end - def normalize_cmd(cmd) - raise Exception.new('commands can only be two digits') if cmd.to_s.length > 2 - normalize(cmd, 2) - end - - def normalize_value(value) - raise Exception.new('values are limited to three digits') if value.to_s.length > 3 - normalize(value, 3) - end - - private - - def normalize(pin, spaces) - pin.to_s.rjust(spaces, '0') + def convert_pin(pin) + pin.to_s.match(/\Aa/i) ? @analog_zero + pin.to_s.gsub(/\Aa/i, '').to_i : pin.to_i end end end \ No newline at end of file diff --git a/lib/dino/message.rb b/lib/dino/message.rb new file mode 100644 index 00000000..b1a95ce9 --- /dev/null +++ b/lib/dino/message.rb @@ -0,0 +1,32 @@ +module Dino + module Message + def self.encode(options={}) + cmd = options[:command] + pin = options[:pin] + val = options[:value] + aux = options[:aux_message] + + raise Exception.new('commands must be specified') unless cmd + raise Exception.new('commands can only be four digits') if cmd.to_s.length > 4 + raise Exception.new('pins can only be four digits') if pin.to_s.length > 4 + raise Exception.new('values can only be four digits') if val.to_s.length > 4 + raise Exception.new('auxillary messages are limited to 255 characters') if aux.to_s.length > 255 + + message = "#{cmd}" + if pin + message << ".#{pin}" + end + if val + message << "." unless pin + message << ".#{val}" + end + if aux + message << "." unless pin + message << "." unless val + message << ".#{aux}" + end + message << "\n" + message + end + end +end diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index f43119d5..6f9ab0c7 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -37,7 +37,7 @@ def handshake flush_read 100.times do begin - write("90.00.000\n") + write Dino::Message.encode(command: 90) Timeout::timeout(0.1) do line = gets.to_s if line.match /ACK:/ diff --git a/spec/lib/board_spec.rb b/spec/lib/board_spec.rb index bbc79e27..923977a4 100644 --- a/spec/lib/board_spec.rb +++ b/spec/lib/board_spec.rb @@ -29,9 +29,9 @@ def io_mock(methods = {}) describe '#update' do context 'when the given pin connects to an analog hardware part' do it 'should call update with the message on the part' do - part = mock(:part, pin: 7) + part = mock(:part, pin: 7, pullup: nil) subject.add_analog_hardware(part) - other_part = mock(:part, pin: 9) + other_part = mock(:part, pin: 9, pullup: nil) subject.add_analog_hardware(other_part) part.should_receive(:update).with('wake up!') @@ -82,11 +82,9 @@ def io_mock(methods = {}) end it 'should set the mode for the given pin to "in" and add a digital listener' do - subject - subject.should_receive(:write).with("00.12.001") - subject.should_receive(:write).with("01.12.000") - subject.should_receive(:write).with("05.12.000") - subject.add_digital_hardware(mock1 = mock(:part1, pin: 12, pullup: nil)) + subject.should_receive(:set_pin_mode).with(12, :in, nil) + subject.should_receive(:digital_listen).with(12) + subject.add_digital_hardware(mock(:part1, pin: 12, pullup: nil)) end end @@ -107,10 +105,8 @@ def io_mock(methods = {}) end it 'should set the mode for the given pin to "in" and add an analog listener' do - subject - subject.should_receive(:write).with("00.12.001") - subject.should_receive(:write).with("01.12.000") - subject.should_receive(:write).with("06.12.000") + subject.should_receive(:set_pin_mode).with(12, :in, nil) + subject.should_receive(:analog_listen).with(12) subject.add_analog_hardware(mock1 = mock(:part1, pin: 12, pullup: nil)) end end @@ -144,106 +140,107 @@ def io_mock(methods = {}) board = Board.new(io_mock(write: true)) board.write('message').should == true end - - it 'should append a new line to hte message' do - io_mock.should_receive(:write).with("hello\n") - subject.write('hello') - end end describe '#digital_write' do - it 'should append a append a write to the pin and value' do - io_mock.should_receive(:write).with("01.01.003\n") + it 'should write the value to the right pin' do + io_mock.should_receive(:write).with(Dino::Message.encode(command: 1, pin: 1, value: 3)) subject.digital_write(01, 003) end end describe '#digital_read' do it 'should tell the board to read once from the given pin' do - io_mock.should_receive(:write).with("02.13.000\n") + io_mock.should_receive(:write).with(Dino::Message.encode(command: 2, pin: 13)) subject.digital_read(13) end end describe '#analog_write' do it 'should append a append a write to the pin and value' do - io_mock.should_receive(:write).with("03.01.003\n") - subject.analog_write(01, 003) + io_mock.should_receive(:write).with(Dino::Message.encode(command: 3, pin: 1, value: 3)) + subject.analog_write(01, 3) end end describe '#analog_read' do it 'should tell the board to read once from the given pin' do - io_mock.should_receive(:write).with("04.13.000\n") + io_mock.should_receive(:write).with(Dino::Message.encode(command: 4, pin: 13)) subject.analog_read(13) end end describe '#digital_listen' do it 'should tell the board to continuously read from the given pin' do - io_mock.should_receive(:write).with("05.13.000\n") + io_mock.should_receive(:write).with(Dino::Message.encode(command: 5, pin: 13)) subject.digital_listen(13) end end describe '#analog_listen' do it 'should tell the board to continuously read from the given pin' do - io_mock.should_receive(:write).with("06.13.000\n") + io_mock.should_receive(:write).with(Dino::Message.encode(command: 6, pin: 13)) subject.analog_listen(13) end end describe '#stop_listener' do it 'should tell the board to stop sending values for the given pin' do - io_mock.should_receive(:write).with("07.13.000\n") + io_mock.should_receive(:write).with(Dino::Message.encode(command: 7, pin: 13)) subject.stop_listener(13) end end describe '#set_pin_mode' do it 'should send a value of 0 if the pin mode is set to out' do - io_mock.should_receive(:write).with("00.13.000\n") + io_mock.should_receive(:write).with(Dino::Message.encode(command: 0, pin: 13, value: 0)) subject.set_pin_mode(13, :out) end it 'should send a value of 1 if the pin mode is set to in' do - io_mock.should_receive(:write).with("00.13.001\n") + io_mock.should_receive(:write).with(Dino::Message.encode(command: 0, pin: 13, value: 1)) subject.set_pin_mode(13, :in) end - end - describe '#handshake' do - it 'should tell the board to reset to defaults' do - io_mock.should_receive(:handshake) - subject.handshake + it 'should set the pullup correctly if mode is in' do + subject.should_receive(:set_pullup).with(13, nil) + subject.set_pin_mode(13, :in) end - end - describe '#normalize_pin' do - it 'should normalize numbers so they are two digits' do - subject.normalize_pin(1).should == '01' + it 'shouldnt affect the pullup if mode is out' do + subject.should_not_receive(:set_pullup).with(13, nil) + subject.set_pin_mode(13, :out) end + end - it 'should not normalize numbers that are already two digits' do - subject.normalize_pin(10).should == '10' + describe '#set_pullup' do + it 'should write high if pullup is enabled' do + io_mock.should_receive(:write).with(Dino::Message.encode(command: 1, pin: 13, value: Board::HIGH)) + subject.set_pullup(13, true) end - it 'should raise if a number larger than two digits are given' do - expect { subject.normalize_pin(1000) }.to raise_exception 'pin number must be in 0-99' + it 'should write low if pullup is disabled' do + io_mock.should_receive(:write).with(Dino::Message.encode(command: 1, pin: 13, value: Board::LOW)) + subject.set_pullup(13, false) end end - describe '#normalize_value' do - it 'should normalize numbers so they are three digits' do - subject.normalize_value(1).should == '001' + describe '#handshake' do + it 'should tell the board to reset to defaults' do + io_mock.should_receive(:handshake) + subject.handshake end + end + + describe '#convert_pin' do + before(:each) { subject.instance_variable_set(:@analog_zero, 14) } - it 'should not normalize numbers that are already three digits' do - subject.normalize_value(10).should == '010' + it 'should convert alphanumeric pins to numbers' do + subject.convert_pin('A1').should == 15 end - it 'should raise if a number larger than three digits are given' do - expect { subject.normalize_value(1000) }.to raise_exception 'values are limited to three digits' + it 'should leave numeric pins alone' do + subject.convert_pin('13').should == 13 end end end diff --git a/spec/lib/message_spec.rb b/spec/lib/message_spec.rb new file mode 100644 index 00000000..aedf7e51 --- /dev/null +++ b/spec/lib/message_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +module Dino + describe Dino::Message do + describe '.encode' do + + it 'should require a command' do + expect { Dino::Message.encode }.to raise_exception(/command/) + expect { Dino::Message.encode(command:90) }.to_not raise_exception + end + + it 'should not allow commands longer than 4 digits' do + expect { Dino::Message.encode(command:90000) }.to raise_exception(/four/) + end + + it 'should not allow pins longer than 4 digits' do + expect { Dino::Message.encode(command:90, pin:90000) }.to raise_exception(/four/) + end + + it 'should not allow values longer than 4 digits' do + expect { Dino::Message.encode(command:90, value:90000) }.to raise_exception(/four/) + end + + it 'should not allow values longer than 4 digits' do + expect { Dino::Message.encode(command:90, aux_message: "0" * 256) }.to raise_exception(/255/) + end + + it 'should build messages correctly' do + Dino::Message.encode(command: 1, pin: 1, value: 1).should == "1.1.1\n" + Dino::Message.encode(command: 1, pin: 1).should == "1.1\n" + Dino::Message.encode(command: 1, value: 1).should == "1..1\n" + Dino::Message.encode(command: 1).should == "1\n" + Dino::Message.encode(command: 1, pin: 1, value: 1, aux_message: "Some Text").should == "1.1.1.Some Text\n" + Dino::Message.encode(command: 1, aux_message: "Some Text").should == "1...Some Text\n" + end + end + end +end From 7791be51b5b51669fcf3a3012cf234159b31732d Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Tue, 26 Mar 2013 14:53:42 -0400 Subject: [PATCH 005/296] Properly escape newlines in aux messages --- lib/dino/message.rb | 1 + spec/lib/message_spec.rb | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/lib/dino/message.rb b/lib/dino/message.rb index b1a95ce9..5fb272b3 100644 --- a/lib/dino/message.rb +++ b/lib/dino/message.rb @@ -5,6 +5,7 @@ def self.encode(options={}) pin = options[:pin] val = options[:value] aux = options[:aux_message] + aux.gsub!("\n", "\\\n") if aux raise Exception.new('commands must be specified') unless cmd raise Exception.new('commands can only be four digits') if cmd.to_s.length > 4 diff --git a/spec/lib/message_spec.rb b/spec/lib/message_spec.rb index aedf7e51..93521fc2 100644 --- a/spec/lib/message_spec.rb +++ b/spec/lib/message_spec.rb @@ -33,6 +33,10 @@ module Dino Dino::Message.encode(command: 1, pin: 1, value: 1, aux_message: "Some Text").should == "1.1.1.Some Text\n" Dino::Message.encode(command: 1, aux_message: "Some Text").should == "1...Some Text\n" end + + it 'should insert a backslash before any newline inside the aux message' do + Dino::Message.encode(command: 1, aux_message: "line1\nline2").should == "1...line1\\\nline2\n" + end end end end From f13ca051d644943b2ff9848edffb10aa11a6c8e0 Mon Sep 17 00:00:00 2001 From: Nathan Lilienthal Date: Wed, 27 Mar 2013 11:42:33 -0400 Subject: [PATCH 006/296] rake and rspec should be defined in the gemspec, not Gemfile --- Gemfile | 2 -- dino.gemspec | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index c5932198..50ec6f58 100644 --- a/Gemfile +++ b/Gemfile @@ -2,5 +2,3 @@ source 'https://rubygems.org' # Specify your gem's dependencies in dino.gemspec gemspec - -gem 'rspec' diff --git a/dino.gemspec b/dino.gemspec index 1d6cc65e..1be2b618 100644 --- a/dino.gemspec +++ b/dino.gemspec @@ -17,4 +17,8 @@ Gem::Specification.new do |gem| gem.executables = ["dino"] gem.add_dependency 'serialport' + gem.add_dependency 'trollop' + + gem.add_development_dependency 'rake' + gem.add_development_dependency 'rspec' end From cb2b600f4bb4fe8d0c91c474020a968695bdd037 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Fri, 29 Mar 2013 17:30:14 -0400 Subject: [PATCH 007/296] Better message construction --- lib/dino/message.rb | 21 ++++++++------------- spec/lib/message_spec.rb | 1 + 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/lib/dino/message.rb b/lib/dino/message.rb index 5fb272b3..d60c4b8a 100644 --- a/lib/dino/message.rb +++ b/lib/dino/message.rb @@ -13,21 +13,16 @@ def self.encode(options={}) raise Exception.new('values can only be four digits') if val.to_s.length > 4 raise Exception.new('auxillary messages are limited to 255 characters') if aux.to_s.length > 255 - message = "#{cmd}" - if pin - message << ".#{pin}" - end - if val - message << "." unless pin - message << ".#{val}" - end - if aux - message << "." unless pin - message << "." unless val - message << ".#{aux}" + message = "" + [aux, val, pin].each do |fragment| + if fragment + message = ".#{fragment}" << message + elsif !message.empty? + message = "." << message + end end + message = "#{cmd}" << message message << "\n" - message end end end diff --git a/spec/lib/message_spec.rb b/spec/lib/message_spec.rb index 93521fc2..7a8db344 100644 --- a/spec/lib/message_spec.rb +++ b/spec/lib/message_spec.rb @@ -32,6 +32,7 @@ module Dino Dino::Message.encode(command: 1).should == "1\n" Dino::Message.encode(command: 1, pin: 1, value: 1, aux_message: "Some Text").should == "1.1.1.Some Text\n" Dino::Message.encode(command: 1, aux_message: "Some Text").should == "1...Some Text\n" + Dino::Message.encode(command: 1, value: 1, aux_message: "Some Text").should == "1..1.Some Text\n" end it 'should insert a backslash before any newline inside the aux message' do From e2891b7f08fb5fa8cc7a2872124b3f899f744dc9 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Fri, 29 Mar 2013 18:27:45 -0400 Subject: [PATCH 008/296] Set up an empty DinoLCD class for @supherman to implement --- src/du/du.ino | 2 +- src/du_ethernet/du_ethernet.ino | 2 +- src/lib/Dino.cpp | 11 +++++++++++ src/lib/Dino.h | 2 ++ src/lib/DinoLCD.cpp | 21 +++++++++++++++++++++ src/lib/DinoLCD.h | 13 +++++++++++++ 6 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 src/lib/DinoLCD.cpp create mode 100644 src/lib/DinoLCD.h diff --git a/src/du/du.ino b/src/du/du.ino index f9644621..df4331c7 100644 --- a/src/du/du.ino +++ b/src/du/du.ino @@ -1,5 +1,6 @@ #include "Dino.h" #include +#include Dino dino; // Dino.h doesn't handle TXRX. Setup a function to tell it to write to Serial. @@ -16,4 +17,3 @@ void loop() { dino.updateListeners(); Serial.flush(); } - diff --git a/src/du_ethernet/du_ethernet.ino b/src/du_ethernet/du_ethernet.ino index 0ada8ca3..94bdcaee 100644 --- a/src/du_ethernet/du_ethernet.ino +++ b/src/du_ethernet/du_ethernet.ino @@ -2,6 +2,7 @@ #include #include #include +#include // Configure your MAC address, IP address, and HTTP port here. byte mac[] = { 0xDE, 0xAD, 0xBE, 0x30, 0x31, 0x32 }; @@ -68,4 +69,3 @@ void loop() { } client.stop(); } - diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index ec98f9cd..a2573ddb 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -4,6 +4,7 @@ #include "Arduino.h" #include "Dino.h" +DinoLCD dinoLCD; Dino::Dino(){ messageFragments[0] = cmdStr; @@ -76,6 +77,7 @@ void Dino::process() { case 7: removeListener (); break; case 8: servoToggle (); break; case 9: servoWrite (); break; + case 10: handleLCD (); break; case 90: reset (); break; case 97: setAnalogDivider (); break; case 98: setHeartRate (); break; @@ -251,6 +253,15 @@ void Dino::servoWrite() { servos[pin - 2].write(val); } +// CMD = 10 +// Write a value to the servo object. +void Dino::handleLCD() { + #ifdef debug + Serial.print("DinoLCD command: "); Serial.print(val); Serial.print(" with data: "); Serial.println(auxMsg); + #endif + dinoLCD.process(val, auxMsg); +} + // CMD = 90 void Dino::reset() { heartRate = 4000; // Default heartRate is ~4ms. diff --git a/src/lib/Dino.h b/src/lib/Dino.h index e53d2184..e8a89dd0 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -7,6 +7,7 @@ #include "Arduino.h" #include +#include "DinoLCD.h" // Allocate listener storage based on what board we're running. #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) @@ -71,6 +72,7 @@ class Dino { void removeListener (); void servoToggle (); void servoWrite (); + void handleLCD (); void reset (); void setAnalogDivider (); void setHeartRate (); diff --git a/src/lib/DinoLCD.cpp b/src/lib/DinoLCD.cpp new file mode 100644 index 00000000..fda37a35 --- /dev/null +++ b/src/lib/DinoLCD.cpp @@ -0,0 +1,21 @@ +#include "Arduino.h" +#include "DinoLCD.h" + +LiquidCrystal lcd(12,11,5,4,3,2); + +DinoLCD::DinoLCD(){ +} + +void DinoLCD::process(int cmd, char *message) { + // Remove this test code and build your implementation here. + + // Dynamically set up the lcd. + LiquidCrystal newLCD(8, 9, 4, 5, 6, 7); + + // Start it up and write the mssage. + lcd = newLCD; + lcd.begin(16,2); + lcd.clear(); + lcd.print(message); +} + diff --git a/src/lib/DinoLCD.h b/src/lib/DinoLCD.h new file mode 100644 index 00000000..b2520a4e --- /dev/null +++ b/src/lib/DinoLCD.h @@ -0,0 +1,13 @@ +#ifndef DinoLCD_h +#define DinoLCD_h + +#include "Arduino.h" +#include + +class DinoLCD { + public: + DinoLCD(); + void process(int cmd, char *message); +}; + +#endif From 5bcde35e76ef25075c0b3a4a75ba2eada50da82e Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Fri, 29 Mar 2013 18:46:26 -0400 Subject: [PATCH 009/296] Update the CLI and WiFi sketch to work with the latest changes. --- bin/dino | 27 ++++++++++++--------------- src/du_wifi/du_wifi.ino | 2 ++ 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/bin/dino b/bin/dino index 7e04bd60..381c9bcf 100755 --- a/bin/dino +++ b/bin/dino @@ -79,17 +79,14 @@ error "No sketches or invalid sketches specified" if $options[:sketch_names].emp $options[:sketch_names].each do |sketch_name| # Define the sources. - sketch_dir = sketch_name - sketch_file = sketch_name + ".ino" src_dir = Pathname.new(__FILE__).realpath.to_s.chomp("/bin/dino") + "/src" - src_header = File.join src_dir, "lib", "Dino.h" - src_implementation = File.join src_dir, "lib", "Dino.cpp" - src_sketch = File.join src_dir, sketch_dir, sketch_file + sketch_dir = sketch_name + sketch_filename = sketch_name + ".ino" + lib_filenames = ["Dino.h", "Dino.cpp", "DinoLCD.h", "DinoLCD.cpp"] # Read the files. - header = File.read(src_header) - implementation = File.read(src_implementation) - sketch = File.read(src_sketch) + libs = lib_filenames.map { |f| File.read(File.join(src_dir, "lib", f)) } + sketch = File.read(File.join(src_dir, sketch_dir, sketch_filename)) # Modify them based on the arguments. if $options[:baud] && sketch_name == SERIAL @@ -113,19 +110,19 @@ $options[:sketch_names].each do |sketch_name| sketch.gsub! "int port = 3466", "int port = #{$options[:port]}" end if $options[:debug] - header.gsub! "// #define debug true", "#define debug true" + libs[0].gsub! "// #define debug true", "#define debug true" end # Define the destinations. working_dir = Dir.pwd dest_dir = File.join working_dir, sketch_dir Dir::mkdir dest_dir - dest_header = File.join dest_dir, "Dino.h" - dest_implementation = File.join dest_dir, "Dino.cpp" - dest_sketch = File.join dest_dir, sketch_file + dest_sketch = File.join(dest_dir, sketch_filename) + dest_libs = lib_filenames.map { |f| File.join(dest_dir, f)} # Write the files. - File.open(dest_header, 'w') { |f| f.write header } - File.open(dest_implementation, 'w') { |f| f.write implementation } - File.open(dest_sketch, 'w') { |f| f.write sketch } + File.open(dest_sketch, 'w') { |f| f.write sketch } + dest_libs.each_with_index do |file, index| + File.open(file, 'w') { |f| f.write libs[index]} + end end \ No newline at end of file diff --git a/src/du_wifi/du_wifi.ino b/src/du_wifi/du_wifi.ino index 706b5ff2..e9ebfc0d 100644 --- a/src/du_wifi/du_wifi.ino +++ b/src/du_wifi/du_wifi.ino @@ -1,6 +1,8 @@ #include "Dino.h" #include #include +#include +#include // Configure your WiFi options here. MAC address and IP address are not configurable. int port = 3466; From 1b7b9f8d300d6259afca6cae766502d1e24baf43 Mon Sep 17 00:00:00 2001 From: Herman Moreno Date: Mon, 1 Apr 2013 12:08:49 -0600 Subject: [PATCH 010/296] Add LCD support --- examples/lcd/lcd.png | Bin 0 -> 45332 bytes examples/lcd/lcd.rb | 12 +++ lib/dino/components.rb | 1 + lib/dino/components/lcd.rb | 80 +++++++++++++++++++ spec/lib/components/lcd_spec.rb | 134 ++++++++++++++++++++++++++++++++ src/lib/DinoLCD.cpp | 57 +++++++++++--- src/lib/DinoLCD.h | 6 ++ 7 files changed, 280 insertions(+), 10 deletions(-) create mode 100644 examples/lcd/lcd.png create mode 100644 examples/lcd/lcd.rb create mode 100644 lib/dino/components/lcd.rb create mode 100644 spec/lib/components/lcd_spec.rb diff --git a/examples/lcd/lcd.png b/examples/lcd/lcd.png new file mode 100644 index 0000000000000000000000000000000000000000..1930763b9315aa6182d0582f1f509da697ab2714 GIT binary patch literal 45332 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 literal 0 HcmV?d00001 diff --git a/examples/lcd/lcd.rb b/examples/lcd/lcd.rb new file mode 100644 index 00000000..8f7131c1 --- /dev/null +++ b/examples/lcd/lcd.rb @@ -0,0 +1,12 @@ +# +# This example writes "Hello World!" in the display +# +require 'bundler/setup' +require 'dino' + +board = Dino::Board.new(Dino::TxRx::Serial.new) +lcd = Dino::Components::LCD.new(board: board, pins: "12,11,5,4,3,2") + +lcd.begin(16,2) +lcd.puts("Hello World!") +sleep diff --git a/lib/dino/components.rb b/lib/dino/components.rb index 234ecb99..8087415f 100644 --- a/lib/dino/components.rb +++ b/lib/dino/components.rb @@ -8,5 +8,6 @@ module Components autoload :Servo, 'dino/components/servo' autoload :Stepper, 'dino/components/stepper' autoload :IrReceiver, 'dino/components/ir_receiver' + autoload :LCD, 'dino/components/lcd' end end diff --git a/lib/dino/components/lcd.rb b/lib/dino/components/lcd.rb new file mode 100644 index 00000000..30b74a97 --- /dev/null +++ b/lib/dino/components/lcd.rb @@ -0,0 +1,80 @@ +module Dino + module Components + class LCD < BaseComponent + + def after_initialize(options) + board.write Dino::Message.encode command: 10, value: 0, aux_message: pins + end + + def begin(cols, rows) + board.write Dino::Message.encode command: 10, value: 1, aux_message: "#{cols},#{rows}" + sleep 2 + end + + def clear + board.write Dino::Message.encode command: 10, value: 2 + end + + def home + board.write Dino::Message.encode command: 10, value: 3 + end + + def set_cursor(col, row) + board.write Dino::Message.encode command: 10, value: 4, aux_message: "#{col},#{row}" + end + + def puts(string) + board.write Dino::Message.encode command: 10, value: 5, aux_message: string + end + + def show_cursor + board.write Dino::Message.encode command: 10, value: 6 + end + + def hide_cursor + board.write Dino::Message.encode command: 10, value: 7 + end + + def blink + board.write Dino::Message.encode command: 10, value: 8 + end + + def no_blink + board.write Dino::Message.encode command: 10, value: 9 + end + + def on + board.write Dino::Message.encode command: 10, value: 10 + end + + def off + board.write Dino::Message.encode command: 10, value: 11 + end + + def scroll_left + board.write Dino::Message.encode command: 10, value: 12 + end + + def scroll_right + board.write Dino::Message.encode command: 10, value: 13 + end + + def enable_autoscroll + board.write Dino::Message.encode command: 10, value: 14 + end + + def disable_autoscroll + board.write Dino::Message.encode command: 10, value: 15 + end + + def left_to_right + board.write Dino::Message.encode command: 10, value: 16 + end + + def right_to_left + board.write Dino::Message.encode command: 10, value: 17 + end + + end + end +end diff --git a/spec/lib/components/lcd_spec.rb b/spec/lib/components/lcd_spec.rb new file mode 100644 index 00000000..ee3503e6 --- /dev/null +++ b/spec/lib/components/lcd_spec.rb @@ -0,0 +1,134 @@ +require 'spec_helper' + +module Dino + module Components + describe LCD do + let(:board) { mock(:board, digital_write: true, set_pin_mode: true) } + subject { LCD.new board: board, pins: '12,11,5,4,3,2' } + + before do + board.should_receive(:write).with("10..0.12,11,5,4,3,2\n") + end + + describe '#begin' do + it 'should initialize the display sending the command "10..1.16,2\n" to the board' do + board.should_receive(:write).with "10..1.16,2\n" + subject.should_receive(:sleep).with 2 + subject.begin(16,2) + end + end + + describe '#clear' do + it 'clears the display sending the command "10..2\n" to the board' do + board.should_receive(:write).with "10..2\n" + subject.clear + end + end + + describe '#home' do + it 'Moves the cursor to the first position with the command "10..3\n"' do + board.should_receive(:write).with "10..3\n" + subject.home + end + end + + describe '#set_cursor' do + it 'moves the cursor to the given position sending the command "10..4.0,1\n"' do + board.should_receive(:write).with "10..4.0,1\n" + subject.set_cursor(0,1) + end + end + + describe '#puts' do + it 'prints a string in the display' do + board.should_receive(:write).with "10..5.AB\n" + subject.puts("AB") + end + end + + describe '#show_cursor' do + it 'shows the cursor with the command "10..6\n"' do + board.should_receive(:write).with "10..6\n" + subject.show_cursor + end + end + + describe '#hide_cursor' do + it 'hides the cursor with the command "10..7\n"' do + board.should_receive(:write).with "10..7\n" + subject.hide_cursor + end + end + + describe '#blink' do + it 'shows a blinking cursor with the command "10..8\n"' do + board.should_receive(:write).with "10..8\n" + subject.blink + end + end + + describe '#no_blink' do + it 'stops a blinking cursor with the command "10..9\n"' do + board.should_receive(:write).with "10..9\n" + subject.no_blink + end + end + + describe '#on' do + it 'turn on the display with the command "10..10\n"' do + board.should_receive(:write).with "10..10\n" + subject.on + end + end + + describe '#off' do + it 'turn off the display with the command "10..11\n"' do + board.should_receive(:write).with "10..11\n" + subject.off + end + end + + describe '#scroll_left' do + it 'move the text in the display one position to the left the command "10..12\n"' do + board.should_receive(:write).with "10..12\n" + subject.scroll_left + end + end + + describe '#scroll_right' do + it 'move the text in the display one position to the right the command "10..13\n"' do + board.should_receive(:write).with "10..13\n" + subject.scroll_right + end + end + + describe '#enable_autoscroll' do + it 'enable autoscroll with the command "10..14\n"' do + board.should_receive(:write).with "10..14\n" + subject.enable_autoscroll + end + end + + describe '#disable_autoscroll' do + it 'disable autoscroll with the command "10..15\n"' do + board.should_receive(:write).with "10..15\n" + subject.disable_autoscroll + end + end + + describe '#left_to_right' do + it 'set the display writing to start from the left with the command "10..16\n"' do + board.should_receive(:write).with "10..16\n" + subject.left_to_right + end + end + + describe '#right_to_left' do + it 'set the display writing to start from the right with the command "10..17\n"' do + board.should_receive(:write).with "10..17\n" + subject.right_to_left + end + end + end + end +end diff --git a/src/lib/DinoLCD.cpp b/src/lib/DinoLCD.cpp index fda37a35..80a8a14f 100644 --- a/src/lib/DinoLCD.cpp +++ b/src/lib/DinoLCD.cpp @@ -6,16 +6,53 @@ LiquidCrystal lcd(12,11,5,4,3,2); DinoLCD::DinoLCD(){ } +int *DinoLCD::parse(char *aux){ + int *values = new int[11]; + char *str; + int index = 0; + while ((str = strsep(&aux, ",")) != NULL) { + values[index] = atoi(str); + index++; + } + return values; +} + void DinoLCD::process(int cmd, char *message) { - // Remove this test code and build your implementation here. - - // Dynamically set up the lcd. - LiquidCrystal newLCD(8, 9, 4, 5, 6, 7); - - // Start it up and write the mssage. - lcd = newLCD; - lcd.begin(16,2); - lcd.clear(); - lcd.print(message); + switch(cmd) { + case 0: setPins(message); break; + case 1: beginLCD(message); break; + case 2: lcd.clear(); break; + case 3: lcd.home(); break; + case 4: setLCDCursor(message); break; + case 5: lcd.print(message); break; + case 6: lcd.cursor(); break; + case 7: lcd.noCursor(); break; + case 8: lcd.blink(); break; + case 9: lcd.noBlink(); break; + case 10: lcd.display(); break; + case 11: lcd.noDisplay(); break; + case 12: lcd.scrollDisplayLeft(); break; + case 13: lcd.scrollDisplayRight(); break; + case 14: lcd.autoscroll(); break; + case 15: lcd.noAutoscroll(); break; + case 16: lcd.leftToRight(); break; + case 17: lcd.rightToLeft(); break; + default: break; + } +} + +void DinoLCD::setPins(char *aux) { + int *pins = parse(aux); + LiquidCrystal newLCD(pins[0],pins[1],pins[2],pins[3],pins[4],pins[5]); + lcd = newLCD; } +void DinoLCD::beginLCD(char *aux) { + int *values = parse(aux); + lcd.begin(values[0], values[1]); +} + +void DinoLCD::setLCDCursor(char *aux) { + int *values = parse(aux); + lcd.setCursor(values[0], values[1]); +} diff --git a/src/lib/DinoLCD.h b/src/lib/DinoLCD.h index b2520a4e..28cdf582 100644 --- a/src/lib/DinoLCD.h +++ b/src/lib/DinoLCD.h @@ -3,11 +3,17 @@ #include "Arduino.h" #include +#include class DinoLCD { public: DinoLCD(); void process(int cmd, char *message); + private: + int *parse(char *aux); + void setPins(char *aux); + void beginLCD(char *aux); + void setLCDCursor(char *aux); }; #endif From 6f778b8e19d3aa0a00df6fb3d5b272a16396e93d Mon Sep 17 00:00:00 2001 From: Herman Moreno Date: Mon, 1 Apr 2013 20:37:03 -0600 Subject: [PATCH 011/296] Allow 8bit mode --- examples/lcd/lcd.rb | 5 ++++- lib/dino/components/lcd.rb | 24 +++++++++++++++++++++++- spec/lib/components/lcd_spec.rb | 3 ++- src/lib/DinoLCD.cpp | 14 ++++++++++++-- src/lib/DinoLCD.h | 1 + 5 files changed, 42 insertions(+), 5 deletions(-) diff --git a/examples/lcd/lcd.rb b/examples/lcd/lcd.rb index 8f7131c1..3187cdf9 100644 --- a/examples/lcd/lcd.rb +++ b/examples/lcd/lcd.rb @@ -5,7 +5,10 @@ require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) -lcd = Dino::Components::LCD.new(board: board, pins: "12,11,5,4,3,2") +lcd = Dino::Components::LCD.new( + board: board, + pins: { rs: 12, enable: 11, d4: 5, d5: 4, d6: 3, d7: 2 } +) lcd.begin(16,2) lcd.puts("Hello World!") diff --git a/lib/dino/components/lcd.rb b/lib/dino/components/lcd.rb index 30b74a97..7f314d86 100644 --- a/lib/dino/components/lcd.rb +++ b/lib/dino/components/lcd.rb @@ -2,8 +2,22 @@ module Dino module Components class LCD < BaseComponent + # Initialize in 4 bits mode + # + # Dino::Componentes::LCD.new( + # board: board, + # pins: { rs: 12, enable: 11, d4: 4, d5: 5, d6: 6, d7: 7 } + # ) + # + # Initialize in 8 bits mode + # + # Dino::Componentes::LCD.new( + # board: board, + # pins: { rs: 12, enable: 11, d0: 0, d1: 1, d2: 2, d3: 3, d4: 4, d5: 5, d6: 6, d7: 7 } + # ) + # def after_initialize(options) - board.write Dino::Message.encode command: 10, value: 0, aux_message: pins + board.write Dino::Message.encode command: 10, value: 0, aux_message: encoded_pins end def begin(cols, rows) @@ -75,6 +89,14 @@ def right_to_left board.write Dino::Message.encode command: 10, value: 17 end + private + + def encoded_pins + encoded = [pins[:rs], pins[:enable], pins[:d0], + pins[:d1], pins[:d2], pins[:d3], pins[:d4], + pins[:d5], pins[:d6], pins[:d7]] + encoded.compact.join(',') + end end end end diff --git a/spec/lib/components/lcd_spec.rb b/spec/lib/components/lcd_spec.rb index ee3503e6..3778f9d7 100644 --- a/spec/lib/components/lcd_spec.rb +++ b/spec/lib/components/lcd_spec.rb @@ -4,7 +4,8 @@ module Dino module Components describe LCD do let(:board) { mock(:board, digital_write: true, set_pin_mode: true) } - subject { LCD.new board: board, pins: '12,11,5,4,3,2' } + + subject { LCD.new board: board, pins: { rs: 12, enable: 11, d4: 5, d5: 4, d6: 3, d7: 2 } } before do board.should_receive(:write).with("10..0.12,11,5,4,3,2\n") diff --git a/src/lib/DinoLCD.cpp b/src/lib/DinoLCD.cpp index 80a8a14f..0c0bbabb 100644 --- a/src/lib/DinoLCD.cpp +++ b/src/lib/DinoLCD.cpp @@ -14,6 +14,7 @@ int *DinoLCD::parse(char *aux){ values[index] = atoi(str); index++; } + parseSize = index; return values; } @@ -43,8 +44,17 @@ void DinoLCD::process(int cmd, char *message) { void DinoLCD::setPins(char *aux) { int *pins = parse(aux); - LiquidCrystal newLCD(pins[0],pins[1],pins[2],pins[3],pins[4],pins[5]); - lcd = newLCD; + if(parseSize > 6) { + // 8 bits mode + LiquidCrystal newLCD(pins[0],pins[1],pins[2],pins[3],pins[4],pins[5], + pins[6], pins[7], pins[8], pins[9]); + lcd = newLCD; + } + else { + // 4 bits mode + LiquidCrystal newLCD(pins[0],pins[1],pins[2],pins[3],pins[4],pins[5]); + lcd = newLCD; + } } void DinoLCD::beginLCD(char *aux) { diff --git a/src/lib/DinoLCD.h b/src/lib/DinoLCD.h index 28cdf582..a90629e2 100644 --- a/src/lib/DinoLCD.h +++ b/src/lib/DinoLCD.h @@ -14,6 +14,7 @@ class DinoLCD { void setPins(char *aux); void beginLCD(char *aux); void setLCDCursor(char *aux); + int parseSize; }; #endif From 02d47d86d04cedb3e455b37de1282701b8eca7fd Mon Sep 17 00:00:00 2001 From: Peter Hellberg Date: Tue, 21 May 2013 23:44:44 +0200 Subject: [PATCH 012/296] Switched to using .ruby-version, bumped the Ruby version to 2.0.0p195 --- .ruby-version | 1 + .rvmrc | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 .ruby-version delete mode 100644 .rvmrc diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 00000000..95a5ad2d --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +ruby-2.0.0-p195 diff --git a/.rvmrc b/.rvmrc deleted file mode 100644 index 7757f1ea..00000000 --- a/.rvmrc +++ /dev/null @@ -1 +0,0 @@ -rvm use ruby-1.9.3@dino --create \ No newline at end of file From 93d2143abb1276e922cb5204d03beb880fbe995c Mon Sep 17 00:00:00 2001 From: Peter Hellberg Date: Wed, 22 May 2013 00:25:38 +0200 Subject: [PATCH 013/296] Added support for using a Seven Segment Display (SSD) --- examples/ssd/ssd.rb | 18 +++++ lib/dino/components.rb | 1 + lib/dino/components/ssd.rb | 117 ++++++++++++++++++++++++++++++++ spec/lib/components/ssd_spec.rb | 93 +++++++++++++++++++++++++ 4 files changed, 229 insertions(+) create mode 100644 examples/ssd/ssd.rb create mode 100644 lib/dino/components/ssd.rb create mode 100644 spec/lib/components/ssd_spec.rb diff --git a/examples/ssd/ssd.rb b/examples/ssd/ssd.rb new file mode 100644 index 00000000..236c602d --- /dev/null +++ b/examples/ssd/ssd.rb @@ -0,0 +1,18 @@ +# +# This is an example of how to use the ssd class +# +require 'bundler/setup' +require 'dino' + +board = Dino::Board.new(Dino::TxRx::Serial.new) +ssd = Dino::Components::SSD.new( + board: board, + pins: [12,13,3,4,5,10,9], + anode: 11 +) + +# Turn off the ssd on exit +trap("SIGINT") { exit !ssd.off } + +# Display each new line on the ssd +loop { ssd.display(gets.chomp) } diff --git a/lib/dino/components.rb b/lib/dino/components.rb index 8087415f..81f19fd4 100644 --- a/lib/dino/components.rb +++ b/lib/dino/components.rb @@ -6,6 +6,7 @@ module Components autoload :Sensor, 'dino/components/sensor' autoload :RgbLed, 'dino/components/rgb_led' autoload :Servo, 'dino/components/servo' + autoload :SSD, 'dino/components/ssd' autoload :Stepper, 'dino/components/stepper' autoload :IrReceiver, 'dino/components/ir_receiver' autoload :LCD, 'dino/components/lcd' diff --git a/lib/dino/components/ssd.rb b/lib/dino/components/ssd.rb new file mode 100644 index 00000000..448a19f1 --- /dev/null +++ b/lib/dino/components/ssd.rb @@ -0,0 +1,117 @@ +# Connect to the Arduino and +# take control of the SSD +# +# ssd = SevenSegmentDisplay.new( +# board: Board.new(TxRx.new), +# pins: [12,13,3,4,5,10,9], +# anode: 11 +# ) + +module Dino + module Components + class SSD < BaseComponent + attr_reader :anode + + def after_initialize(options={}) + @anode = options[:anode] + + raise Exception.new('anode must be specified') unless anode + + # Set all pins to output + pins.each { |pin| set_pin_mode(pin, :out) } + + # Clear the display + clear + + # Turn on the display + on + end + + def clear + 7.times { |t| toggle t, 0 } + end + + def display(char) + key = char.to_s.upcase + + return scroll(key) if key.length > 1 + + # Make sure the ssd is turned on. + on + + if chars = CHARACTERS[key] + chars.each_with_index { |s,i| toggle i, s } + else + clear + end + end + + def on + digital_write anode, Board::HIGH + end + + def off + digital_write anode, Board::LOW + end + + CHARACTERS = { + '0' => [1,1,1,1,1,1,0], + '1' => [0,1,1,0,0,0,0], + '2' => [1,1,0,1,1,0,1], + '3' => [1,1,1,1,0,0,1], + '4' => [0,1,1,0,0,1,1], + '5' => [1,0,1,1,0,1,1], + '6' => [1,0,1,1,1,1,1], + '7' => [1,1,1,0,0,0,0], + '8' => [1,1,1,1,1,1,1], + '9' => [1,1,1,1,0,1,1], + ' ' => [0,0,0,0,0,0,0], + '_' => [0,0,0,1,0,0,0], + '-' => [0,0,0,0,0,0,1], + 'A' => [1,1,1,0,1,1,1], + 'B' => [0,0,1,1,1,1,1], + 'C' => [0,0,0,1,1,0,1], + 'D' => [0,1,1,1,1,0,1], + 'E' => [1,0,0,1,1,1,1], + 'F' => [1,0,0,0,1,1,1], + 'G' => [1,0,1,1,1,1,0], + 'H' => [0,0,1,0,1,1,1], + 'I' => [0,0,1,0,0,0,0], + 'J' => [0,1,1,1,1,0,0], + 'K' => [1,0,1,0,1,1,1], + 'L' => [0,0,0,1,1,1,0], + 'M' => [1,1,1,0,1,1,0], + 'N' => [0,0,1,0,1,0,1], + 'O' => [0,0,1,1,1,0,1], + 'P' => [1,1,0,0,1,1,1], + 'Q' => [1,1,1,0,0,1,1], + 'R' => [0,0,0,0,1,0,1], + 'S' => [0,0,1,1,0,1,1], + 'T' => [0,0,0,1,1,1,1], + 'U' => [0,0,1,1,1,0,0], + 'V' => [0,1,1,1,1,1,0], + 'W' => [0,1,1,1,1,1,1], + 'X' => [0,1,1,0,1,1,1], + 'Y' => [0,1,1,1,0,1,1], + 'Z' => [1,1,0,1,1,0,0], + } + + private + + def scroll(string) + string.chars.each do |chr| + off + sleep 0.05 + display chr + sleep 0.5 + end + + clear + end + + def toggle(number, state) + digital_write pins[number], state == 1 ? 0 : 1 + end + end + end +end diff --git a/spec/lib/components/ssd_spec.rb b/spec/lib/components/ssd_spec.rb new file mode 100644 index 00000000..c6b0fc6d --- /dev/null +++ b/spec/lib/components/ssd_spec.rb @@ -0,0 +1,93 @@ +require 'spec_helper' + +module Dino + module Components + describe SSD do + let(:anode) { 11 } + let(:pins) { [12,13,3,4,5,10,9] } + + let(:board) do + mock(:board, digital_write: true, set_pin_mode: true) + end + + let(:ssd) do + SSD.new board: board, pins: pins, anode: anode + end + + describe '#initialize' do + it 'should raise if it does not receive a pin' do + expect { SSD.new(board: board, anode: anode) }.to raise_exception + end + + it 'should raise if it does not receive a board' do + expect { SSD.new(pins: pins, anode: anode) }.to raise_exception + end + + it 'should raise if it does not receive a board' do + expect { SSD.new(board: board, pins: pins) }.to raise_exception + end + + it 'should set the pins to out' do + pins.each do |pin| + board.should_receive(:set_pin_mode).with(pin, :out, nil) + end + + ssd.after_initialize(anode: anode) + end + + it "should clear the display" do + ssd.should_receive(:clear).once + ssd.after_initialize(anode: anode) + end + + it "should turn on the display" do + ssd.should_receive(:on).once + ssd.after_initialize(anode: anode) + end + end + + describe '#clear' do + it 'should toggle all the seven leds to off' do + ssd.should_receive(:toggle).exactly(7).with(anything, 0) + ssd.clear + end + end + + describe '#on' do + it 'should turn the ssd on' do + board.should_receive(:digital_write).with(anode, Board::HIGH) + ssd.on + end + end + + describe '#off' do + it 'should turn the ssd off' do + board.should_receive(:digital_write).with(anode, Board::LOW) + ssd.off + end + end + + describe '#display' do + it "should scroll on multiple characters" do + ssd.should_receive(:scroll).with('FOO') + ssd.display('foo') + end + + it "should make sure the ssd is turned on" do + ssd.should_receive(:on) + ssd.display(1) + end + + it "should clear the display on unknown character" do + ssd.should_receive(:clear) + ssd.display('+') + end + + it "should toggle all the segments in order to display a character" do + ssd.should_receive(:toggle).exactly(7) + ssd.display('7') + end + end + end + end +end From f50e0ce52419e46a7725e08114024a9540811070 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Sat, 1 Jun 2013 01:38:29 -0400 Subject: [PATCH 014/296] Untested shift register support. --- lib/dino/components.rb | 19 ++++--- lib/dino/components/shift_register.rb | 31 +++++++++++ src/lib/Dino.cpp | 13 ++++- src/lib/Dino.h | 79 ++++++++++++++------------- 4 files changed, 94 insertions(+), 48 deletions(-) create mode 100644 lib/dino/components/shift_register.rb diff --git a/lib/dino/components.rb b/lib/dino/components.rb index 81f19fd4..8ddd6d32 100644 --- a/lib/dino/components.rb +++ b/lib/dino/components.rb @@ -1,14 +1,15 @@ module Dino module Components require 'dino/components/base_component' - autoload :Led, 'dino/components/led' - autoload :Button, 'dino/components/button' - autoload :Sensor, 'dino/components/sensor' - autoload :RgbLed, 'dino/components/rgb_led' - autoload :Servo, 'dino/components/servo' - autoload :SSD, 'dino/components/ssd' - autoload :Stepper, 'dino/components/stepper' - autoload :IrReceiver, 'dino/components/ir_receiver' - autoload :LCD, 'dino/components/lcd' + autoload :Led, 'dino/components/led' + autoload :Button, 'dino/components/button' + autoload :Sensor, 'dino/components/sensor' + autoload :RgbLed, 'dino/components/rgb_led' + autoload :Servo, 'dino/components/servo' + autoload :SSD, 'dino/components/ssd' + autoload :Stepper, 'dino/components/stepper' + autoload :IrReceiver, 'dino/components/ir_receiver' + autoload :LCD, 'dino/components/lcd' + autoload :ShiftRegister, 'dino/components/shift_register' end end diff --git a/lib/dino/components/shift_register.rb b/lib/dino/components/shift_register.rb new file mode 100644 index 00000000..4c04c08c --- /dev/null +++ b/lib/dino/components/shift_register.rb @@ -0,0 +1,31 @@ +module Dino + module Components + class ShiftRegister < BaseComponent + # options = {board: my_board, pins: {latch: latch_pin, clock: clock_pin, data: data_pin} + def after_initialize(options={}) + raise 'missing pins[:latch] pin' unless self.pins[:latch] + raise 'missing pins[:clock] pin' unless self.pins[:clock] + raise 'missing pins[:data] pin' unless self.pins[:data] + + pins.each do |pin| + set_pin_mode(pin, :out) + analog_write(pin, Board::LOW) + end + end + + def latch_off + digital_write(pins[:latch], Board::LOW) + end + + def latch_on + digital_write(pins[:latch], Board::HIGH) + end + + def write(byte) + latch_off + board.write(Dino::Message.encode command: 11, pin: convert_pin(pins[:data]), value: byte, aux_message: convert_pin(pins[:clock])}) + latch_on + end + end + end +end diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 40ae423b..14cb016a 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -31,7 +31,7 @@ void Dino::parse(char c) { } // If fragment delimiter, terminate current fragment and move to next. - // Unless we're in the extended message, then just append. + // Unless we're in the auxillary message fragment, then just append. else if (c == '.') { if (fragmentIndex < 3) { append('\0'); @@ -78,6 +78,7 @@ void Dino::process() { case 8: servoToggle (); break; case 9: servoWrite (); break; case 10: handleLCD (); break; + case 11: shiftWrite (); break; case 90: reset (); break; case 97: setAnalogDivider (); break; case 98: setHeartRate (); break; @@ -262,6 +263,16 @@ void Dino::handleLCD() { dinoLCD.process(val, auxMsg); } +// CMD = 11 +// Write to a shift register. +void Dino::shiftWrite() { + #ifdef debug + Serial.print("Shift write :"); Serial.print(val); Serial.print(" to pin "); Serial.print(pin); Serial.print(". Clock pin: "); Serial.println(auxMsg); + #endif + // auxMsg should be the clock pin. + shiftOut(pin, atoi(auxMsg), MSBFIRST, val); +} + // CMD = 90 void Dino::reset() { heartRate = 4000; // Default heartRate is ~4ms. diff --git a/src/lib/Dino.h b/src/lib/Dino.h index d56dfb87..28b084a9 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -26,64 +26,67 @@ class Dino { Dino(); void setupWrite(void (*writeCallback)(char *str)); void parse(char c); - void process(); void updateListeners(); private: - // Manage heartbeat and listeners. - long heartRate; - long lastUpdate; - unsigned int loopCount; - unsigned int analogDivider; - - // Listeners correspond to raw pin number by array index, and store boolean. false == disabled. - boolean analogListeners[PIN_COUNT]; - boolean digitalListeners[PIN_COUNT]; + // API-accessible functions. + void setMode (); //cmd = 0 + void dWrite (); //cmd = 1 + void dRead (); //cmd = 2 + void aWrite (); //cmd = 3 + void aRead (); //cmd = 4 + void addDigitalListener (); //cmd = 5 + void addAnalogListener (); //cmd = 6 + void removeListener (); //cmd = 7 + void servoToggle (); //cmd = 8 + void servoWrite (); //cmd = 9 + void handleLCD (); //cmd = 10 + void shiftWrite (); //cmd = 11 + void reset (); //cmd = 90 + void setAnalogDivider (); //cmd = 97 + void setHeartRate (); //cmd = 98 + + // Parser state storage and utility functions. + char *messageFragments[4]; + byte fragmentIndex; + int charIndex; + boolean backslash; + void append(char c); + void process(); - // Keep track of the last read values for digital listeners. Only write responses when changed. - byte digitalListenerValues[PIN_COUNT]; - // Parsed message storage. char cmdStr[5]; int cmd; char pinStr[5]; int pin; char valStr[5]; int val; char auxMsg[256]; - - // Parser state storage. - char *messageFragments[4]; - byte fragmentIndex; - int charIndex; - boolean backslash; // Value and response storage. int rval; char response[8]; + + // Use a write callback from the main sketch to respond. void (*_writeCallback)(char *str); void writeResponse(); + // Arduino native library variables. Servo servos[12]; - // API-accessible functions. - void setMode (); - void dWrite (); - void dRead (); - void aWrite (); - void aRead (); - void addDigitalListener (); - void addAnalogListener (); - void removeListener (); - void servoToggle (); - void servoWrite (); - void handleLCD (); - void reset (); - void setAnalogDivider (); - void setHeartRate (); + // Internal timing variables and utility functions. + long heartRate; + long lastUpdate; + unsigned int loopCount; + unsigned int analogDivider; + long timeSince (long event); + + // Listeners correspond to raw pin number by array index, and store boolean. false == disabled. + boolean analogListeners[PIN_COUNT]; + boolean digitalListeners[PIN_COUNT]; + + // Keep track of the last read values for digital listeners. Only write responses when changed. + byte digitalListenerValues[PIN_COUNT]; - // Internal functions. - void append (char c); - long timeSince (long event); + // Listener update functions. void updateDigitalListeners (); void updateAnalogListeners (); }; - #endif From b9de1ba6b45ad24f1837f9a7934dbff4abb305e5 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Sun, 2 Jun 2013 11:58:33 -0400 Subject: [PATCH 015/296] Simplify LCD initialization --- examples/lcd/lcd.rb | 5 +++-- lib/dino/components/lcd.rb | 26 +++++++++++++------------- spec/lib/components/lcd_spec.rb | 11 ++--------- 3 files changed, 18 insertions(+), 24 deletions(-) diff --git a/examples/lcd/lcd.rb b/examples/lcd/lcd.rb index 3187cdf9..f6c04507 100644 --- a/examples/lcd/lcd.rb +++ b/examples/lcd/lcd.rb @@ -7,9 +7,10 @@ board = Dino::Board.new(Dino::TxRx::Serial.new) lcd = Dino::Components::LCD.new( board: board, - pins: { rs: 12, enable: 11, d4: 5, d5: 4, d6: 3, d7: 2 } + pins: { rs: 12, enable: 11, d4: 5, d5: 4, d6: 3, d7: 2 }, + cols: 16, + rows: 2 ) -lcd.begin(16,2) lcd.puts("Hello World!") sleep diff --git a/lib/dino/components/lcd.rb b/lib/dino/components/lcd.rb index 7f314d86..b7cba3c7 100644 --- a/lib/dino/components/lcd.rb +++ b/lib/dino/components/lcd.rb @@ -6,23 +6,28 @@ class LCD < BaseComponent # # Dino::Componentes::LCD.new( # board: board, - # pins: { rs: 12, enable: 11, d4: 4, d5: 5, d6: 6, d7: 7 } + # pins: { rs: 12, enable: 11, d4: 4, d5: 5, d6: 6, d7: 7 }, + # cols: 16, + # rows: 2 # ) # # Initialize in 8 bits mode # # Dino::Componentes::LCD.new( # board: board, - # pins: { rs: 12, enable: 11, d0: 0, d1: 1, d2: 2, d3: 3, d4: 4, d5: 5, d6: 6, d7: 7 } + # pins: { rs: 12, enable: 11, d0: 0, d1: 1, d2: 2, d3: 3, d4: 4, d5: 5, d6: 6, d7: 7 }, + # cols: 16, + # rows: 2 # ) # def after_initialize(options) board.write Dino::Message.encode command: 10, value: 0, aux_message: encoded_pins + @cols, @rows = options[:cols], options[:rows] + board.write Dino::Message.encode command: 10, value: 1, aux_message: "#{@cols},#{@rows}" end - def begin(cols, rows) - board.write Dino::Message.encode command: 10, value: 1, aux_message: "#{cols},#{rows}" - sleep 2 + def puts(string) + board.write Dino::Message.encode command: 10, value: 5, aux_message: string end def clear @@ -37,10 +42,6 @@ def set_cursor(col, row) board.write Dino::Message.encode command: 10, value: 4, aux_message: "#{col},#{row}" end - def puts(string) - board.write Dino::Message.encode command: 10, value: 5, aux_message: string - end - def show_cursor board.write Dino::Message.encode command: 10, value: 6 end @@ -92,10 +93,9 @@ def right_to_left private def encoded_pins - encoded = [pins[:rs], pins[:enable], pins[:d0], - pins[:d1], pins[:d2], pins[:d3], pins[:d4], - pins[:d5], pins[:d6], pins[:d7]] - encoded.compact.join(',') + [pins[:rs], pins[:enable], pins[:d0], + pins[:d1], pins[:d2], pins[:d3], pins[:d4], + pins[:d5], pins[:d6], pins[:d7]].compact.join(',') end end end diff --git a/spec/lib/components/lcd_spec.rb b/spec/lib/components/lcd_spec.rb index 3778f9d7..dc9b2991 100644 --- a/spec/lib/components/lcd_spec.rb +++ b/spec/lib/components/lcd_spec.rb @@ -5,18 +5,11 @@ module Components describe LCD do let(:board) { mock(:board, digital_write: true, set_pin_mode: true) } - subject { LCD.new board: board, pins: { rs: 12, enable: 11, d4: 5, d5: 4, d6: 3, d7: 2 } } + subject { LCD.new board: board, pins: { rs: 12, enable: 11, d4: 5, d5: 4, d6: 3, d7: 2 }, cols: 16, rows: 2 } before do board.should_receive(:write).with("10..0.12,11,5,4,3,2\n") - end - - describe '#begin' do - it 'should initialize the display sending the command "10..1.16,2\n" to the board' do - board.should_receive(:write).with "10..1.16,2\n" - subject.should_receive(:sleep).with 2 - subject.begin(16,2) - end + board.should_receive(:write).with("10..1.16,2\n") end describe '#clear' do From 7f2bf5e7b432736afd6a2f61037c5270e7b00fa6 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Fri, 7 Jun 2013 12:31:44 -0400 Subject: [PATCH 016/296] Make sure aux messages convert to strings. --- lib/dino/message.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dino/message.rb b/lib/dino/message.rb index d60c4b8a..c4e81be1 100644 --- a/lib/dino/message.rb +++ b/lib/dino/message.rb @@ -5,7 +5,7 @@ def self.encode(options={}) pin = options[:pin] val = options[:value] aux = options[:aux_message] - aux.gsub!("\n", "\\\n") if aux + aux.to_s.gsub!("\n", "\\\n") if aux raise Exception.new('commands must be specified') unless cmd raise Exception.new('commands can only be four digits') if cmd.to_s.length > 4 From 271f626fb1cec9e900ea78cd72a0c5706fdc0693 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Fri, 7 Jun 2013 13:21:27 -0400 Subject: [PATCH 017/296] Use a non-standard port when testing to avoid hanging rspec --- spec/lib/tx_rx/tcp_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/lib/tx_rx/tcp_spec.rb b/spec/lib/tx_rx/tcp_spec.rb index 31c756f6..725b09f5 100644 --- a/spec/lib/tx_rx/tcp_spec.rb +++ b/spec/lib/tx_rx/tcp_spec.rb @@ -4,7 +4,7 @@ module Dino describe TxRx::TCP do before :each do @host = "127.0.0.1" - @port = 3466 + @port = 3467 @instance = TxRx::TCP.new(@host, @port) end @@ -14,7 +14,7 @@ module Dino end it 'should return the TCPSocket if connected' do - @server = TCPServer.new 3466 + @server = TCPServer.new @port @instance.io.should be_a TCPSocket @server.close end @@ -27,7 +27,7 @@ module Dino end it 'should use the existing io instance if set' do - @server = TCPServer.new 3466 + @server = TCPServer.new @port socket = @instance.io @instance.io.should be socket @server.close From e6e14eb5dbdb53c38d388efd90d6f71f7c2bd896 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Fri, 7 Jun 2013 15:08:16 -0400 Subject: [PATCH 018/296] Tested shift register support. Supports multiple registers via array of bytes. --- examples/shift_register.rb | 17 +++++++ lib/dino/components/shift_register.rb | 14 +++-- spec/lib/components/shift_register_spec.rb | 59 ++++++++++++++++++++++ 3 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 examples/shift_register.rb create mode 100644 spec/lib/components/shift_register_spec.rb diff --git a/examples/shift_register.rb b/examples/shift_register.rb new file mode 100644 index 00000000..fe5945b6 --- /dev/null +++ b/examples/shift_register.rb @@ -0,0 +1,17 @@ +# +# This is a simple example to write to a shift register. +# Writing a byte of 255 sets all the output pins to high. +# +require 'bundler/setup' +require 'dino' + +board = Dino::Board.new(Dino::TxRx::Serial.new) +shift_register = Dino::Components::ShiftRegister.new(pins: {data: 11, latch: 8, clock: 12}, board: board) + +# Write a single byte +shift_register.write(255) + +# Write an array of bytes (for multiple registers). +shift_register.write([255, 0]) + +sleep diff --git a/lib/dino/components/shift_register.rb b/lib/dino/components/shift_register.rb index 4c04c08c..dddacbf2 100644 --- a/lib/dino/components/shift_register.rb +++ b/lib/dino/components/shift_register.rb @@ -7,9 +7,9 @@ def after_initialize(options={}) raise 'missing pins[:clock] pin' unless self.pins[:clock] raise 'missing pins[:data] pin' unless self.pins[:data] - pins.each do |pin| + pins.each_value do |pin| set_pin_mode(pin, :out) - analog_write(pin, Board::LOW) + digital_write(pin, Board::LOW) end end @@ -21,9 +21,15 @@ def latch_on digital_write(pins[:latch], Board::HIGH) end - def write(byte) + def write(bytes) + bytes = [bytes] unless bytes.class == Array + data_pin = board.convert_pin(pins[:data]) + clock_pin = board.convert_pin(pins[:clock]) + latch_off - board.write(Dino::Message.encode command: 11, pin: convert_pin(pins[:data]), value: byte, aux_message: convert_pin(pins[:clock])}) + bytes.each do |byte| + board.write Dino::Message.encode( command: 11, pin: data_pin, value: byte, aux_message: clock_pin) + end latch_on end end diff --git a/spec/lib/components/shift_register_spec.rb b/spec/lib/components/shift_register_spec.rb new file mode 100644 index 00000000..fc7a9202 --- /dev/null +++ b/spec/lib/components/shift_register_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' + +module Dino + module Components + describe ShiftRegister do + let(:board) { mock(:board, digital_write: true, set_pin_mode: true) } + + subject { ShiftRegister.new board: board, pins: {clock: 12, data: 11, latch: 8} } + + describe '#initialize' do + it 'should set all pins to out and low' do + board.should_receive(:set_pin_mode).with(12, :out, nil) + board.should_receive(:set_pin_mode).with(11, :out, nil) + board.should_receive(:set_pin_mode).with(8, :out, nil) + board.should_receive(:digital_write).with(12, Board::LOW) + board.should_receive(:digital_write).with(11, Board::LOW) + board.should_receive(:digital_write).with(8, Board::LOW) + + ShiftRegister.new(pins: {clock: 12, data: 11, latch: 8}, board: board) + end + end + + describe '#latch_off' do + it 'should set the latch pin low' do + board.should_receive(:digital_write).with(8, Board::LOW) + + subject.latch_off + end + end + + describe '#latch_off' do + it 'should set the latch pin high' do + board.should_receive(:digital_write).with(8, Board::HIGH) + + subject.latch_on + end + end + + describe '#write' do + it 'should write a single byte as value and clock pin as aux to the data pin' do + board.should_receive(:convert_pin).with(11) { |pin| pin } + board.should_receive(:convert_pin).with(12) { |pin| pin } + board.should_receive(:write).with "11.11.255.12\n" + + subject.write(255) + end + + it 'should write an array of bytes as value and clock pin as aux to the data pin' do + board.should_receive(:convert_pin).with(11) { |pin| pin } + board.should_receive(:convert_pin).with(12) { |pin| pin } + board.should_receive(:write).with "11.11.255.12\n" + board.should_receive(:write).with "11.11.0.12\n" + + subject.write([255,0]) + end + end + end + end +end From 6a5875e960ca445f6bd1aa2aba4b18b44e8cab61 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Fri, 7 Jun 2013 17:30:19 -0400 Subject: [PATCH 019/296] Remove some accidentally committed benchmarks. --- examples/bench.rb | 110 ------------------------------------------ examples/benchmark.rb | 69 -------------------------- 2 files changed, 179 deletions(-) delete mode 100644 examples/bench.rb delete mode 100644 examples/benchmark.rb diff --git a/examples/bench.rb b/examples/bench.rb deleted file mode 100644 index 14cf0d93..00000000 --- a/examples/bench.rb +++ /dev/null @@ -1,110 +0,0 @@ -# -# This is a simple example to blink an led -# every half a second -# - -require File.expand_path('../../lib/dino', __FILE__) - -board = Dino::Board.new(Dino::TxRx::Serial.new) - -# Keep a led blinking. -led = Dino::Components::Led.new(pin: '13', board: board) -led_thread = Thread.new do - [:on, :off].cycle do |switch| - led.send(switch) - sleep 0.5 - end -end - -# Keep a servo moving. -servo = Dino::Components::Servo.new(pin: 9, board: board) -servo_thread = Thread.new do - [0, 90].cycle do |pos| - servo.position = pos - sleep 0.3 - end -end - -# Keep a servo moving. -servo2 = Dino::Components::Servo.new(pin: 10, board: board) -servo_thread2 = Thread.new do - [0, 90].cycle do |pos| - servo2.position = pos - sleep 0.5 - end -end - -# Keep a servo moving. -servo3 = Dino::Components::Servo.new(pin: 11, board: board) -servo_thread3 = Thread.new do - [0, 90].cycle do |pos| - servo3.position = pos - sleep 0.5 - end -end - - - -$counter_analog = 0 -counter_digital = 0 -test_time = 5 - -puts "Starting tests..." - -# Ranges of digital and analog pin numbers to read. -digital_range = 2..5 -analog_range = 0..5 - -digital_range.each do |digital_max| - analog_range.each do |analog_max| - - # Define ranges for this test test. - digital_test_range = digital_range.min..digital_max - analog_test_range = analog_range.min..analog_max - - # Setup hardware - analog_test_range.each do |pin| - eval "$sensor#{pin} = Dino::Components::Sensor.new(pin: 'A#{pin}', board: board)" - eval "$sensor#{pin}.when_data_received { $counter_analog = $counter_analog + 1 }" - end - digital_test_range.each do |pin| - eval "$digital#{pin} = Dino::Components::Button.new(pin: '#{pin}', board: board, pullup: true)" - eval "$digital#{pin}.up { counter_digital = counter_digital + 1 }" - eval "$digital#{pin}.down { counter_digital = counter_digital + 1 }" - end - - # Pre-test measurement - state1_digital = counter_digital - state1_analog = $counter_analog - - # Wait - sleep test_time - - # Post-test measurement - state2_digital = counter_digital - state2_analog = $counter_analog - - # Calculation - diff_digital = state2_digital - state1_digital - diff_analog = state2_analog - state1_analog - - # Print - print "#{digital_test_range.count}D/#{analog_test_range.count}A Components: " - print "#{diff_digital/(test_time * digital_test_range.count)}Hz" - print "/" - print "#{diff_analog/(test_time * analog_test_range.count)}Hz per component | " - puts "#{(diff_digital + diff_analog)/(test_time)} responses/s overall" - - # Remove hardware - analog_test_range.each do |pin| - board.remove_analog_hardware(eval "$sensor#{pin}") - end - digital_test_range.each do |pin| - board.remove_digital_hardware(eval "$digital#{pin}") - end - end -end - -servo_thread.kill -led_thread.kill - diff --git a/examples/benchmark.rb b/examples/benchmark.rb deleted file mode 100644 index fb6e7313..00000000 --- a/examples/benchmark.rb +++ /dev/null @@ -1,69 +0,0 @@ -# -# This is a simple example to blink an led -# every half a second -# - -require File.expand_path('../../lib/dino', __FILE__) -txrx = Dino::TxRx.new -# txrx = Dino::TxRx::TCP.new("192.168.0.77") -# txrx = Dino::TxRx::TCP.new("192.168.0.143", 3001) # ; txrx.io; sleep 5 -board = Dino::Board.new(txrx) - -txrx.write("!9800003.") - -# Warm up -sleep 2 - -counter_analog = 0 -counter_digital = 0 -count_analog_responses = Proc.new { counter_analog = counter_analog + 1 } -test_time = 4 - -puts "Starting tests..." - -(5..9).each do |digital_count| - (0..5).each do |analog_count| - - # Setup hardware - (0..analog_count).each do |pin| - eval "$sensor#{pin} = Dino::Components::Sensor.new(pin: 'A#{pin}', board: board)" - eval "$sensor#{pin}.when_data_received(count_analog_responses)" - end - (5..digital_count).each do |pin| - eval "$digital#{pin} = Dino::Components::Button.new(pin: '#{pin}', board: board, pullup: true)" - eval "$digital#{pin}.up { counter_digital = counter_digital + 1 }" - eval "$digital#{pin}.down { counter_digital = counter_digital + 1 }" - end - - # Pre-test measurement - state1_digital = counter_digital - state1_analog = counter_analog - - # Wait - sleep test_time - - # Post-test measurement - state2_digital = counter_digital - state2_analog = counter_analog - - # Calculation - diff_digital = state2_digital - state1_digital - diff_analog = state2_analog - state1_analog - - # Print - print "#{digital_count -4}D/#{analog_count + 1}A Components: " - print "#{diff_digital/(test_time * (digital_count - 4))}Hz" - print "/" - print "#{diff_analog/(test_time * (analog_count + 1))}Hz per component | " - puts "#{(diff_digital + diff_analog)/(test_time)} responses/s overall" - - # Remove hardware - (0..analog_count).each do |pin| - board.remove_analog_hardware(eval "$sensor#{pin}") - end - (5..digital_count).each do |pin| - board.remove_digital_hardware(eval "$digital#{pin}") - end - end -end - From 1e2f4fc068db6549218569f93f560ca36bc60275 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Sat, 8 Jun 2013 23:59:23 -0400 Subject: [PATCH 020/296] Set the heart rate in microseconds --- lib/dino/board.rb | 2 +- src/lib/Dino.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/dino/board.rb b/lib/dino/board.rb index 150a2cbc..2c140c4f 100644 --- a/lib/dino/board.rb +++ b/lib/dino/board.rb @@ -23,7 +23,7 @@ def analog_divider=(value) end def heart_rate=(value) - write Dino::Message.encode(command: 98, value: value) + write Dino::Message.encode(command: 98, aux_message: value) end def start_read diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 14cb016a..a61b579a 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -302,8 +302,7 @@ void Dino::setAnalogDivider() { // CMD = 98 // Set the heart rate in milliseconds. Store it in microseconds. void Dino::setHeartRate() { - int rate = val; - heartRate = (rate * 1000); + heartRate = atoi(auxMsg); #ifdef debug Serial.print("Heart rate set to "); Serial.print(heartRate); Serial.println(" microseconds"); #endif From b782ff149d41a47343fb8e5305a401e0bbf05206 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Sun, 9 Jun 2013 03:29:47 -0400 Subject: [PATCH 021/296] CLI update: Ruby module, UNIX-ey output, auto-upload on Macs --- bin/dino | 135 ++---------------- lib/dino/board.rb | 2 +- lib/dino/tx_rx.rb | 2 +- lib/dino/version.rb | 2 +- lib/dino_cli.rb | 21 +++ lib/dino_cli/generator.rb | 82 +++++++++++ lib/dino_cli/helper.rb | 42 ++++++ lib/dino_cli/parser.rb | 60 ++++++++ lib/dino_cli/uploader.rb | 19 +++ .../du_ethernet.ino => dino_ethernet.ino} | 0 src/{du/du.ino => dino_serial.ino} | 0 src/{du_wifi/du_wifi.ino => dino_wifi.ino} | 0 12 files changed, 239 insertions(+), 126 deletions(-) create mode 100644 lib/dino_cli.rb create mode 100644 lib/dino_cli/generator.rb create mode 100644 lib/dino_cli/helper.rb create mode 100644 lib/dino_cli/parser.rb create mode 100644 lib/dino_cli/uploader.rb rename src/{du_ethernet/du_ethernet.ino => dino_ethernet.ino} (100%) rename src/{du/du.ino => dino_serial.ino} (100%) rename src/{du_wifi/du_wifi.ino => dino_wifi.ino} (100%) diff --git a/bin/dino b/bin/dino index 381c9bcf..63d5b20d 100755 --- a/bin/dino +++ b/bin/dino @@ -1,128 +1,17 @@ #!/usr/bin/env ruby -require "pathname" -require "fileutils" - -SERIAL = "du" -ETHERNET = "du_ethernet" -WIFI = "du_wifi" - -$options = {} -$options[:sketch_names] = [] - -def error(message) - $stderr.puts "Error: " + message - usage -end - -def usage - $stderr.puts "Usage: #{File.basename($0)} COMMAND [command-specific-options]" - $stderr.puts - $stderr.puts "Commands:" - $stderr.puts " generate-sketch SKETCH [options]" - $stderr.puts - $stderr.puts " Available sketches and options:" - $stderr.puts - $stderr.puts " serial" - $stderr.puts " --baud BAUD" - $stderr.puts " --debug" - $stderr.puts - $stderr.puts " ethernet" - $stderr.puts " --mac XX:XX:XX:XX:XX:XX" - $stderr.puts " --ip XXX.XXX.XXX.XXX" - $stderr.puts " --port PORT" - $stderr.puts " --debug" - $stderr.puts - $stderr.puts " wifi" - $stderr.puts " --ssid SSID" - $stderr.puts " --password PASSWORD" - $stderr.puts " --port PORT" - $stderr.puts " --debug" - $stderr.puts - exit(2) -end +# encoding: UTF-8 -# Command must be the first argument. -$options[:command] = ARGV.shift -usage if $options[:command].match /help|usage/ - -# Parse the rest loosely. -loop do - case ARGV[0] - when 'serial' - ARGV.shift; $options[:sketch_names] << SERIAL unless $options[:sketch_names].include? SERIAL - when 'ethernet' - ARGV.shift; $options[:sketch_names] << ETHERNET unless $options[:sketch_names].include? ETHERNET - when 'wifi' - ARGV.shift; $options[:sketch_names] << WIFI unless $options[:sketch_names].include? WIFI - when '--baud' - ARGV.shift; $options[:baud] = ARGV.shift - when '--mac' - ARGV.shift; $options[:mac] = ARGV.shift - when '--ip' - ARGV.shift; $options[:ip] = ARGV.shift - when '--ssid' - ARGV.shift; $options[:ssid] = ARGV.shift - when '--password' - ARGV.shift; $options[:password] = ARGV.shift - when '--port' - ARGV.shift; $options[:port] = ARGV.shift - when '--debug' - ARGV.shift; $options[:debug] = true - when /^-/ - error "Invalid argument '#{ARGV[0]}'" - else break - end -end - -error "Invalid command '#{$options[:command]}'" unless $options[:command] == "generate-sketch" -error "No sketches or invalid sketches specified" if $options[:sketch_names].empty? - -$options[:sketch_names].each do |sketch_name| - # Define the sources. - src_dir = Pathname.new(__FILE__).realpath.to_s.chomp("/bin/dino") + "/src" - sketch_dir = sketch_name - sketch_filename = sketch_name + ".ino" - lib_filenames = ["Dino.h", "Dino.cpp", "DinoLCD.h", "DinoLCD.cpp"] - - # Read the files. - libs = lib_filenames.map { |f| File.read(File.join(src_dir, "lib", f)) } - sketch = File.read(File.join(src_dir, sketch_dir, sketch_filename)) +# Find myself +require "pathname" +bin_file = Pathname.new(__FILE__).realpath - # Modify them based on the arguments. - if $options[:baud] && sketch_name == SERIAL - sketch.gsub! "115200", $options[:baud] - end - if $options[:mac] && sketch_name == ETHERNET - octets = $options[:mac].split(':') - bytes = octets.map { |o| "0x#{o.upcase}" } - sketch.gsub! "{ 0xDE, 0xAD, 0xBE, 0x30, 0x31, 0x32 }", bytes.inspect.gsub("[", "{").gsub("]", "}").gsub("\"", "") - end - if $options[:ip] && sketch_name == ETHERNET - sketch.gsub! "192,168,0,77", $options[:ip].gsub(".", ",") - end - if $options[:ssid] && sketch_name == WIFI - sketch.gsub! "yourNetwork", $options[:ssid] - end - if $options[:password] && sketch_name == WIFI - sketch.gsub! "yourPassword", $options[:password] - end - if $options[:port] - sketch.gsub! "int port = 3466", "int port = #{$options[:port]}" - end - if $options[:debug] - libs[0].gsub! "// #define debug true", "#define debug true" - end +# Make sure dino is in the load path +$:.unshift File.expand_path("../../lib", bin_file) - # Define the destinations. - working_dir = Dir.pwd - dest_dir = File.join working_dir, sketch_dir - Dir::mkdir dest_dir - dest_sketch = File.join(dest_dir, sketch_filename) - dest_libs = lib_filenames.map { |f| File.join(dest_dir, f)} +# Work out paths for the CLI +working_dir = Dir.pwd +src_dir = bin_file.to_s.chomp("/bin/dino") + "/src" - # Write the files. - File.open(dest_sketch, 'w') { |f| f.write sketch } - dest_libs.each_with_index do |file, index| - File.open(file, 'w') { |f| f.write libs[index]} - end -end \ No newline at end of file +# Start the CLI +require "dino_cli" +DinoCLI.start(working_dir: working_dir, src_dir: src_dir, args: ARGV) diff --git a/lib/dino/board.rb b/lib/dino/board.rb index 2c140c4f..94723fb0 100644 --- a/lib/dino/board.rb +++ b/lib/dino/board.rb @@ -99,4 +99,4 @@ def convert_pin(pin) pin.to_s.match(/\Aa/i) ? @analog_zero + pin.to_s.gsub(/\Aa/i, '').to_i : pin.to_i end end -end \ No newline at end of file +end diff --git a/lib/dino/tx_rx.rb b/lib/dino/tx_rx.rb index 89c9d82f..d0399eae 100644 --- a/lib/dino/tx_rx.rb +++ b/lib/dino/tx_rx.rb @@ -8,4 +8,4 @@ def self.new(options={}) self::Serial.new(options) end end -end \ No newline at end of file +end diff --git a/lib/dino/version.rb b/lib/dino/version.rb index 53a79fa4..304dd5e5 100644 --- a/lib/dino/version.rb +++ b/lib/dino/version.rb @@ -1,3 +1,3 @@ module Dino - VERSION = "0.11.2" + VERSION = "0.12.0" end diff --git a/lib/dino_cli.rb b/lib/dino_cli.rb new file mode 100644 index 00000000..c3d432dc --- /dev/null +++ b/lib/dino_cli.rb @@ -0,0 +1,21 @@ +module DinoCLI + require "dino_cli/parser" + require "dino_cli/generator" + require "dino_cli/uploader" + + COMMANDS = ["generate-sketch"] + SKETCHES = ["serial", "ethernet", "wifi"] + + def self.start(options={}) + parsed_options = DinoCLI::Parser.run(options) + + method = parsed_options[:command].gsub('-', '_').to_s + self.send method, options + end + + def self.generate_sketch(options) + sketch_file = DinoCLI::Generator.run!(options) + + options[:upload] ? DinoCLI::Uploader.run!(file: sketch_file) : $stdout.puts(sketch_file) + end +end diff --git a/lib/dino_cli/generator.rb b/lib/dino_cli/generator.rb new file mode 100644 index 00000000..4f0d9742 --- /dev/null +++ b/lib/dino_cli/generator.rb @@ -0,0 +1,82 @@ +class DinoCLI::Generator + require "fileutils" + LIB_FILENAMES = ["Dino.h", "Dino.cpp", "DinoLCD.h", "DinoLCD.cpp"] + attr_reader :options + + def initialize(options={}) + @options = options + end + + def self.run!(options={}) + instance = self.new(options) + + instance.read + instance.modify + instance.write + end + + def read + @libs = LIB_FILENAMES.map do |f| + File.read(File.join(options[:src_dir], "lib", f)) + end + @sketch = File.read File.join(options[:src_dir], sketch_filename) + end + + def modify + if options[:baud] && serial? + @sketch.gsub! "115200", options[:baud] + end + if options[:mac] && ethernet? + octets = @options[:mac].split(':') + bytes = octets.map { |o| "0x#{o.upcase}" } + @sketch.gsub! "{ 0xDE, 0xAD, 0xBE, 0x30, 0x31, 0x32 }", bytes.inspect.gsub("[", "{").gsub("]", "}").gsub("\"", "") + end + if options[:ip] && ethernet? + @sketch.gsub! "192,168,0,77", options[:ip].gsub(".", ",") + end + if options[:ssid] && wifi? + @sketch.gsub! "yourNetwork", options[:ssid] + end + if options[:password] && wifi? + @sketch.gsub! "yourPassword", options[:password] + end + if options[:port] + @sketch.gsub! "int port = 3466", "int port = #{options[:port]}" + end + if options[:debug] + @libs[0].gsub! "// #define debug true", "#define debug true" + end + end + + def write + sketch = File.join(output_dir, sketch_filename) + File.open(sketch, 'w') { |f| f.write @sketch } + + libs = LIB_FILENAMES.map { |f| File.join(output_dir, f)} + libs.each_with_index do |file, index| + File.open(file, 'w') { |f| f.write @libs[index]} + end + + sketch + end + + def output_dir + @output_dir ||= make_output_dir + end + + def make_output_dir + dir = File.join options[:working_dir], options[:sketch_name] + Dir::mkdir(dir) unless File.directory?(dir) + dir + end + + %w(serial ethernet wifi).each do |sketch| + define_method("#{sketch}?") do + options[:sketch_name].match /#{sketch}/ + end + end + + def sketch_filename + "#{options[:sketch_name]}.ino" + end +end diff --git a/lib/dino_cli/helper.rb b/lib/dino_cli/helper.rb new file mode 100644 index 00000000..fece1920 --- /dev/null +++ b/lib/dino_cli/helper.rb @@ -0,0 +1,42 @@ +module DinoCLI::Helper + def error(message) + $stderr.puts + $stderr.puts "Error: #{message}" + $stderr.puts + usage + end + + def usage + $stderr.puts "Usage:" + $stderr.puts " dino COMMAND [command-specific-options]" + $stderr.puts + $stderr.puts "Commands:" + $stderr.puts " generate-sketch SKETCH [options]" + $stderr.puts + $stderr.puts " Available sketches and options:" + $stderr.puts + $stderr.puts " serial" + $stderr.puts " --baud BAUD" + $stderr.puts " --debug" + $stderr.puts " --upload" + $stderr.puts + $stderr.puts " ethernet" + $stderr.puts " --mac XX:XX:XX:XX:XX:XX" + $stderr.puts " --ip XXX.XXX.XXX.XXX" + $stderr.puts " --port PORT" + $stderr.puts " --debug" + $stderr.puts " --upload" + $stderr.puts + $stderr.puts " wifi" + $stderr.puts " --ssid SSID" + $stderr.puts " --password PASSWORD" + $stderr.puts " --port PORT" + $stderr.puts " --debug" + $stderr.puts " --upload" + $stderr.puts + $stderr.puts "Note: Automatic upload requires Arduino IDE 1.5 or greater." + $stderr.puts " Set your board type and serialport in the IDE first." + $stderr.puts + exit(2) + end +end diff --git a/lib/dino_cli/parser.rb b/lib/dino_cli/parser.rb new file mode 100644 index 00000000..1af902a7 --- /dev/null +++ b/lib/dino_cli/parser.rb @@ -0,0 +1,60 @@ +class DinoCLI::Parser + require 'dino_cli/helper' + include DinoCLI::Helper + + def initialize(options={}) + @options = options + end + + def self.run(options={}) + self.new(options).parse + end + + def parse + args = @options[:args].dup + + # Command must be the first arg. + @options[:command] = args.shift + usage if @options[:command].match /help|usage/ + error "Invalid command '#{@options[:command]}'" unless DinoCLI::COMMANDS.include? @options[:command] + + # Parse the rest loosely. + loop do + case args[0] + when 'serial' + args.shift; set_sketch("serial") + when 'ethernet' + args.shift; set_sketch("ethernet") + when 'wifi' + args.shift; set_sketch("wifi") + when '--baud' + args.shift; @options[:baud] = args.shift + when '--mac' + args.shift; @options[:mac] = args.shift + when '--ip' + args.shift; @options[:ip] = args.shift + when '--ssid' + args.shift; @options[:ssid] = args.shift + when '--password' + args.shift; @options[:password] = args.shift + when '--port' + args.shift; @options[:port] = args.shift + when '--debug' + args.shift; @options[:debug] = true + when '--upload' + args.shift; @options[:upload] = true + when /^-/ + error "Invalid argument '#{ARGV[0]}'" + else break + end + end + error "No valid sketch specified" if @options[:sketch_name].nil? + + @options + end + + def set_sketch(name) + error "More than one sketch specified" unless @options[:sketch_name].nil? + @options[:sketch_name] = "dino_#{name}" + end +end diff --git a/lib/dino_cli/uploader.rb b/lib/dino_cli/uploader.rb new file mode 100644 index 00000000..cd92df43 --- /dev/null +++ b/lib/dino_cli/uploader.rb @@ -0,0 +1,19 @@ +class DinoCLI::Uploader + attr_reader :options + + def initialize(options={}) + @options = options + end + + def self.run!(options={}) + instance = self.new(options) + + instance.upload + end + + def upload + if RUBY_PLATFORM.match /darwin/i + system("/Applications/Arduino.app/Contents/MacOS/JavaApplicationStub --upload '#{options[:file]}'") + end + end +end diff --git a/src/du_ethernet/du_ethernet.ino b/src/dino_ethernet.ino similarity index 100% rename from src/du_ethernet/du_ethernet.ino rename to src/dino_ethernet.ino diff --git a/src/du/du.ino b/src/dino_serial.ino similarity index 100% rename from src/du/du.ino rename to src/dino_serial.ino diff --git a/src/du_wifi/du_wifi.ino b/src/dino_wifi.ino similarity index 100% rename from src/du_wifi/du_wifi.ino rename to src/dino_wifi.ino From d73ae9918983ba2e6c222c84f5e8424d345a1345 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Sun, 9 Jun 2013 15:01:57 -0400 Subject: [PATCH 022/296] Add CLI option to compile to .hex file --- lib/dino_cli.rb | 36 ++++++++++++++++++++---------------- lib/dino_cli/generator.rb | 9 +++++---- lib/dino_cli/parser.rb | 2 ++ lib/dino_cli/uploader.rb | 26 ++++++++++++++++++++++---- 4 files changed, 49 insertions(+), 24 deletions(-) diff --git a/lib/dino_cli.rb b/lib/dino_cli.rb index c3d432dc..bffe309e 100644 --- a/lib/dino_cli.rb +++ b/lib/dino_cli.rb @@ -1,21 +1,25 @@ module DinoCLI - require "dino_cli/parser" - require "dino_cli/generator" - require "dino_cli/uploader" + require "dino_cli/parser" + require "dino_cli/generator" + require "dino_cli/uploader" - COMMANDS = ["generate-sketch"] - SKETCHES = ["serial", "ethernet", "wifi"] - - def self.start(options={}) - parsed_options = DinoCLI::Parser.run(options) + COMMANDS = ["generate-sketch"] + SKETCHES = ["serial", "ethernet", "wifi"] + + def self.start(options={}) + parsed_options = DinoCLI::Parser.run(options) - method = parsed_options[:command].gsub('-', '_').to_s - self.send method, options - end + method = parsed_options[:command].gsub('-', '_').to_s + self.send method, options + end - def self.generate_sketch(options) - sketch_file = DinoCLI::Generator.run!(options) - - options[:upload] ? DinoCLI::Uploader.run!(file: sketch_file) : $stdout.puts(sketch_file) - end + def self.generate_sketch(options) + options = DinoCLI::Generator.run!(options) + + if options[:upload] || options[:compile] + DinoCLI::Uploader.run!(options) + else + $stdout.puts options[:sketch_file] + end + end end diff --git a/lib/dino_cli/generator.rb b/lib/dino_cli/generator.rb index 4f0d9742..fab3e36d 100644 --- a/lib/dino_cli/generator.rb +++ b/lib/dino_cli/generator.rb @@ -1,7 +1,7 @@ class DinoCLI::Generator require "fileutils" LIB_FILENAMES = ["Dino.h", "Dino.cpp", "DinoLCD.h", "DinoLCD.cpp"] - attr_reader :options + attr_accessor :options def initialize(options={}) @options = options @@ -57,11 +57,12 @@ def write File.open(file, 'w') { |f| f.write @libs[index]} end - sketch + options[:sketch_file] = sketch + options end def output_dir - @output_dir ||= make_output_dir + options[:output_dir] ||= make_output_dir end def make_output_dir @@ -77,6 +78,6 @@ def make_output_dir end def sketch_filename - "#{options[:sketch_name]}.ino" + options[:sketch_filename] ||= "#{options[:sketch_name]}.ino" end end diff --git a/lib/dino_cli/parser.rb b/lib/dino_cli/parser.rb index 1af902a7..87c76236 100644 --- a/lib/dino_cli/parser.rb +++ b/lib/dino_cli/parser.rb @@ -41,6 +41,8 @@ def parse args.shift; @options[:port] = args.shift when '--debug' args.shift; @options[:debug] = true + when '--compile' + args.shift; @options[:compile] = true when '--upload' args.shift; @options[:upload] = true when /^-/ diff --git a/lib/dino_cli/uploader.rb b/lib/dino_cli/uploader.rb index cd92df43..d7d4327b 100644 --- a/lib/dino_cli/uploader.rb +++ b/lib/dino_cli/uploader.rb @@ -1,4 +1,6 @@ class DinoCLI::Uploader + require "pathname" + require "fileutils" attr_reader :options def initialize(options={}) @@ -8,12 +10,28 @@ def initialize(options={}) def self.run!(options={}) instance = self.new(options) - instance.upload + if options[:upload] + instance.upload + elsif options[:compile] + instance.compile + end + end + + def compile + output = `#{executable} --verify --verbose '#{options[:sketch_file]}'` + + tmp_hex_file = output.match(/elf.*.hex/)[0].gsub("elf", "").lstrip + FileUtils::copy(tmp_hex_file, options[:output_dir]) + + hex_file = File.join(options[:output_dir], Pathname.new(tmp_hex_file).basename) + $stdout.puts hex_file end def upload - if RUBY_PLATFORM.match /darwin/i - system("/Applications/Arduino.app/Contents/MacOS/JavaApplicationStub --upload '#{options[:file]}'") - end + `#{executable} --upload '#{options[:sketch_file]}'"` + end + + def executable + return "/Applications/Arduino.app/Contents/MacOS/JavaApplicationStub" if RUBY_PLATFORM.match(/darwin/i) end end From 9cd72bc54ac9976ff485af7bf3bd4b5f960256f2 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Mon, 10 Jun 2013 22:11:16 -0400 Subject: [PATCH 023/296] Improved Sensor class: Keyed callbacks Use either listeners or instantaneous reading Better example at examples/sensor/sensor.rb --- examples/sensor/sensor.rb | 33 ++++++++++-- lib/dino/board.rb | 30 +++-------- lib/dino/components/button.rb | 5 +- lib/dino/components/sensor.rb | 40 +++++++++++--- lib/dino/message.rb | 2 +- spec/lib/board_spec.rb | 84 +++++++----------------------- spec/lib/components/button_spec.rb | 10 ++-- spec/lib/components/sensor_spec.rb | 68 +++++++++++++++++------- 8 files changed, 145 insertions(+), 127 deletions(-) diff --git a/examples/sensor/sensor.rb b/examples/sensor/sensor.rb index af2e88b4..768cfd11 100644 --- a/examples/sensor/sensor.rb +++ b/examples/sensor/sensor.rb @@ -8,10 +8,35 @@ require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) +board.analog_divider = 128 sensor = Dino::Components::Sensor.new(pin: 'A0', board: board) -sensor.when_data_received do |data| - puts data -end +# Single read with block as callback. Fires only once. +sensor.read { |value| puts "Single read value: #{value}" }; sleep 1 -sleep +# Continuous listen with block as callback. Fires every time data is received until #stop_listening is called. +sensor.listen { |value| puts "Listening. Read value: #{value}" }; sleep 5 + +# Stop listening. Automatically removes the callback from the #listen block. +sensor.stop_listening; sleep 1 + +# Add a persistent callback. +sensor.on_data { |value| puts "Persistent callback: #{value}" } + +# Add a keyed callback. +sensor.on_data(:test) { |value| puts "Keyed callback: #{value}"} + +# Single read. All callbacks fire, block given fires only once. +sensor.read { |value| puts "#read block. Value: #{value}" }; sleep 1 + +# Continuous listen. All callbacks added with #on_data continue to fire, plus the block. +sensor.listen { |value| puts "#listen block. Value: #{value}" }; sleep 5 + +# Stop listening. Automatically removes the callback from the #listen block. +sensor.stop_listening + +# Remove callbacks keyed with :test. +sensor.clear_callbacks(:test) + +# Remove all callbacks. +sensor.clear_callbacks diff --git a/lib/dino/board.rb b/lib/dino/board.rb index 94723fb0..be83cb05 100644 --- a/lib/dino/board.rb +++ b/lib/dino/board.rb @@ -1,16 +1,12 @@ module Dino class Board - attr_reader :digital_hardware, :analog_hardware, :analog_zero + attr_reader :input_hardware, :analog_zero LOW, HIGH = 000, 255 DIVIDERS = [1, 2, 4, 8, 16, 32, 64, 128] def initialize(io) - @io, @digital_hardware, @analog_hardware = io, [], [] + @io, @input_hardware = io, [] io.add_observer(self) - handshake - end - - def handshake @analog_zero = @io.handshake end @@ -39,31 +35,19 @@ def write(msg) end def update(pin, msg) - (@digital_hardware + @analog_hardware).each do |part| + @input_hardware.each do |part| part.update(msg) if convert_pin(pin) == convert_pin(part.pin) end end - def add_digital_hardware(part) - set_pin_mode(part.pin, :in, part.pullup) - digital_listen(part.pin) - @digital_hardware << part - end - - def remove_digital_hardware(part) - stop_listener(part.pin) - @digital_hardware.delete(part) - end - - def add_analog_hardware(part) + def add_input_hardware(part) + @input_hardware << part set_pin_mode(part.pin, :in, part.pullup) - analog_listen(part.pin) - @analog_hardware << part end - def remove_analog_hardware(part) + def remove_input_hardware(part) stop_listener(part.pin) - @analog_hardware.delete(part) + @input_hardware.delete(part) end def set_pin_mode(pin, mode, pullup=nil) diff --git a/lib/dino/components/button.rb b/lib/dino/components/button.rb index fdcbc06d..9df42225 100644 --- a/lib/dino/components/button.rb +++ b/lib/dino/components/button.rb @@ -7,8 +7,9 @@ class Button < BaseComponent def after_initialize(options={}) @down_callbacks, @up_callbacks, @state = [], [], UP - self.board.add_digital_hardware(self) - self.board.start_read + board.add_input_hardware(self) + board.start_read + board.digital_listen(self.pin) end def down(&callback) diff --git a/lib/dino/components/sensor.rb b/lib/dino/components/sensor.rb index 485f389d..7dffc0ac 100644 --- a/lib/dino/components/sensor.rb +++ b/lib/dino/components/sensor.rb @@ -2,19 +2,45 @@ module Dino module Components class Sensor < BaseComponent def after_initialize(options={}) - @data_callbacks = [] - @board.add_analog_hardware(self) - @board.start_read + clear_callbacks + board.add_input_hardware(self) + board.start_read end - def when_data_received(&block) - @data_callbacks << block + def read(&block) + add_callback(:read, &block) if block_given? + board.analog_read(pin) + end + + def listen(&block) + add_callback(:listen, &block) if block_given? + board.analog_listen(pin) + end + + def stop_listening + board.stop_listener(pin) + clear_callbacks[:listen] + end + + def add_callback(key=nil, &block) + key ||= :persistent + @callbacks[key] ||= [] + @callbacks[key] << block + end + + alias :on_data :add_callback + + def clear_callbacks(key=nil) + key ? @callbacks[key] = nil : @callbacks = {} end def update(data) - @data_callbacks.each do |callback| - callback.call(data) + @callbacks.each_value do |array| + array.each do |callback| + callback.call(data) + end end + @callbacks[:read] = [] end end end diff --git a/lib/dino/message.rb b/lib/dino/message.rb index c4e81be1..6dfa1930 100644 --- a/lib/dino/message.rb +++ b/lib/dino/message.rb @@ -7,7 +7,7 @@ def self.encode(options={}) aux = options[:aux_message] aux.to_s.gsub!("\n", "\\\n") if aux - raise Exception.new('commands must be specified') unless cmd + raise Exception.new('command must be specified') unless cmd raise Exception.new('commands can only be four digits') if cmd.to_s.length > 4 raise Exception.new('pins can only be four digits') if pin.to_s.length > 4 raise Exception.new('values can only be four digits') if val.to_s.length > 4 diff --git a/spec/lib/board_spec.rb b/spec/lib/board_spec.rb index 923977a4..968885b8 100644 --- a/spec/lib/board_spec.rb +++ b/spec/lib/board_spec.rb @@ -27,32 +27,18 @@ def io_mock(methods = {}) end describe '#update' do - context 'when the given pin connects to an analog hardware part' do + context 'when the given pin connects to input hardware part' do it 'should call update with the message on the part' do part = mock(:part, pin: 7, pullup: nil) - subject.add_analog_hardware(part) + subject.add_input_hardware(part) other_part = mock(:part, pin: 9, pullup: nil) - subject.add_analog_hardware(other_part) + subject.add_input_hardware(other_part) part.should_receive(:update).with('wake up!') subject.update(7, 'wake up!') end end - context 'when the given pin connects to an digital hardware part' do - it 'should call update with the message on the part' do - part = mock(:part, pin: 5, pullup: nil) - subject.add_digital_hardware(part) - other_part = mock(:part, pin: 11, pullup: nil) - subject.add_digital_hardware(other_part) - - part.should_receive(:update).with('wake up!') - other_part.should_not_receive(:update).with('wake up!') - - subject.update(5, 'wake up!') - end - end - context 'when the given pin is not connected' do it 'should not do anything' do expect { @@ -62,61 +48,34 @@ def io_mock(methods = {}) end end - describe '#digital_hardware' do - it 'should initialize as empty' do - subject.digital_hardware.should == [] - end - end - - describe '#analog_hardware' do + describe '#input_hardware' do it 'should initialize as empty' do - subject.analog_hardware.should == [] + subject.input_hardware.should == [] end end - describe '#add_digital_hardware' do + describe '#add_input_hardware' do it 'should add digital hardware to the board' do - subject.add_digital_hardware(mock1 = mock(:part1, pin: 12, pullup: nil)) - subject.add_digital_hardware(mock2 = mock(:part2, pin: 14, pullup: nil)) - subject.digital_hardware.should =~ [mock1, mock2] + subject.add_input_hardware(mock1 = mock(:part1, pin: 12, pullup: nil)) + subject.add_input_hardware(mock2 = mock(:part2, pin: 14, pullup: nil)) + subject.input_hardware.should =~ [mock1, mock2] end - it 'should set the mode for the given pin to "in" and add a digital listener' do + it 'should set the mode for the given pin to "in"' do subject.should_receive(:set_pin_mode).with(12, :in, nil) - subject.should_receive(:digital_listen).with(12) - subject.add_digital_hardware(mock(:part1, pin: 12, pullup: nil)) + subject.add_input_hardware(mock(:part1, pin: 12, pullup: nil)) end end - describe '#remove_digital_hardware' do - it 'should remove the given part from the hardware of the board' do + describe '#remove_input_hardware' do + it 'should remove the given part from the hardware of the board and stop listening' do mock = mock(:part1, pin: 12, pullup: nil) - subject.add_digital_hardware(mock) - subject.remove_digital_hardware(mock) - subject.digital_hardware.should == [] - end - end - - describe '#add_analog_hardware' do - it 'should add analog hardware to the board' do - subject.add_analog_hardware(mock1 = mock(:part1, pin: 12, pullup: nil)) - subject.add_analog_hardware(mock2 = mock(:part2, pin: 14, pullup: nil)) - subject.analog_hardware.should =~ [mock1, mock2] - end - - it 'should set the mode for the given pin to "in" and add an analog listener' do - subject.should_receive(:set_pin_mode).with(12, :in, nil) - subject.should_receive(:analog_listen).with(12) - subject.add_analog_hardware(mock1 = mock(:part1, pin: 12, pullup: nil)) - end - end + subject.add_input_hardware(mock) - describe '#remove_analog_hardware' do - it 'should remove the given part from the hardware of the board' do - mock = mock(:part1, pin: 12, pullup: nil) - subject.add_analog_hardware(mock) - subject.remove_analog_hardware(mock) - subject.analog_hardware.should == [] + subject.should_receive(:stop_listener).with(12) + subject.remove_input_hardware(mock) + + subject.input_hardware.should == [] end end @@ -225,13 +184,6 @@ def io_mock(methods = {}) end end - describe '#handshake' do - it 'should tell the board to reset to defaults' do - io_mock.should_receive(:handshake) - subject.handshake - end - end - describe '#convert_pin' do before(:each) { subject.instance_variable_set(:@analog_zero, 14) } diff --git a/spec/lib/components/button_spec.rb b/spec/lib/components/button_spec.rb index 052d7f7b..5eedfa05 100644 --- a/spec/lib/components/button_spec.rb +++ b/spec/lib/components/button_spec.rb @@ -16,16 +16,18 @@ module Components }.to raise_exception end - it 'should add itself to the board and start reading' do + it 'should add itself to the board, start read and digital listen' do board = mock(:board) - board.should_receive(:add_digital_hardware) + board.should_receive(:add_input_hardware) board.should_receive(:start_read) - Button.new(board: board, pin: 'a pin') + board.should_receive(:digital_listen).with(7) + + Button.new(board: board, pin: 7) end end context 'callbacks' do - let(:board) { mock(:board, add_digital_hardware: true, start_read: true) } + let(:board) { mock(:board, add_input_hardware: true, start_read: true, digital_listen: true) } let(:button) {Button.new(board: board, pin: mock)} describe '#down' do it 'should add a callback to the down_callbacks array' do diff --git a/spec/lib/components/sensor_spec.rb b/spec/lib/components/sensor_spec.rb index 7767ab92..fb36fe1e 100644 --- a/spec/lib/components/sensor_spec.rb +++ b/spec/lib/components/sensor_spec.rb @@ -5,38 +5,66 @@ module Components describe Sensor do let(:board){mock(:board).as_null_object} + let(:sensor) {Sensor.new(board: board, pin: 7)} describe '#initalize' do it 'should raise if it does not receive a pin' do - expect { - Sensor.new(board: 'a board') - }.to raise_exception + expect { Sensor.new(board: board) }.to raise_exception end it 'should raise if it does not receive a board' do - expect { - Sensor.new(pin: 'a pin') - }.to raise_exception + expect { Sensor.new(pin: 7) }.to raise_exception end - it 'should add itself to the board and start reading' do - board.should_receive(:add_analog_hardware) + it 'should add itself to the board and start read' do + board.should_receive(:add_input_hardware) board.should_receive(:start_read) - Sensor.new(board: board, pin: 'a pin') + Sensor.new(board: board, pin: 7) end - it 'should initalize data_callbacks' do - sensor = Sensor.new(board: board, pin: 'a pin') - sensor.instance_variable_get(:@data_callbacks).should == [] + it 'should initalize callbacks' do + sensor.instance_variable_get(:@callbacks).should == {} end end - describe '#when_data_received' do - - it "should add a callback to the list of callbacks" do - sensor = Sensor.new(board: board, pin: 'a pin') - sensor.when_data_received { "this is a block" } - sensor.instance_variable_get(:@data_callbacks).should_not be_empty + describe '#read' do + it 'should tell the board to read once' do + board.should_receive(:analog_read).with(7) + sensor.read + end + + it 'should accept a callback as block and add to @callbacks[:read]' + end + + describe '#listen' do + it 'should tell the board to start listening' do + board.should_receive(:analog_listen).with(7) + sensor.listen + end + + it 'should accept a callback as block and add to @callbacks[:listen]' + end + + describe '#add_callback' do + it 'should require a key' + it 'should add the block to the array corresponding to the key in the @callbacks hash' + end + + describe '#clear_callbacks' do + it 'should clear all callbacks if called with no key' do + sensor.on_data { |data| puts data } + sensor.on_data(:test) { |data| puts data } + sensor.clear_callbacks + sensor.instance_variable_get(:@callbacks).should == {} + end + + it 'should clear callbacks of a particular key if called with a key' + end + + describe '#on_data' do + it "should add a callback with the :persistent key" do + sensor.on_data { "this is a block" } + sensor.instance_variable_get(:@callbacks)[:persistent].should_not be_empty end end @@ -46,10 +74,10 @@ module Components first_block_data = nil second_block_data = nil - sensor.when_data_received do |data| + sensor.on_data do |data| first_block_data = data end - sensor.when_data_received do |data| + sensor.on_data do |data| second_block_data = data end From bf9261ddf2b19c35648a99c4aaa25a53b268f77e Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Tue, 11 Jun 2013 01:22:23 -0400 Subject: [PATCH 024/296] Dino::Components::Core - better core functionality to inherit from --- lib/dino/components.rb | 2 +- lib/dino/components/base_component.rb | 44 ------------ lib/dino/components/button.rb | 50 +------------ lib/dino/components/core.rb | 10 +++ lib/dino/components/core/analog_input.rb | 21 ++++++ lib/dino/components/core/base.rb | 46 ++++++++++++ lib/dino/components/core/digital_input.rb | 41 +++++++++++ lib/dino/components/core/input.rb | 49 +++++++++++++ lib/dino/components/core/output.rb | 8 +++ lib/dino/components/ir_receiver.rb | 2 +- lib/dino/components/lcd.rb | 2 +- lib/dino/components/led.rb | 2 +- lib/dino/components/rgb_led.rb | 2 +- lib/dino/components/sensor.rb | 43 +----------- lib/dino/components/servo.rb | 2 +- lib/dino/components/shift_register.rb | 2 +- lib/dino/components/ssd.rb | 2 +- lib/dino/components/stepper.rb | 2 +- spec/lib/components/base_component_spec.rb | 66 ----------------- spec/lib/components/button_spec.rb | 82 ++++++---------------- spec/lib/components/core/base_spec.rb | 65 +++++++++++++++++ 21 files changed, 273 insertions(+), 270 deletions(-) delete mode 100644 lib/dino/components/base_component.rb create mode 100644 lib/dino/components/core.rb create mode 100644 lib/dino/components/core/analog_input.rb create mode 100644 lib/dino/components/core/base.rb create mode 100644 lib/dino/components/core/digital_input.rb create mode 100644 lib/dino/components/core/input.rb create mode 100644 lib/dino/components/core/output.rb delete mode 100644 spec/lib/components/base_component_spec.rb create mode 100644 spec/lib/components/core/base_spec.rb diff --git a/lib/dino/components.rb b/lib/dino/components.rb index 8ddd6d32..7c06c3e7 100644 --- a/lib/dino/components.rb +++ b/lib/dino/components.rb @@ -1,6 +1,6 @@ module Dino module Components - require 'dino/components/base_component' + require 'dino/components/core' autoload :Led, 'dino/components/led' autoload :Button, 'dino/components/button' autoload :Sensor, 'dino/components/sensor' diff --git a/lib/dino/components/base_component.rb b/lib/dino/components/base_component.rb deleted file mode 100644 index 36236c3c..00000000 --- a/lib/dino/components/base_component.rb +++ /dev/null @@ -1,44 +0,0 @@ -module Dino - module Components - class BaseComponent - attr_reader :board, :pin, :pullup - alias :pins :pin - - def initialize(options={}) - self.board = options[:board] - self.pin = options[:pin] || options[:pins] - self.pullup = options[:pullup] - - raise 'board and pin or pins are required for a component' if self.board.nil? || self.pin.nil? - after_initialize(options) - end - - # - # As BaseComponent does a lot of work for you with regarding to setting up, it is - # best not to override #initialize and instead define an #after_initialize method - # within your subclass. - # - # @note This method should be implemented in the BaseComponent subclass. - # - def after_initialize(options={}) ; end - - protected - - attr_writer :board, :pin, :pullup - alias :pins= :pin= - - def digital_write(pin=self.pin, value) - self.board.digital_write(pin, value) - end - - def analog_write(pin=self.pin, value) - self.board.analog_write(pin, value) - end - - def set_pin_mode(pin=self.pin, mode) - self.board.set_pin_mode(pin, mode, pullup) - end - end - end -end - diff --git a/lib/dino/components/button.rb b/lib/dino/components/button.rb index 9df42225..90b22cae 100644 --- a/lib/dino/components/button.rb +++ b/lib/dino/components/button.rb @@ -1,52 +1,8 @@ module Dino module Components - class Button < BaseComponent - UP = "01" - DOWN = "00" - - def after_initialize(options={}) - @down_callbacks, @up_callbacks, @state = [], [], UP - - board.add_input_hardware(self) - board.start_read - board.digital_listen(self.pin) - end - - def down(&callback) - @down_callbacks << callback - end - - def up(&callback) - @up_callbacks << callback - end - - def update(data) - return if data == @state - @state = data - - case data - when UP - button_up - when DOWN - button_down - else - return - end - end - - private - - def button_up - @up_callbacks.each do |callback| - callback.call - end - end - - def button_down - @down_callbacks.each do |callback| - callback.call - end - end + class Button < Core::DigitalInput + alias :down :on_low + alias :up :on_high end end end diff --git a/lib/dino/components/core.rb b/lib/dino/components/core.rb new file mode 100644 index 00000000..aa0f11e4 --- /dev/null +++ b/lib/dino/components/core.rb @@ -0,0 +1,10 @@ +module Dino + module Components + module Core + require 'dino/components/core/base' + require 'dino/components/core/input' + require 'dino/components/core/analog_input' + require 'dino/components/core/digital_input' + end + end +end \ No newline at end of file diff --git a/lib/dino/components/core/analog_input.rb b/lib/dino/components/core/analog_input.rb new file mode 100644 index 00000000..b77b2b88 --- /dev/null +++ b/lib/dino/components/core/analog_input.rb @@ -0,0 +1,21 @@ +module Dino + module Components + module Core + class AnalogInput < Input + def read(&block) + super &block + board.analog_read(pin) + end + + def listen(&block) + super &block + board.analog_listen(pin) + end + end + + def update(data) + super(data.to_i) + end + end + end +end diff --git a/lib/dino/components/core/base.rb b/lib/dino/components/core/base.rb new file mode 100644 index 00000000..f4f8910f --- /dev/null +++ b/lib/dino/components/core/base.rb @@ -0,0 +1,46 @@ +module Dino + module Components + module Core + class Base + attr_reader :board, :pin, :pullup + alias :pins :pin + + def initialize(options={}) + self.board = options[:board] + self.pin = options[:pin] || options[:pins] + self.pullup = options[:pullup] + + raise 'board and pin or pins are required for a component' if self.board.nil? || self.pin.nil? + after_initialize(options) + end + + # + # As BaseComponent does a lot of work for you with regarding to setting up, it is + # best not to override #initialize and instead define an #after_initialize method + # within your subclass. + # + # @note This method should be implemented in the BaseComponent subclass. + # + def after_initialize(options={}) ; end + + protected + + attr_writer :board, :pin, :pullup + alias :pins= :pin= + + def digital_write(pin=self.pin, value) + self.board.digital_write(pin, value) + end + + def analog_write(pin=self.pin, value) + self.board.analog_write(pin, value) + end + + def set_pin_mode(pin=self.pin, mode) + self.board.set_pin_mode(pin, mode, pullup) + end + end + end + end +end + diff --git a/lib/dino/components/core/digital_input.rb b/lib/dino/components/core/digital_input.rb new file mode 100644 index 00000000..6fa16913 --- /dev/null +++ b/lib/dino/components/core/digital_input.rb @@ -0,0 +1,41 @@ +module Dino + module Components + module Core + class DigitalInput < Input + HIGH = 1 + LOW = 0 + + def initialize(options={}) + super options + listen + end + + def read(&block) + super &block + board.digital_read(pin) + end + + def listen(&block) + super &block + board.digital_listen(pin) + end + + def on_high(&block) + add_callback(:high) do |data| + block.call(data) if data == HIGH + end + end + + def on_low(&block) + add_callback(:low) do |data| + block.call(data) if data == LOW + end + end + + def update(data) + super(@state = data.to_i) + end + end + end + end +end diff --git a/lib/dino/components/core/input.rb b/lib/dino/components/core/input.rb new file mode 100644 index 00000000..09aec8be --- /dev/null +++ b/lib/dino/components/core/input.rb @@ -0,0 +1,49 @@ +module Dino + module Components + module Core + class Input < Base + def initialize(options={}) + super options + + clear_callbacks + board.add_input_hardware(self) + board.start_read + + after_initialize(options) + end + + def read(&block) + add_callback(:read, &block) if block_given? + end + + def listen(&block) + add_callback(:listen, &block) if block_given? + end + + def stop_listening + board.stop_listener(pin) + clear_callbacks[:listen] + end + + def add_callback(key=nil, &block) + key ||= :persistent + @callbacks[key] ||= [] + @callbacks[key] << block + end + + alias :on_data :add_callback + + def clear_callbacks(key=nil) + key ? @callbacks[key] = nil : @callbacks = {} + end + + def update(data) + @callbacks.each_value do |array| + array.each { |callback| callback.call(data) } + end + @callbacks[:read] = [] + end + end + end + end +end diff --git a/lib/dino/components/core/output.rb b/lib/dino/components/core/output.rb new file mode 100644 index 00000000..50717f89 --- /dev/null +++ b/lib/dino/components/core/output.rb @@ -0,0 +1,8 @@ +module Dino + module Components + module Core + class Output < Base + end + end + end +end diff --git a/lib/dino/components/ir_receiver.rb b/lib/dino/components/ir_receiver.rb index f72ad11e..bd545ad4 100644 --- a/lib/dino/components/ir_receiver.rb +++ b/lib/dino/components/ir_receiver.rb @@ -1,6 +1,6 @@ module Dino module Components - class IrReceiver < BaseComponent + class IrReceiver < Core::Base STABLE = "01" def after_initialize(options={}) diff --git a/lib/dino/components/lcd.rb b/lib/dino/components/lcd.rb index b7cba3c7..eda03940 100644 --- a/lib/dino/components/lcd.rb +++ b/lib/dino/components/lcd.rb @@ -1,6 +1,6 @@ module Dino module Components - class LCD < BaseComponent + class LCD < Core::Base # Initialize in 4 bits mode # diff --git a/lib/dino/components/led.rb b/lib/dino/components/led.rb index f813b12d..343f8b03 100644 --- a/lib/dino/components/led.rb +++ b/lib/dino/components/led.rb @@ -1,6 +1,6 @@ module Dino module Components - class Led < BaseComponent + class Led < Core::Base def after_initialize(options={}) set_pin_mode(:out) off diff --git a/lib/dino/components/rgb_led.rb b/lib/dino/components/rgb_led.rb index 1ba5c794..26c5f65e 100644 --- a/lib/dino/components/rgb_led.rb +++ b/lib/dino/components/rgb_led.rb @@ -1,6 +1,6 @@ module Dino module Components - class RgbLed < BaseComponent + class RgbLed < Core::Base # options = {board: my_board, pins: {red: red_pin, green: green_pin, blue: blue_pin} def after_initialize(options={}) raise 'missing pins[:red] pin' unless self.pins[:red] diff --git a/lib/dino/components/sensor.rb b/lib/dino/components/sensor.rb index 7dffc0ac..ec96d532 100644 --- a/lib/dino/components/sensor.rb +++ b/lib/dino/components/sensor.rb @@ -1,47 +1,6 @@ module Dino module Components - class Sensor < BaseComponent - def after_initialize(options={}) - clear_callbacks - board.add_input_hardware(self) - board.start_read - end - - def read(&block) - add_callback(:read, &block) if block_given? - board.analog_read(pin) - end - - def listen(&block) - add_callback(:listen, &block) if block_given? - board.analog_listen(pin) - end - - def stop_listening - board.stop_listener(pin) - clear_callbacks[:listen] - end - - def add_callback(key=nil, &block) - key ||= :persistent - @callbacks[key] ||= [] - @callbacks[key] << block - end - - alias :on_data :add_callback - - def clear_callbacks(key=nil) - key ? @callbacks[key] = nil : @callbacks = {} - end - - def update(data) - @callbacks.each_value do |array| - array.each do |callback| - callback.call(data) - end - end - @callbacks[:read] = [] - end + class Sensor < Core::AnalogInput end end end diff --git a/lib/dino/components/servo.rb b/lib/dino/components/servo.rb index 2a85dd3a..70dea259 100644 --- a/lib/dino/components/servo.rb +++ b/lib/dino/components/servo.rb @@ -1,6 +1,6 @@ module Dino module Components - class Servo < BaseComponent + class Servo < Core::Base attr_reader :position def after_initialize(options={}) diff --git a/lib/dino/components/shift_register.rb b/lib/dino/components/shift_register.rb index dddacbf2..dc83a2ca 100644 --- a/lib/dino/components/shift_register.rb +++ b/lib/dino/components/shift_register.rb @@ -1,6 +1,6 @@ module Dino module Components - class ShiftRegister < BaseComponent + class ShiftRegister < Core::Base # options = {board: my_board, pins: {latch: latch_pin, clock: clock_pin, data: data_pin} def after_initialize(options={}) raise 'missing pins[:latch] pin' unless self.pins[:latch] diff --git a/lib/dino/components/ssd.rb b/lib/dino/components/ssd.rb index 448a19f1..5231ab7a 100644 --- a/lib/dino/components/ssd.rb +++ b/lib/dino/components/ssd.rb @@ -9,7 +9,7 @@ module Dino module Components - class SSD < BaseComponent + class SSD < Core::Base attr_reader :anode def after_initialize(options={}) diff --git a/lib/dino/components/stepper.rb b/lib/dino/components/stepper.rb index 57f3607c..d292fa35 100644 --- a/lib/dino/components/stepper.rb +++ b/lib/dino/components/stepper.rb @@ -1,6 +1,6 @@ module Dino module Components - class Stepper < BaseComponent + class Stepper < Core::Base def after_initialize(options={}) raise 'missing pins[:step] pin' unless self.pins[:step] diff --git a/spec/lib/components/base_component_spec.rb b/spec/lib/components/base_component_spec.rb deleted file mode 100644 index 689474e9..00000000 --- a/spec/lib/components/base_component_spec.rb +++ /dev/null @@ -1,66 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - - describe BaseComponent do - - it 'should initialize with board and pin' do - pin = "a pin" - board = "a board" - component = BaseComponent.new(pin: pin, board: board) - - component.pin.should == pin - component.board.should == board - end - - it 'should assign pins' do - pins = {red: 'red', green: 'green', blue: 'blue'} - board = "a board" - component = BaseComponent.new(pins: pins, board: board) - - component.pins.should == pins - end - - it 'should require a pin or pins' do - expect { - BaseComponent.new(board: 'some board') - }.to raise_exception - end - - it 'should require a board' do - expect { - BaseComponent.new(pin: 'some pin') - }.to raise_exception - end - - context "when subclassed #after_initialize should be executed" do - - class SpecComponent < BaseComponent - - def sucessfully_initialized? ; @success ; end - - def options ; @options ; end - - def after_initialize(options={}) - @success = true - @options = options - end - end - - let(:options) { { pin: pin, board: board } } - let(:pin) { "a pin" } - let(:board) { "a board" } - - it "should call #after_initialize with options" do - component = SpecComponent.new(options) - component.should be_sucessfully_initialized - component.options.should eq options - end - - end - - end - end -end - diff --git a/spec/lib/components/button_spec.rb b/spec/lib/components/button_spec.rb index 5eedfa05..65e7c9e7 100644 --- a/spec/lib/components/button_spec.rb +++ b/spec/lib/components/button_spec.rb @@ -30,90 +30,48 @@ module Components let(:board) { mock(:board, add_input_hardware: true, start_read: true, digital_listen: true) } let(:button) {Button.new(board: board, pin: mock)} describe '#down' do - it 'should add a callback to the down_callbacks array' do + it 'should add a callback to @callbacks[:low]' do callback = mock button.down do callback.called end - down_callbacks = button.instance_variable_get(:@down_callbacks) + down_callbacks = button.instance_variable_get(:@callbacks)[:low] down_callbacks.size.should == 1 - callback.should_receive(:called) - down_callbacks.first.call end end describe '#up' do - it 'should add a callback to the up_callbacks array' do + it 'should add a callback to @callbacks[:high]' do callback = mock button.up do callback.called end - up_callbacks = button.instance_variable_get(:@up_callbacks) + up_callbacks = button.instance_variable_get(:@callbacks)[:high] up_callbacks.size.should == 1 - callback.should_receive(:called) - up_callbacks.first.call end end describe '#update' do - it 'should call the down callbacks' do - callback_1 = mock - button.down do - callback_1.called - end + it 'should call @callbacks[:high] only when HIGH' do + high_callback = mock + low_callback = mock + button.up { high_callback.called } + button.down { low_callback.calles } - callback_2 = mock - button.down do - callback_2.called - end - callback_1.should_receive(:called) - callback_2.should_receive(:called) - button.update(Button::DOWN) + high_callback.should_receive(:called) + low_callback.should_not_receive(:called) + button.update(1) end - it 'should call the up callbacks' do - callback_1 = mock - button.up do - callback_1.called - end + it 'should call @callbacks[:low] only when LOW' do + high_callback = mock + low_callback = mock + button.up { high_callback.called } + button.down { low_callback.called } - callback_2 = mock - button.up do - callback_2.called - end - - button.instance_variable_set(:@state, Button::DOWN) - - callback_1.should_receive(:called) - callback_2.should_receive(:called) - button.update(Button::UP) - end - - it 'should not call the callbacks if the state has not changed' do - callback = mock - button.up do - callback.called - end - - callback.should_not_receive(:called) - button.update(Button::UP) - button.update(Button::UP) - end - - it 'should not call the callbacks if the data is not UP or DOWN' do - callback_1 = mock - button.up do - callback_1.called - end - - callback_2 = mock - button.down do - callback_2.called - end - - callback_1.should_not_receive(:called) - callback_2.should_not_receive(:called) - button.update('foobarred') + high_callback.should_not_receive(:called) + low_callback.should_receive(:called) + button.update(0) end end end diff --git a/spec/lib/components/core/base_spec.rb b/spec/lib/components/core/base_spec.rb new file mode 100644 index 00000000..12184c13 --- /dev/null +++ b/spec/lib/components/core/base_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' + +module Dino + module Components + module Core + describe Base do + + it 'should initialize with board and pin' do + pin = "a pin" + board = "a board" + component = Base.new(pin: pin, board: board) + + component.pin.should == pin + component.board.should == board + end + + it 'should assign pins' do + pins = {red: 'red', green: 'green', blue: 'blue'} + board = "a board" + component = Base.new(pins: pins, board: board) + + component.pins.should == pins + end + + it 'should require a pin or pins' do + expect { + Base.new(board: 'some board') + }.to raise_exception + end + + it 'should require a board' do + expect { + Base.new(pin: 'some pin') + }.to raise_exception + end + + context "when subclassed #after_initialize should be executed" do + + class SpecComponent < Base + + def sucessfully_initialized? ; @success ; end + + def options ; @options ; end + + def after_initialize(options={}) + @success = true + @options = options + end + end + + let(:options) { { pin: pin, board: board } } + let(:pin) { "a pin" } + let(:board) { "a board" } + + it "should call #after_initialize with options" do + component = SpecComponent.new(options) + component.should be_sucessfully_initialized + component.options.should eq options + end + end + end + end + end +end + From ae5cc5564f37ec83dd4c584740d8276d40de7746 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Tue, 11 Jun 2013 01:52:14 -0400 Subject: [PATCH 025/296] Refactor LCD and IR receiver --- lib/dino/components/ir_receiver.rb | 27 ++-------- lib/dino/components/lcd.rb | 79 +++++++++--------------------- 2 files changed, 27 insertions(+), 79 deletions(-) diff --git a/lib/dino/components/ir_receiver.rb b/lib/dino/components/ir_receiver.rb index bd545ad4..ccbd10a2 100644 --- a/lib/dino/components/ir_receiver.rb +++ b/lib/dino/components/ir_receiver.rb @@ -1,30 +1,11 @@ module Dino module Components - class IrReceiver < Core::Base - STABLE = "01" - - def after_initialize(options={}) - @flash_callbacks = [] - - self.board.add_digital_hardware(self) - self.board.start_read - end - - def flash(callback) - @flash_callbacks << callback - end + class IrReceiver < Core::DigitalInput + alias :flash :on_low def update(data) - return if data == STABLE - light_flashed - end - - private - - def light_flashed - @flash_callbacks.each do |callback| - callback.call - end + return if data.to_i == HIGH + super data end end end diff --git a/lib/dino/components/lcd.rb b/lib/dino/components/lcd.rb index eda03940..eda2f15b 100644 --- a/lib/dino/components/lcd.rb +++ b/lib/dino/components/lcd.rb @@ -26,68 +26,35 @@ def after_initialize(options) board.write Dino::Message.encode command: 10, value: 1, aux_message: "#{@cols},#{@rows}" end - def puts(string) - board.write Dino::Message.encode command: 10, value: 5, aux_message: string - end - - def clear - board.write Dino::Message.encode command: 10, value: 2 - end - - def home - board.write Dino::Message.encode command: 10, value: 3 + LIBRARY_COMMANDS = { + clear: '2', + home: '3', + show_cursor: '6', + hide_cursor: '7', + blink: '8', + no_blink: '9', + on: '10', + off: '11', + scroll_left: '12', + scroll_right: '13', + enable_autoscroll: '14', + disable_autoscroll:'15', + left_to_right: '16', + right_to_left: '17' + } + + LIBRARY_COMMANDS.each_pair do |command, command_id| + define_method(command) do + board.write Dino::Message.encode(command: 10, value: command_id) + end end def set_cursor(col, row) board.write Dino::Message.encode command: 10, value: 4, aux_message: "#{col},#{row}" end - def show_cursor - board.write Dino::Message.encode command: 10, value: 6 - end - - def hide_cursor - board.write Dino::Message.encode command: 10, value: 7 - end - - def blink - board.write Dino::Message.encode command: 10, value: 8 - end - - def no_blink - board.write Dino::Message.encode command: 10, value: 9 - end - - def on - board.write Dino::Message.encode command: 10, value: 10 - end - - def off - board.write Dino::Message.encode command: 10, value: 11 - end - - def scroll_left - board.write Dino::Message.encode command: 10, value: 12 - end - - def scroll_right - board.write Dino::Message.encode command: 10, value: 13 - end - - def enable_autoscroll - board.write Dino::Message.encode command: 10, value: 14 - end - - def disable_autoscroll - board.write Dino::Message.encode command: 10, value: 15 - end - - def left_to_right - board.write Dino::Message.encode command: 10, value: 16 - end - - def right_to_left - board.write Dino::Message.encode command: 10, value: 17 + def puts(string) + board.write Dino::Message.encode command: 10, value: 5, aux_message: string end private From c76feeaf80e807b8bd9a93de4c4c2ca5e0acd86e Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Wed, 12 Jun 2013 10:14:38 -0400 Subject: [PATCH 026/296] Refactor logic into Components::Core. Add Components::Core::MultiPin. Create complex components by using basic (one pin) components as proxies. --- examples/rgb_led/rgb_led.rb | 6 +- lib/dino/components/core.rb | 4 +- lib/dino/components/core/analog_input.rb | 16 +- lib/dino/components/core/base.rb | 39 +++-- .../core/{input.rb => base_input.rb} | 38 +++-- lib/dino/components/core/base_output.rb | 43 ++++++ lib/dino/components/core/digital_input.rb | 24 ++- lib/dino/components/core/multi_pin.rb | 65 ++++++++ lib/dino/components/core/output.rb | 8 - lib/dino/components/lcd.rb | 2 +- lib/dino/components/led.rb | 16 +- lib/dino/components/rgb_led.rb | 36 ++--- lib/dino/components/servo.rb | 13 +- lib/dino/components/shift_register.rb | 33 ++--- lib/dino/components/ssd.rb | 22 ++- lib/dino/components/stepper.rb | 23 ++- spec/lib/components/button_spec.rb | 72 --------- spec/lib/components/core/analog_input_spec.rb | 28 ++++ spec/lib/components/core/base_input_spec.rb | 119 +++++++++++++++ spec/lib/components/core/base_output_spec.rb | 75 ++++++++++ spec/lib/components/core/base_spec.rb | 72 ++++----- .../lib/components/core/digital_input_spec.rb | 66 +++++++++ spec/lib/components/core/multi_pin_spec.rb | 78 ++++++++++ spec/lib/components/led_spec.rb | 42 ------ spec/lib/components/rgb_led_spec.rb | 139 ++++++------------ spec/lib/components/sensor_spec.rb | 82 ----------- spec/lib/components/servo_spec.rb | 34 ++--- spec/lib/components/shift_register_spec.rb | 46 ++---- spec/lib/components/ssd_spec.rb | 66 ++++----- spec/lib/components/stepper_spec.rb | 60 +++----- 30 files changed, 746 insertions(+), 621 deletions(-) rename lib/dino/components/core/{input.rb => base_input.rb} (56%) create mode 100644 lib/dino/components/core/base_output.rb create mode 100644 lib/dino/components/core/multi_pin.rb delete mode 100644 lib/dino/components/core/output.rb create mode 100644 spec/lib/components/core/analog_input_spec.rb create mode 100644 spec/lib/components/core/base_input_spec.rb create mode 100644 spec/lib/components/core/base_output_spec.rb create mode 100644 spec/lib/components/core/digital_input_spec.rb create mode 100644 spec/lib/components/core/multi_pin_spec.rb diff --git a/examples/rgb_led/rgb_led.rb b/examples/rgb_led/rgb_led.rb index 46c6693a..783ab52d 100644 --- a/examples/rgb_led/rgb_led.rb +++ b/examples/rgb_led/rgb_led.rb @@ -21,13 +21,13 @@ loop do puts "DELAY: #{seconds = (delay / 1000.0)}" p 'red' - led.red + led.color = :red sleep(seconds) - led.blue p 'blue' + led.color = :blue sleep(seconds) - led.green p 'green' + led.color = :green sleep(seconds) end #led.color(224, 27, 106) diff --git a/lib/dino/components/core.rb b/lib/dino/components/core.rb index aa0f11e4..2ef4d795 100644 --- a/lib/dino/components/core.rb +++ b/lib/dino/components/core.rb @@ -2,9 +2,11 @@ module Dino module Components module Core require 'dino/components/core/base' - require 'dino/components/core/input' + require 'dino/components/core/multi_pin' + require 'dino/components/core/base_input' require 'dino/components/core/analog_input' require 'dino/components/core/digital_input' + require 'dino/components/core/base_output' end end end \ No newline at end of file diff --git a/lib/dino/components/core/analog_input.rb b/lib/dino/components/core/analog_input.rb index b77b2b88..ba9b8f51 100644 --- a/lib/dino/components/core/analog_input.rb +++ b/lib/dino/components/core/analog_input.rb @@ -1,21 +1,15 @@ module Dino module Components module Core - class AnalogInput < Input - def read(&block) - super &block - board.analog_read(pin) + class AnalogInput < BaseInput + def poll + board.analog_read(self.pin) end - def listen(&block) - super &block - board.analog_listen(pin) + def start_listening + board.analog_listen(self.pin) end end - - def update(data) - super(data.to_i) - end end end end diff --git a/lib/dino/components/core/base.rb b/lib/dino/components/core/base.rb index f4f8910f..e9eec9fa 100644 --- a/lib/dino/components/core/base.rb +++ b/lib/dino/components/core/base.rb @@ -2,45 +2,40 @@ module Dino module Components module Core class Base - attr_reader :board, :pin, :pullup - alias :pins :pin + attr_reader :board, :pin, :mode, :pullup, :state def initialize(options={}) + raise 'board and pin are required for a component' if options[:board].nil? || options[:pin].nil? + self.board = options[:board] - self.pin = options[:pin] || options[:pins] - self.pullup = options[:pullup] + self.pin = board.convert_pin(options[:pin]) + @pullup = options[:pullup] - raise 'board and pin or pins are required for a component' if self.board.nil? || self.pin.nil? after_initialize(options) end + protected + + attr_writer :board, :pin + # - # As BaseComponent does a lot of work for you with regarding to setting up, it is - # best not to override #initialize and instead define an #after_initialize method - # within your subclass. + # Components::Core::Base does a lot of setup work for you. + # Define #after_initialize in your subclass instead of overriding #initialize # # @note This method should be implemented in the BaseComponent subclass. # def after_initialize(options={}) ; end - protected - - attr_writer :board, :pin, :pullup - alias :pins= :pin= - - def digital_write(pin=self.pin, value) - self.board.digital_write(pin, value) + def mode=(mode) + @mode = mode + board.set_pin_mode(self.pin, mode, pullup) end - def analog_write(pin=self.pin, value) - self.board.analog_write(pin, value) - end - - def set_pin_mode(pin=self.pin, mode) - self.board.set_pin_mode(pin, mode, pullup) + def pullup=(pullup) + @pullup = pullup + board.set_pullup(self.pin, pullup) end end end end end - diff --git a/lib/dino/components/core/input.rb b/lib/dino/components/core/base_input.rb similarity index 56% rename from lib/dino/components/core/input.rb rename to lib/dino/components/core/base_input.rb index 09aec8be..5d2dfe96 100644 --- a/lib/dino/components/core/input.rb +++ b/lib/dino/components/core/base_input.rb @@ -1,29 +1,37 @@ module Dino module Components module Core - class Input < Base + class BaseInput < Base + attr_reader :state + def initialize(options={}) super options - clear_callbacks + remove_callbacks + self.mode = :in board.add_input_hardware(self) board.start_read after_initialize(options) end - + def read(&block) add_callback(:read, &block) if block_given? + poll end - + def listen(&block) add_callback(:listen, &block) if block_given? + start_listening end - def stop_listening - board.stop_listener(pin) - clear_callbacks[:listen] - end + # + # Define these in your subclass. + # Should correspond to Board#digital_read, Board#digital_listen for digital + # and Board#analog_read, Board#analog_listen for analog components. + # + def poll ; end + def start_listening ; end def add_callback(key=nil, &block) key ||= :persistent @@ -33,15 +41,23 @@ def add_callback(key=nil, &block) alias :on_data :add_callback - def clear_callbacks(key=nil) + def remove_callback(key=nil) key ? @callbacks[key] = nil : @callbacks = {} end + alias :remove_callbacks :remove_callback + + def stop_listening + board.stop_listener(pin) + remove_callback :listen + end + def update(data) + @state = data @callbacks.each_value do |array| - array.each { |callback| callback.call(data) } + array.each { |callback| callback.call(@state) } end - @callbacks[:read] = [] + remove_callback :read end end end diff --git a/lib/dino/components/core/base_output.rb b/lib/dino/components/core/base_output.rb new file mode 100644 index 00000000..103f8977 --- /dev/null +++ b/lib/dino/components/core/base_output.rb @@ -0,0 +1,43 @@ +module Dino + module Components + module Core + class BaseOutput < Base + attr_reader :state + + def initialize(options={}) + super options + + self.mode = :out + low + end + + def digital_write(value) + board.digital_write(pin, @state = value) + end + + def analog_write(value) + board.analog_write(pin, @state = value) + end + + def write(value) + unless [Board::LOW, Board::HIGH].include? value + analog_write(value) + else + digital_write(value) + end + end + + def low + digital_write Board::LOW + end + + def high + digital_write Board::HIGH + end + + alias :off :low + alias :on :high + end + end + end +end diff --git a/lib/dino/components/core/digital_input.rb b/lib/dino/components/core/digital_input.rb index 6fa16913..74b72a83 100644 --- a/lib/dino/components/core/digital_input.rb +++ b/lib/dino/components/core/digital_input.rb @@ -1,40 +1,34 @@ module Dino module Components module Core - class DigitalInput < Input + class DigitalInput < BaseInput HIGH = 1 LOW = 0 def initialize(options={}) - super options - listen + super(options) + start_listening end - def read(&block) - super &block - board.digital_read(pin) + def poll + board.digital_read(self.pin) end - def listen(&block) - super &block - board.digital_listen(pin) + def start_listening + board.digital_listen(self.pin) end def on_high(&block) add_callback(:high) do |data| - block.call(data) if data == HIGH + block.call(data) if data.to_i == HIGH end end def on_low(&block) add_callback(:low) do |data| - block.call(data) if data == LOW + block.call(data) if data.to_i == LOW end end - - def update(data) - super(@state = data.to_i) - end end end end diff --git a/lib/dino/components/core/multi_pin.rb b/lib/dino/components/core/multi_pin.rb new file mode 100644 index 00000000..23294583 --- /dev/null +++ b/lib/dino/components/core/multi_pin.rb @@ -0,0 +1,65 @@ +module Dino + module Components + module Core + class MultiPin + attr_reader :board, :pins, :pullups + + def initialize(options={}) + raise 'board and pins are required for a component' if options[:board].nil? || options[:pins].nil? + + self.board = options[:board] + self.pins = options[:pins] + self.pullups = options[:pullups] || {} + + after_initialize(options) + end + + def state + state = {} + pins.each_key do |pin| + state[pin] = self.send(pin).state + end + state + end + + protected + + attr_writer :board, :pins, :pullups + + # + # @note This method should be implemented in your subclass. + # + def after_initialize(options={}) ; end + + # + # Build complex components by proxying each pin to a basic, single pin component. + # + # Call #proxy with a hash like: + # class MyComponent < MultiPin + # proxy {lamp: Core::BaseOutput, motor: Core::BaseOutput} + # end + # + # Checks the @pins hash for the pins corresponding to the keys like: + # pins => {lamp: 9, motor: 10} - No error + # pins => {lamp: 9} - Error raised + # + # It instantiates a new instance of the class passed in for each key in an instance variable. + # Sets up an attr_reader on the singleton class, so the proxy component can be accessed like: + # my_component.lamp.on; my_component.motor.analog_write(128) + # + def proxy(proxies={}) + proxies.each_pair do |key, klass| + raise "missing pins[:#{key}] pin" unless pins[key] + + proxy_component = klass.new(board: board, pin: pins[key], pullup: pullups[key]) + instance_variable_set("@#{key}", proxy_component) + + singleton_class.class_eval { attr_reader key } + end + end + + alias :proxies :proxy + end + end + end +end diff --git a/lib/dino/components/core/output.rb b/lib/dino/components/core/output.rb deleted file mode 100644 index 50717f89..00000000 --- a/lib/dino/components/core/output.rb +++ /dev/null @@ -1,8 +0,0 @@ -module Dino - module Components - module Core - class Output < Base - end - end - end -end diff --git a/lib/dino/components/lcd.rb b/lib/dino/components/lcd.rb index eda2f15b..f46dc4dc 100644 --- a/lib/dino/components/lcd.rb +++ b/lib/dino/components/lcd.rb @@ -1,6 +1,6 @@ module Dino module Components - class LCD < Core::Base + class LCD < Core::MultiPin # Initialize in 4 bits mode # diff --git a/lib/dino/components/led.rb b/lib/dino/components/led.rb index 343f8b03..302d103d 100644 --- a/lib/dino/components/led.rb +++ b/lib/dino/components/led.rb @@ -1,18 +1,8 @@ module Dino module Components - class Led < Core::Base - def after_initialize(options={}) - set_pin_mode(:out) - off - end - - def on - digital_write(Board::HIGH) - end - - def off - digital_write(Board::LOW) - end + class Led < Core::BaseOutput + alias :on :high + alias :off :low end end end diff --git a/lib/dino/components/rgb_led.rb b/lib/dino/components/rgb_led.rb index 26c5f65e..d91ddacd 100644 --- a/lib/dino/components/rgb_led.rb +++ b/lib/dino/components/rgb_led.rb @@ -1,16 +1,13 @@ module Dino module Components - class RgbLed < Core::Base + class RgbLed < Core::MultiPin + # # options = {board: my_board, pins: {red: red_pin, green: green_pin, blue: blue_pin} + # def after_initialize(options={}) - raise 'missing pins[:red] pin' unless self.pins[:red] - raise 'missing pins[:green] pin' unless self.pins[:green] - raise 'missing pins[:blue] pin' unless self.pins[:blue] - - pins.each do |color, pin| - set_pin_mode(pin, :out) - analog_write(pin, Board::LOW) - end + proxies red: Core::BaseOutput, + green: Core::BaseOutput, + blue: Core::BaseOutput end # Format: [R, G, B] @@ -25,17 +22,22 @@ def after_initialize(options={}) off: [000, 000, 000] } - COLORS.each_key do |color| - define_method(color) do - analog_write(pins[:red], COLORS[color][0]) - analog_write(pins[:green], COLORS[color][1]) - analog_write(pins[:blue], COLORS[color][2]) - end + def write(array) + red.write array[0] + green.write array[1] + blue.write array[2] + end + + def color=(color) + return write(color) if color.class == Array + + color = color.to_sym + write(COLORS[color]) if COLORS.include? color end - def blinky + def cycle [:red, :green, :blue].cycle do |color| - self.send(color) + self.color = color sleep(0.01) end end diff --git a/lib/dino/components/servo.rb b/lib/dino/components/servo.rb index 70dea259..e0461d71 100644 --- a/lib/dino/components/servo.rb +++ b/lib/dino/components/servo.rb @@ -1,16 +1,17 @@ module Dino module Components - class Servo < Core::Base - attr_reader :position - + class Servo < Core::BaseOutput def after_initialize(options={}) - set_pin_mode(:out) board.servo_toggle(pin, 1) - self.position = options[:position] || 0 end def position=(value) - board.servo_write(pin, @position = angle(value)) + @state = angle(value) + board.servo_write(pin, @state) + end + + def position + @state end def angle(value) diff --git a/lib/dino/components/shift_register.rb b/lib/dino/components/shift_register.rb index dc83a2ca..11418c69 100644 --- a/lib/dino/components/shift_register.rb +++ b/lib/dino/components/shift_register.rb @@ -1,36 +1,23 @@ module Dino module Components - class ShiftRegister < Core::Base - # options = {board: my_board, pins: {latch: latch_pin, clock: clock_pin, data: data_pin} + class ShiftRegister < Core::MultiPin + # + # options = {board: my_board, pins: {clock: clock_pin, latch: latch_pin, data: data_pin} + # def after_initialize(options={}) - raise 'missing pins[:latch] pin' unless self.pins[:latch] - raise 'missing pins[:clock] pin' unless self.pins[:clock] - raise 'missing pins[:data] pin' unless self.pins[:data] - - pins.each_value do |pin| - set_pin_mode(pin, :out) - digital_write(pin, Board::LOW) - end - end - - def latch_off - digital_write(pins[:latch], Board::LOW) - end - - def latch_on - digital_write(pins[:latch], Board::HIGH) + proxy clock: Core::BaseOutput, + latch: Core::BaseOutput, + data: Core::BaseOutput end def write(bytes) bytes = [bytes] unless bytes.class == Array - data_pin = board.convert_pin(pins[:data]) - clock_pin = board.convert_pin(pins[:clock]) - latch_off + latch.low bytes.each do |byte| - board.write Dino::Message.encode( command: 11, pin: data_pin, value: byte, aux_message: clock_pin) + board.write Dino::Message.encode(command: 11, pin: data.pin, value: byte, aux_message: clock.pin) end - latch_on + latch.high end end end diff --git a/lib/dino/components/ssd.rb b/lib/dino/components/ssd.rb index 5231ab7a..edae8f79 100644 --- a/lib/dino/components/ssd.rb +++ b/lib/dino/components/ssd.rb @@ -9,26 +9,22 @@ module Dino module Components - class SSD < Core::Base + class SSD < Core::MultiPin attr_reader :anode def after_initialize(options={}) - @anode = options[:anode] + raise Exception.new('anode must be specified') unless options[:anode] + @anode = Core::BaseOutput.new(board: board, pin: options[:anode]) - raise Exception.new('anode must be specified') unless anode + # Create a Core::BaseOutput for every pin. + @pins.map! { |pin| Core::BaseOutput.new(board: board, pin: pin) } - # Set all pins to output - pins.each { |pin| set_pin_mode(pin, :out) } - - # Clear the display clear - - # Turn on the display on end def clear - 7.times { |t| toggle t, 0 } + 7.times { |pin| toggle pin, 0 } end def display(char) @@ -47,11 +43,11 @@ def display(char) end def on - digital_write anode, Board::HIGH + @anode.high end def off - digital_write anode, Board::LOW + @anode.low end CHARACTERS = { @@ -110,7 +106,7 @@ def scroll(string) end def toggle(number, state) - digital_write pins[number], state == 1 ? 0 : 1 + @pins[number].write (state == 1 ? 0 : 1) end end end diff --git a/lib/dino/components/stepper.rb b/lib/dino/components/stepper.rb index d292fa35..165d1ef1 100644 --- a/lib/dino/components/stepper.rb +++ b/lib/dino/components/stepper.rb @@ -1,26 +1,21 @@ module Dino module Components - class Stepper < Core::Base - + class Stepper < Core::MultiPin def after_initialize(options={}) - raise 'missing pins[:step] pin' unless self.pins[:step] - raise 'missing pins[:direction] pin' unless self.pins[:direction] - - set_pin_mode(pins[:step], :out) - set_pin_mode(pins[:direction], :out) - digital_write(pins[:step], Board::LOW) + proxies step: Core::BaseOutput, + direction: Core::BaseOutput end def step_cc - digital_write(self.pins[:direction], Board::HIGH) - digital_write(self.pins[:step], Board::HIGH) - digital_write(self.pins[:step], Board::LOW) + direction.high + step.high + step.low end def step_cw - digital_write(self.pins[:direction], Board::LOW) - digital_write(self.pins[:step], Board::HIGH) - digital_write(self.pins[:step], Board::LOW) + direction.low + step.high + step.low end end end diff --git a/spec/lib/components/button_spec.rb b/spec/lib/components/button_spec.rb index 65e7c9e7..d9cc1e48 100644 --- a/spec/lib/components/button_spec.rb +++ b/spec/lib/components/button_spec.rb @@ -3,78 +3,6 @@ module Dino module Components describe Button do - describe '#initialize' do - it 'should raise if it does not receive a pin' do - expect { - Button.new(board: 'a board') - }.to raise_exception - end - - it 'should raise if it does not receive a board' do - expect { - Button.new(pin: 'a pin') - }.to raise_exception - end - - it 'should add itself to the board, start read and digital listen' do - board = mock(:board) - board.should_receive(:add_input_hardware) - board.should_receive(:start_read) - board.should_receive(:digital_listen).with(7) - - Button.new(board: board, pin: 7) - end - end - - context 'callbacks' do - let(:board) { mock(:board, add_input_hardware: true, start_read: true, digital_listen: true) } - let(:button) {Button.new(board: board, pin: mock)} - describe '#down' do - it 'should add a callback to @callbacks[:low]' do - callback = mock - button.down do - callback.called - end - down_callbacks = button.instance_variable_get(:@callbacks)[:low] - down_callbacks.size.should == 1 - end - end - - describe '#up' do - it 'should add a callback to @callbacks[:high]' do - callback = mock - button.up do - callback.called - end - up_callbacks = button.instance_variable_get(:@callbacks)[:high] - up_callbacks.size.should == 1 - end - end - - describe '#update' do - it 'should call @callbacks[:high] only when HIGH' do - high_callback = mock - low_callback = mock - button.up { high_callback.called } - button.down { low_callback.calles } - - high_callback.should_receive(:called) - low_callback.should_not_receive(:called) - button.update(1) - end - - it 'should call @callbacks[:low] only when LOW' do - high_callback = mock - low_callback = mock - button.up { high_callback.called } - button.down { low_callback.called } - - high_callback.should_not_receive(:called) - low_callback.should_receive(:called) - button.update(0) - end - end - end end end end diff --git a/spec/lib/components/core/analog_input_spec.rb b/spec/lib/components/core/analog_input_spec.rb new file mode 100644 index 00000000..6be3c687 --- /dev/null +++ b/spec/lib/components/core/analog_input_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +module Dino + module Components + module Core + describe AnalogInput do + let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } + let(:board) { Board.new(txrx) } + let(:options) { { pin: 'A0', board: board } } + subject { AnalogInput.new(options) } + + describe '#poll' do + it 'should send #analog_read to the board with its pin' do + board.should_receive(:analog_read).with(subject.pin) + subject.read + end + end + + describe '#start_listening' do + it 'should send #analog_listen to the board with its pin' do + board.should_receive(:analog_listen).with(subject.pin) + subject.start_listening + end + end + end + end + end +end diff --git a/spec/lib/components/core/base_input_spec.rb b/spec/lib/components/core/base_input_spec.rb new file mode 100644 index 00000000..b6f75c7b --- /dev/null +++ b/spec/lib/components/core/base_input_spec.rb @@ -0,0 +1,119 @@ +require 'spec_helper' + +module Dino + module Components + module Core + describe BaseInput do + let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } + let(:board) { Board.new(txrx) } + let(:options) { { pin: 'A0', board: board } } + subject { BaseInput.new(options) } + + describe '#initialize' do + it 'should add itself as input hardware, set mode to in and start read' do + board.should_receive(:set_pin_mode).with(14, :in, nil) + board.should_receive(:add_input_hardware) + board.should_receive(:start_read) + + BaseInput.new(options) + end + end + + context 'callbacks' do + describe '#add_callback' do + it 'should add a callback to the :persistent key if no key is given' do + subject.add_callback { mock } + subject.instance_variable_get(:@callbacks)[:persistent].should_not be_empty + end + + it 'should add a callback to the key if a key is given' do + subject.add_callback(:test) { mock } + subject.instance_variable_get(:@callbacks)[:test].should_not be_empty + end + end + + describe '#remove_callback' do + it 'should remove all callbacks if no key is given' do + subject.add_callback { mock } + subject.remove_callback + + subject.instance_variable_get(:@callbacks).should == {} + end + + it 'should remove callbacks for the given key if key is given' do + subject.add_callback { mock } + subject.add_callback(:test) { mock } + subject.remove_callback(:test) + + subject.instance_variable_get(:@callbacks)[:test].should be_nil + end + end + end + + context 'reading' do + describe '#read' do + it 'should add the block given as a callback to the :read key' do + subject.should_receive(:add_callback).with(:read) + subject.read { mock } + end + + it 'should call #poll once' do + subject.should_receive(:poll) + subject.read + end + end + + describe '#listen' do + it 'should add the block given as a callback to the :listen key' do + subject.should_receive(:add_callback).with(:listen) + subject.listen { mock } + end + + it 'should call #start_listening' do + subject.should_receive(:start_listening) + subject.listen + end + end + + describe '#stop_listening' do + it 'should tell the board to turn the listener off' do + board.should_receive(:stop_listener).with(subject.pin) + subject.stop_listening + end + + it 'should remove all callbacks with the :listen key' do + subject.listen { mock } + subject.should_receive(:remove_callback).with(:listen) + + subject.stop_listening + end + end + end + + describe '#update' do + it 'should update @state' do + subject.update("something") + subject.instance_variable_get(:@state).should == "something" + end + + it 'should call all callbacks passing in the given data' do + first_block_data = nil + second_block_data = nil + subject.add_callback { |data| first_block_data = data } + subject.add_callback { |data| second_block_data = data } + + subject.update('Some data') + [first_block_data, second_block_data].each { |block_data| block_data.should == "Some data" } + end + + it 'should remove any callbacks keyed with :read' do + subject.add_callback(:read) { |data| first_block_data = data } + + subject.update("Some data") + subject.instance_variable_get(:@callbacks)[:read].should be_nil + end + end + end + end + end +end diff --git a/spec/lib/components/core/base_output_spec.rb b/spec/lib/components/core/base_output_spec.rb new file mode 100644 index 00000000..8552d604 --- /dev/null +++ b/spec/lib/components/core/base_output_spec.rb @@ -0,0 +1,75 @@ +require 'spec_helper' + +module Dino + module Components + module Core + describe BaseOutput do + let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } + let(:board) { Board.new(txrx) } + let(:options) { { pin: 'A0', board: board } } + subject { BaseOutput.new(options) } + + before(:each) { subject } + + describe '#initialize' do + it 'should set mode to out and go low' do + board.should_receive(:set_pin_mode).with(14, :out, nil) + board.should_receive(:digital_write).with(14, Board::LOW) + + BaseOutput.new(options) + end + end + + describe '#digital_write' do + it 'should update the @state instance variable and call #digital_write on the board' do + board.should_receive(:digital_write).with(subject.pin, Board::HIGH).once + + subject.digital_write(Board::HIGH) + subject.state.should == Board::HIGH + end + end + + describe '#analog_write' do + it 'should update the @state instance variable and call #analog_write on the board' do + board.should_receive(:analog_write).with(subject.pin, 128).once + + subject.analog_write(128) + subject.state.should == 128 + end + end + + describe '#write' do + it 'should call #digital_write if value is HIGH' do + subject.should_receive(:digital_write).with(Board::HIGH) + subject.write(Board::HIGH) + end + + + it 'should call #digital_write if value is LOW' do + subject.should_receive(:digital_write).with(Board::LOW) + subject.write(Board::LOW) + end + + it 'should call #analog_write if value is anything else' do + subject.should_receive(:analog_write).with(128) + subject.write(128) + end + end + + describe '#high' do + it 'should call #digital_write with HIGH' do + subject.should_receive(:digital_write).with(Board::HIGH) + subject.high + end + end + + describe '#low' do + it 'should call #digital_write with LOW' do + subject.should_receive(:digital_write).with(Board::LOW) + subject.low + end + end + end + end + end +end diff --git a/spec/lib/components/core/base_spec.rb b/spec/lib/components/core/base_spec.rb index 12184c13..61147f4b 100644 --- a/spec/lib/components/core/base_spec.rb +++ b/spec/lib/components/core/base_spec.rb @@ -4,40 +4,33 @@ module Dino module Components module Core describe Base do + let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } + let(:board) { Board.new(txrx) } + let(:options) { { pin: 'A0', board: board } } + subject { Base.new(options) } + + describe '#initialize' do + it 'should convert the pin to an integer' do + board.should_receive(:convert_pin).with(options[:pin]) - it 'should initialize with board and pin' do - pin = "a pin" - board = "a board" - component = Base.new(pin: pin, board: board) - - component.pin.should == pin - component.board.should == board - end - - it 'should assign pins' do - pins = {red: 'red', green: 'green', blue: 'blue'} - board = "a board" - component = Base.new(pins: pins, board: board) - - component.pins.should == pins - end + component = Base.new(options) + end - it 'should require a pin or pins' do - expect { - Base.new(board: 'some board') - }.to raise_exception - end + it 'should require a pin' do + expect { + Base.new(board: board) + }.to raise_exception + end - it 'should require a board' do - expect { - Base.new(pin: 'some pin') - }.to raise_exception + it 'should require a board' do + expect { + Base.new(pin: 'A0') + }.to raise_exception + end end - context "when subclassed #after_initialize should be executed" do - + context "when subclassed" do class SpecComponent < Base - def sucessfully_initialized? ; @success ; end def options ; @options ; end @@ -48,18 +41,31 @@ def after_initialize(options={}) end end - let(:options) { { pin: pin, board: board } } - let(:pin) { "a pin" } - let(:board) { "a board" } - it "should call #after_initialize with options" do component = SpecComponent.new(options) component.should be_sucessfully_initialized component.options.should eq options end end + + describe '#mode=' do + it 'should tell the board to set the pin mode' do + board.should_receive(:set_pin_mode).with(subject.pin, :out, nil) + + subject.send(:mode=, :out) + subject.mode.should == :out + end + end + + describe '#pullup=' do + it 'should tell the board to set the pullup mode' do + board.should_receive(:set_pullup).with(subject.pin, true) + + subject.send(:pullup=, true) + subject.pullup.should == true + end + end end end end end - diff --git a/spec/lib/components/core/digital_input_spec.rb b/spec/lib/components/core/digital_input_spec.rb new file mode 100644 index 00000000..4a73ca63 --- /dev/null +++ b/spec/lib/components/core/digital_input_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +module Dino + module Components + module Core + describe DigitalInput do + let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } + let(:board) { Board.new(txrx) } + let(:options) { { pin: 'A0', board: board } } + subject { DigitalInput.new(options) } + + describe '#initialize' do + it 'should start listening immediately' do + board.should_receive(:digital_listen).with(14) + + component = DigitalInput.new(options) + end + end + + describe '#poll' do + it 'should send #digital_read to the board with its pin' do + board.should_receive(:digital_read).with(subject.pin) + subject.read + end + end + + describe '#start_listening' do + it 'should send #digital_listen to the board with its pin' do + subject + subject.stop_listening + board.should_receive(:digital_listen).with(subject.pin) + + subject.start_listening + end + end + + context 'callbacks' do + before :each do + @low_callback = mock + @high_callback = mock + subject.on_low { @low_callback.called } + subject.on_high { @high_callback.called } + end + + describe '#on_low' do + it 'should add a callback that only gets fired when LOW' do + @low_callback.should_receive(:called) + @high_callback.should_not_receive(:called) + + subject.update(DigitalInput::LOW) + end + end + + describe '#on_high' do + it 'should add a callback that only gets fired when HIGH' do + @low_callback.should_not_receive(:called) + @high_callback.should_receive(:called) + + subject.update(DigitalInput::HIGH) + end + end + end + end + end + end +end diff --git a/spec/lib/components/core/multi_pin_spec.rb b/spec/lib/components/core/multi_pin_spec.rb new file mode 100644 index 00000000..8657f4e1 --- /dev/null +++ b/spec/lib/components/core/multi_pin_spec.rb @@ -0,0 +1,78 @@ +require 'spec_helper' + +module Dino + module Components + module Core + describe MultiPin do + let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } + let(:board) { Board.new(txrx) } + let(:options) { { pins: {red: 9, blue: 10}, board: board } } + + describe '#initialize' do + it 'should require a pin' do + expect { + MultiPin.new(board: board) + }.to raise_exception + end + + it 'should require a board' do + expect { + MultiPin.new(pins: options[:pins]) + }.to raise_exception + end + end + + context "when subclassed" do + class MPComponent < MultiPin + def sucessfully_initialized? ; @success ; end + + def options ; @options ; end + + def after_initialize(options={}) + @success = true + @options = options + proxy red: BaseOutput, blue: BaseInput + end + end + + it "should call #after_initialize with options" do + component = MPComponent.new(options) + component.should be_sucessfully_initialized + component.options.should eq options + end + + describe '#proxy' do + it 'should raise if any of the required pins are missing' do + expect { + MPComponent.new(board: board, pins: {blue: 11}) + }.to raise_exception(/red/) + end + + it 'should create the right class of proxy component for each pin with the right options' do + BaseOutput.should_receive(:new).with({board: board, pin: options[:pins][:red], pullup: nil }) + BaseInput.should_receive(:new).with({board: board, pin: options[:pins][:blue], pullup: true}) + + MPComponent.new options.merge(pullups: {blue: true}) + end + + it 'should assign the proxy commponent to the right instance variable' do + component = MPComponent.new(options) + component.red.class.should == BaseOutput + component.blue.class.should == BaseInput + end + + describe '#states' do + it 'should return a hash corresponding to the state of each proxy component (pin)' do + component = MPComponent.new(options) + component.red.write(128) + component.blue.update(555) + + component.state.should == {red: 128, blue: 555} + end + end + end + end + end + end + end +end diff --git a/spec/lib/components/led_spec.rb b/spec/lib/components/led_spec.rb index dd51c21c..9552e020 100644 --- a/spec/lib/components/led_spec.rb +++ b/spec/lib/components/led_spec.rb @@ -3,48 +3,6 @@ module Dino module Components describe Led do - let(:board) { mock(:board, digital_write: true, set_pin_mode: true) } - - describe '#initialize' do - it 'should raise if it does not receive a pin' do - expect { - Led.new(board: board) - }.to raise_exception - end - - it 'should raise if it does not receive a board' do - expect { - Led.new(pins: {}) - }.to raise_exception - end - - it 'should set the pin to out' do - board.should_receive(:set_pin_mode).with(13, :out, nil) - Led.new(pin: 13, board: board) - end - - it 'should set the pin to low' do - board.should_receive(:digital_write).with(13, Board::LOW) - Led.new(pin: 13, board: board) - end - end - - describe '#on' do - it 'should send a high to the board with the pin' do - @led = Led.new(pin: 13, board: board) - board.should_receive(:digital_write).with(13, Board::HIGH) - @led.on - end - end - - describe '#off' do - it 'should send a high to the board with the pin' do - @led = Led.new(pin: 13, board: board) - board.should_receive(:digital_write).with(13, Board::LOW) - @led.off - end - end - describe '#blink' do it 'should turn the led off if it is on' it 'should not block' diff --git a/spec/lib/components/rgb_led_spec.rb b/spec/lib/components/rgb_led_spec.rb index 45ac741f..03e18fb6 100644 --- a/spec/lib/components/rgb_led_spec.rb +++ b/spec/lib/components/rgb_led_spec.rb @@ -3,119 +3,62 @@ module Dino module Components describe RgbLed do - let(:board) { mock(:board, analog_write: true, set_pin_mode: true) } - let(:pins) { {red: 1, green: 2, blue: 3} } - let(:rgb) { RgbLed.new(pins: pins, board: board)} + let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } + let(:board) { Board.new(txrx) } + let(:options) { { board: board, pins: {red: 1, green: 2, blue: 3} } } + subject { RgbLed.new(options) } describe '#initialize' do - it 'should raise if it does not receive a pin' do - expect { - RgbLed.new(board: 'a board') - }.to raise_exception - end - - it 'should raise if it does not receive a board' do - expect { - RgbLed.new(pins: pins) - }.to raise_exception - end - - it 'should set the pin to out' do - board.should_receive(:set_pin_mode).with(1, :out, nil) - board.should_receive(:set_pin_mode).with(2, :out, nil) - board.should_receive(:set_pin_mode).with(3, :out, nil) - - RgbLed.new(pins: pins, board: board) - end - - it 'should set the pin to low' do - board.should_receive(:analog_write).with(1, Board::LOW) - board.should_receive(:analog_write).with(2, Board::LOW) - board.should_receive(:analog_write).with(3, Board::LOW) - - RgbLed.new(pins: pins, board: board) + it 'should create a BaseOutput instance for each pin' do + led = RgbLed.new(options) + + led.red.class.should == Core::BaseOutput + led.green.class.should == Core::BaseOutput + led.blue.class.should == Core::BaseOutput end end - describe '#red' do - it 'should set red to high, blue and green to low' do - board.should_receive(:analog_write).with(1, Board::HIGH) - board.should_receive(:analog_write).with(2, Board::LOW) - board.should_receive(:analog_write).with(3, Board::LOW) - rgb.red - end - end + describe '#write' do + it 'should write the elements of the array to red, green and blue' do + subject.red.should_receive(:write).with(0) + subject.green.should_receive(:write).with(128) + subject.blue.should_receive(:write).with(0) - describe '#green' do - it 'should set green to high, red and blue to low' do - board.should_receive(:analog_write).with(1, Board::LOW) - board.should_receive(:analog_write).with(2, Board::HIGH) - board.should_receive(:analog_write).with(3, Board::LOW) - rgb.green + subject.write [0, 128, 0] end end - describe '#blue' do - it 'should set blue to high, red and green to low' do - board.should_receive(:analog_write).with(1, Board::LOW) - board.should_receive(:analog_write).with(2, Board::LOW) - board.should_receive(:analog_write).with(3, Board::HIGH) - rgb.blue + describe '#color=' do + it 'should write an array of values' do + subject.should_receive(:write).with([128, 0, 0]) + subject.color = [128, 0, 0] end - end - - describe '#cyan' do - it 'should set blue and green to high, red to low' do - board.should_receive(:analog_write).with(1, Board::LOW) - board.should_receive(:analog_write).with(2, Board::HIGH) - board.should_receive(:analog_write).with(3, Board::HIGH) - rgb.cyan - end - end - - describe '#yellow' do - it 'should set red and green to high, blue to low' do - board.should_receive(:analog_write).with(1, Board::HIGH) - board.should_receive(:analog_write).with(2, Board::HIGH) - board.should_receive(:analog_write).with(3, Board::LOW) - rgb.yellow - end - end - - describe '#magenta' do - it 'should set red and blue to high, green to low' do - board.should_receive(:analog_write).with(1, Board::HIGH) - board.should_receive(:analog_write).with(2, Board::LOW) - board.should_receive(:analog_write).with(3, Board::HIGH) - rgb.magenta - end - end - - describe '#white' do - it 'should set all to high' do - board.should_receive(:analog_write).with(1, Board::HIGH) - board.should_receive(:analog_write).with(2, Board::HIGH) - board.should_receive(:analog_write).with(3, Board::HIGH) - rgb.white - end - end - describe '#off' do - it 'should set all to low' do - board.should_receive(:analog_write).with(1, Board::LOW) - board.should_receive(:analog_write).with(2, Board::LOW) - board.should_receive(:analog_write).with(3, Board::LOW) - rgb.off + it 'should look up named colors in COLORS whether passed in as symbol or string' do + colors = { + red: [255, 000, 000], + green: [000, 255, 000], + blue: [000, 000, 255], + cyan: [000, 255, 255], + yellow: [255, 255, 000], + magenta: [255, 000, 255], + white: [255, 255, 255], + off: [000, 000, 000] + } + colors.each_value { |color| subject.should_receive(:write).with(color).twice } + + colors.each_key { |key| subject.color = key } + colors.each_key { |key| subject.color = key.to_s } end end - describe '#blinky' do - it 'should set blue to high, red and green to low' do + describe '#cycle' do + it 'should cycle through the 3 base colors' do Array.any_instance.should_receive(:cycle).and_yield(:red).and_yield(:green).and_yield(:blue) - rgb.should_receive(:red) - rgb.should_receive(:green) - rgb.should_receive(:blue) - rgb.blinky + subject.should_receive(:color=).with(:red) + subject.should_receive(:color=).with(:green) + subject.should_receive(:color=).with(:blue) + subject.cycle end end end diff --git a/spec/lib/components/sensor_spec.rb b/spec/lib/components/sensor_spec.rb index fb36fe1e..589a4587 100644 --- a/spec/lib/components/sensor_spec.rb +++ b/spec/lib/components/sensor_spec.rb @@ -3,88 +3,6 @@ module Dino module Components describe Sensor do - - let(:board){mock(:board).as_null_object} - let(:sensor) {Sensor.new(board: board, pin: 7)} - - describe '#initalize' do - it 'should raise if it does not receive a pin' do - expect { Sensor.new(board: board) }.to raise_exception - end - - it 'should raise if it does not receive a board' do - expect { Sensor.new(pin: 7) }.to raise_exception - end - - it 'should add itself to the board and start read' do - board.should_receive(:add_input_hardware) - board.should_receive(:start_read) - Sensor.new(board: board, pin: 7) - end - - it 'should initalize callbacks' do - sensor.instance_variable_get(:@callbacks).should == {} - end - end - - describe '#read' do - it 'should tell the board to read once' do - board.should_receive(:analog_read).with(7) - sensor.read - end - - it 'should accept a callback as block and add to @callbacks[:read]' - end - - describe '#listen' do - it 'should tell the board to start listening' do - board.should_receive(:analog_listen).with(7) - sensor.listen - end - - it 'should accept a callback as block and add to @callbacks[:listen]' - end - - describe '#add_callback' do - it 'should require a key' - it 'should add the block to the array corresponding to the key in the @callbacks hash' - end - - describe '#clear_callbacks' do - it 'should clear all callbacks if called with no key' do - sensor.on_data { |data| puts data } - sensor.on_data(:test) { |data| puts data } - sensor.clear_callbacks - sensor.instance_variable_get(:@callbacks).should == {} - end - - it 'should clear callbacks of a particular key if called with a key' - end - - describe '#on_data' do - it "should add a callback with the :persistent key" do - sensor.on_data { "this is a block" } - sensor.instance_variable_get(:@callbacks)[:persistent].should_not be_empty - end - end - - describe '#update' do - it 'should call all callbacks passing in the given data' do - sensor = Sensor.new(board: board, pin: 'a pin') - - first_block_data = nil - second_block_data = nil - sensor.on_data do |data| - first_block_data = data - end - sensor.on_data do |data| - second_block_data = data - end - - sensor.update('Some data') - [first_block_data, second_block_data].each { |block_data| block_data.should == "Some data" } - end - end end end end diff --git a/spec/lib/components/servo_spec.rb b/spec/lib/components/servo_spec.rb index c96ab6be..10639774 100644 --- a/spec/lib/components/servo_spec.rb +++ b/spec/lib/components/servo_spec.rb @@ -3,29 +3,15 @@ module Dino module Components describe Servo do - let(:board) { mock(:board, analog_write: true, set_pin_mode: true, servo_toggle: true, servo_write: true) } + let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } + let(:board) { Board.new(txrx) } + let(:options) { { board: board, pin: 9 } } + subject { Servo.new(options) } describe '#initialize' do - it 'should raise if it does not receive a pin' do - expect { - Servo.new(board: board) - }.to raise_exception - end - - it 'should raise if it does not receive a board' do - expect { - Servo.new(pin: 13) - }.to raise_exception - end - - it 'should set the pins to out' do - board.should_receive(:set_pin_mode).with(13, :out, nil) - Servo.new(pin: 13, board: board) - end - - it 'should set the inital position to 0' do - servo = Servo.new(pin: 13, board: board) - servo.instance_variable_get(:@position).should == 0 + it 'should toggle the servo library on for the pin' do + board.should_receive(:servo_toggle).with(options[:pin], 1) + subject end end @@ -34,17 +20,17 @@ module Components it 'should set the position of the Servo' do servo.position = 90 - servo.instance_variable_get(:@position).should == 90 + servo.position.should == 90 end it 'should let you write up to 180' do servo.position = 180 - servo.instance_variable_get(:@position).should == 180 + servo.position.should == 180 end it 'should modulate when position > 180' do servo.position = 190 - servo.instance_variable_get(:@position).should == 10 + servo.position.should == 10 end it 'should write the new position to the board' do diff --git a/spec/lib/components/shift_register_spec.rb b/spec/lib/components/shift_register_spec.rb index fc7a9202..7b38bbac 100644 --- a/spec/lib/components/shift_register_spec.rb +++ b/spec/lib/components/shift_register_spec.rb @@ -3,53 +3,35 @@ module Dino module Components describe ShiftRegister do - let(:board) { mock(:board, digital_write: true, set_pin_mode: true) } - - subject { ShiftRegister.new board: board, pins: {clock: 12, data: 11, latch: 8} } + let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } + let(:board) { Board.new(txrx) } + let(:options) { { board: board, pins: {clock: 12, data: 11, latch: 8} } } + subject { ShiftRegister.new(options) } describe '#initialize' do - it 'should set all pins to out and low' do - board.should_receive(:set_pin_mode).with(12, :out, nil) - board.should_receive(:set_pin_mode).with(11, :out, nil) - board.should_receive(:set_pin_mode).with(8, :out, nil) - board.should_receive(:digital_write).with(12, Board::LOW) - board.should_receive(:digital_write).with(11, Board::LOW) - board.should_receive(:digital_write).with(8, Board::LOW) - - ShiftRegister.new(pins: {clock: 12, data: 11, latch: 8}, board: board) - end - end - - describe '#latch_off' do - it 'should set the latch pin low' do - board.should_receive(:digital_write).with(8, Board::LOW) - - subject.latch_off - end - end - - describe '#latch_off' do - it 'should set the latch pin high' do - board.should_receive(:digital_write).with(8, Board::HIGH) - - subject.latch_on + it 'should create a BaseOutput instance for each pin' do + subject.clock.class.should == Core::BaseOutput + subject.latch.class.should == Core::BaseOutput + subject.data.class.should == Core::BaseOutput end end describe '#write' do + before(:each) { subject } + it 'should write a single byte as value and clock pin as aux to the data pin' do - board.should_receive(:convert_pin).with(11) { |pin| pin } - board.should_receive(:convert_pin).with(12) { |pin| pin } + subject.latch.should_receive(:digital_write).with(Board::LOW) board.should_receive(:write).with "11.11.255.12\n" + subject.latch.should_receive(:digital_write).with(Board::HIGH) subject.write(255) end it 'should write an array of bytes as value and clock pin as aux to the data pin' do - board.should_receive(:convert_pin).with(11) { |pin| pin } - board.should_receive(:convert_pin).with(12) { |pin| pin } + subject.latch.should_receive(:digital_write).with(Board::LOW) board.should_receive(:write).with "11.11.255.12\n" board.should_receive(:write).with "11.11.0.12\n" + subject.latch.should_receive(:digital_write).with(Board::HIGH) subject.write([255,0]) end diff --git a/spec/lib/components/ssd_spec.rb b/spec/lib/components/ssd_spec.rb index c6b0fc6d..ac0d90f8 100644 --- a/spec/lib/components/ssd_spec.rb +++ b/spec/lib/components/ssd_spec.rb @@ -3,89 +3,75 @@ module Dino module Components describe SSD do + let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } + let(:board) { Board.new(txrx) } let(:anode) { 11 } let(:pins) { [12,13,3,4,5,10,9] } - - let(:board) do - mock(:board, digital_write: true, set_pin_mode: true) - end - - let(:ssd) do - SSD.new board: board, pins: pins, anode: anode - end + subject { SSD.new(board: board, pins: pins, anode: anode) } describe '#initialize' do - it 'should raise if it does not receive a pin' do - expect { SSD.new(board: board, anode: anode) }.to raise_exception - end - - it 'should raise if it does not receive a board' do - expect { SSD.new(pins: pins, anode: anode) }.to raise_exception - end - - it 'should raise if it does not receive a board' do + it 'should raise if it does not receive an anode' do expect { SSD.new(board: board, pins: pins) }.to raise_exception end - it 'should set the pins to out' do - pins.each do |pin| - board.should_receive(:set_pin_mode).with(pin, :out, nil) - end + it 'should create a Core::BaseOutput for the anode and each pin' do + subject - ssd.after_initialize(anode: anode) + subject.anode.class.should == Core::BaseOutput + subject.pins.each { |pin| pin.class.should == Core::BaseOutput} end it "should clear the display" do - ssd.should_receive(:clear).once - ssd.after_initialize(anode: anode) + SSD.any_instance.should_receive(:clear).once + SSD.new(board: board, pins: pins, anode: anode) end it "should turn on the display" do - ssd.should_receive(:on).once - ssd.after_initialize(anode: anode) + SSD.any_instance.should_receive(:on).once + SSD.new(board: board, pins: pins, anode: anode) end end describe '#clear' do it 'should toggle all the seven leds to off' do - ssd.should_receive(:toggle).exactly(7).with(anything, 0) - ssd.clear + subject.should_receive(:toggle).exactly(7).with(anything, 0) + subject.clear end end describe '#on' do it 'should turn the ssd on' do - board.should_receive(:digital_write).with(anode, Board::HIGH) - ssd.on + subject.anode.should_receive(:digital_write).with(Board::HIGH) + subject.on end end describe '#off' do it 'should turn the ssd off' do - board.should_receive(:digital_write).with(anode, Board::LOW) - ssd.off + subject.anode.should_receive(:digital_write).with(Board::LOW) + subject.off end end describe '#display' do it "should scroll on multiple characters" do - ssd.should_receive(:scroll).with('FOO') - ssd.display('foo') + subject.should_receive(:scroll).with('FOO') + subject.display('foo') end it "should make sure the ssd is turned on" do - ssd.should_receive(:on) - ssd.display(1) + subject.should_receive(:on) + subject.display(1) end it "should clear the display on unknown character" do - ssd.should_receive(:clear) - ssd.display('+') + subject.should_receive(:clear) + subject.display('+') end it "should toggle all the segments in order to display a character" do - ssd.should_receive(:toggle).exactly(7) - ssd.display('7') + subject.should_receive(:toggle).exactly(7) + subject.display('7') end end end diff --git a/spec/lib/components/stepper_spec.rb b/spec/lib/components/stepper_spec.rb index 013eee9d..a37e1b23 100644 --- a/spec/lib/components/stepper_spec.rb +++ b/spec/lib/components/stepper_spec.rb @@ -3,56 +3,36 @@ module Dino module Components describe Stepper do - let(:board) { mock(:board, digital_write: true, set_pin_mode: true) } + let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } + let(:board) { Board.new(txrx) } + let(:options) { { pins: {step: 9, direction: 10}, board: board } } - describe '#initialize' do - it 'should raise if it does not receive a step pin' do - expect { - Stepper.new(board: board) - }.to raise_exception - end - - it 'should raise if it does not receive a direction pin' do - expect { - Stepper.new(board: board) - }.to raise_exception - end - - it 'should raise if it does not receive a board' do - expect { - Stepper.new(pins: {step: 12, direction: 13}) - }.to raise_exception - end + subject { Stepper.new(options) } - it 'should set the pins to out' do - board.should_receive(:set_pin_mode).with(13, :out, nil) - board.should_receive(:set_pin_mode).with(12, :out, nil) - Stepper.new(pins: {step: 13, direction: 12}, board: board) - end - - it 'should set the step pin to low' do - board.should_receive(:digital_write).with(13, Board::LOW) - Stepper.new(pins: {step: 13, direction: 12}, board: board) + describe '#initialize' do + it 'should create a BaseOutput instance for each pin' do + subject.step.class.should == Core::BaseOutput + subject.direction.class.should == Core::BaseOutput end end describe '#step_cc' do - it 'should send a high to the board with the pin' do - @stepper = Stepper.new(pins: {step: 13, direction: 12}, board: board) - board.should_receive(:digital_write).with(12, Board::HIGH) - board.should_receive(:digital_write).with(13, Board::HIGH) - board.should_receive(:digital_write).with(13, Board::LOW) - @stepper.step_cc + it 'should send high to the step pin with the direction pin high' do + subject.direction.should_receive(:digital_write).with(Board::HIGH) + subject.step.should_receive(:digital_write).with(Board::HIGH) + subject.step.should_receive(:digital_write).with(Board::LOW) + + subject.step_cc end end describe '#step_cw' do - it 'should send a high to the board with the pin' do - @stepper = Stepper.new(pins: {step: 13, direction: 12}, board: board) - board.should_receive(:digital_write).with(12, Board::LOW) - board.should_receive(:digital_write).with(13, Board::HIGH) - board.should_receive(:digital_write).with(13, Board::LOW) - @stepper.step_cw + it 'should send high to the board with the direction pin low' do + subject.direction.should_receive(:digital_write).with(Board::LOW) + subject.step.should_receive(:digital_write).with(Board::HIGH) + subject.step.should_receive(:digital_write).with(Board::LOW) + + subject.step_cw end end end From 5fa9b9602f65bd4334d523d73e3361b5670420ff Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Wed, 12 Jun 2013 21:12:16 -0400 Subject: [PATCH 027/296] Core::Threaded mixin to provide components with a managed thread Updated LED example with non-blocking blink --- examples/led/led.rb | 17 ++- lib/dino/components.rb | 1 + lib/dino/components/core.rb | 1 + lib/dino/components/core/base_input.rb | 2 - lib/dino/components/core/base_output.rb | 7 +- lib/dino/components/core/threaded.rb | 51 +++++++++ lib/dino/components/led.rb | 8 +- lib/dino/components/relay.rb | 6 ++ spec/lib/components/core/base_output_spec.rb | 16 +++ spec/lib/components/core/threaded_spec.rb | 104 +++++++++++++++++++ spec/lib/components/led_spec.rb | 4 - 11 files changed, 204 insertions(+), 13 deletions(-) create mode 100644 lib/dino/components/core/threaded.rb create mode 100644 lib/dino/components/relay.rb create mode 100644 spec/lib/components/core/threaded_spec.rb diff --git a/examples/led/led.rb b/examples/led/led.rb index 9cc4182c..f7b8b7a7 100644 --- a/examples/led/led.rb +++ b/examples/led/led.rb @@ -8,7 +8,16 @@ board = Dino::Board.new(Dino::TxRx::Serial.new) led = Dino::Components::Led.new(pin: 13, board: board) -[:on, :off].cycle do |switch| - led.send(switch) - sleep 0.5 -end +# Start blinking every half second. +led.blink 0.5 + +# Wait for 5 seconds. #blink does not block. +sleep 5 + +# Calling #on implicitly stops #blink. +led.on +sleep 5 + +# Blink faster. +led.blink 0.25 +sleep diff --git a/lib/dino/components.rb b/lib/dino/components.rb index 7c06c3e7..6ff4354a 100644 --- a/lib/dino/components.rb +++ b/lib/dino/components.rb @@ -11,5 +11,6 @@ module Components autoload :IrReceiver, 'dino/components/ir_receiver' autoload :LCD, 'dino/components/lcd' autoload :ShiftRegister, 'dino/components/shift_register' + autoload :Relay, 'dino/components/relay' end end diff --git a/lib/dino/components/core.rb b/lib/dino/components/core.rb index 2ef4d795..f0eac0e8 100644 --- a/lib/dino/components/core.rb +++ b/lib/dino/components/core.rb @@ -3,6 +3,7 @@ module Components module Core require 'dino/components/core/base' require 'dino/components/core/multi_pin' + require 'dino/components/core/threaded' require 'dino/components/core/base_input' require 'dino/components/core/analog_input' require 'dino/components/core/digital_input' diff --git a/lib/dino/components/core/base_input.rb b/lib/dino/components/core/base_input.rb index 5d2dfe96..63dd48cb 100644 --- a/lib/dino/components/core/base_input.rb +++ b/lib/dino/components/core/base_input.rb @@ -2,8 +2,6 @@ module Dino module Components module Core class BaseInput < Base - attr_reader :state - def initialize(options={}) super options diff --git a/lib/dino/components/core/base_output.rb b/lib/dino/components/core/base_output.rb index 103f8977..c73d83d8 100644 --- a/lib/dino/components/core/base_output.rb +++ b/lib/dino/components/core/base_output.rb @@ -2,7 +2,8 @@ module Dino module Components module Core class BaseOutput < Base - attr_reader :state + include Threaded + interrupt_with :digital_write, :analog_write def initialize(options={}) super options @@ -35,6 +36,10 @@ def high digital_write Board::HIGH end + def toggle + state == Board::LOW ? high : low + end + alias :off :low alias :on :high end diff --git a/lib/dino/components/core/threaded.rb b/lib/dino/components/core/threaded.rb new file mode 100644 index 00000000..a1b20329 --- /dev/null +++ b/lib/dino/components/core/threaded.rb @@ -0,0 +1,51 @@ +module Dino + module Components + module Core + module Threaded + attr_reader :thread, :interrupts_enabled + + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + def interrupt_with(*args) + interrupts = self.class_eval('@@interrupts') rescue [] + args.each { |a| interrupts << a } + self.class_variable_set(:@@interrupts, interrupts) + end + end + + def threaded(&block) + stop_thread + enable_interrupts unless interrupts_enabled + @thread = Thread.new { block.call} + end + + def threaded_loop(&block) + threaded do + loop { block.call } + end + end + + def stop_thread + @thread.kill if @thread + end + + def enable_interrupts + interrupts = self.class.class_eval('@@interrupts') + interrupts.each do |method_name| + standard_method = self.method(method_name) + + singleton_class.send(:define_method, method_name) do |*args| + stop_thread unless (Thread.current == @thread) + standard_method.call(*args) + end + end + + @interrupts_enabled = true + end + end + end + end +end diff --git a/lib/dino/components/led.rb b/lib/dino/components/led.rb index 302d103d..b8f8dff8 100644 --- a/lib/dino/components/led.rb +++ b/lib/dino/components/led.rb @@ -1,8 +1,12 @@ module Dino module Components class Led < Core::BaseOutput - alias :on :high - alias :off :low + def blink(interval=0.5) + threaded_loop do + toggle + sleep interval + end + end end end end diff --git a/lib/dino/components/relay.rb b/lib/dino/components/relay.rb new file mode 100644 index 00000000..3b285532 --- /dev/null +++ b/lib/dino/components/relay.rb @@ -0,0 +1,6 @@ +module Dino + module Components + class Relay < Core::BaseOutput + end + end +end diff --git a/spec/lib/components/core/base_output_spec.rb b/spec/lib/components/core/base_output_spec.rb index 8552d604..5f166d10 100644 --- a/spec/lib/components/core/base_output_spec.rb +++ b/spec/lib/components/core/base_output_spec.rb @@ -69,6 +69,22 @@ module Core subject.low end end + + describe '#toggle' do + it 'should call high if currently LOW' do + subject.low + subject.should_receive(:high) + + subject.toggle + end + + it 'should call LOW if anything else' do + subject.high + subject.should_receive(:low) + + subject.toggle + end + end end end end diff --git a/spec/lib/components/core/threaded_spec.rb b/spec/lib/components/core/threaded_spec.rb new file mode 100644 index 00000000..4297428e --- /dev/null +++ b/spec/lib/components/core/threaded_spec.rb @@ -0,0 +1,104 @@ +require 'spec_helper' + +module Dino + module Components + module Core + describe Threaded do + class ThreadedComponent + include Threaded + interrupt_with :interrupt_it + def interrupt_it; end + def dont_interrupt_it; end + end + + subject { ThreadedComponent.new } + + describe '#threaded' do + it 'should stop the existing thread' do + subject.should_receive(:stop_thread) + subject.threaded { mock } + end + + it 'should enable interrupts on the first threaded call' do + subject.should_receive(:enable_interrupts).once + thread = subject.threaded { mock } + end + + it 'should start a thread and call the block passed.' do + block = mock + Thread.should_receive(:new).once.and_yield + block.should_receive(:call) + + subject.threaded { block.call } + end + end + + describe '#stop_thread' do + it 'should kill the internal thread' do + subject.threaded { sleep } + subject.thread.should_receive(:kill) + + subject.stop_thread + end + end + + describe '#enable_interrupts' do + it 'should set @enable_interrupts to true' do + subject.enable_interrupts + subject.interrupts_enabled.should == true + end + end + + context 'interrupts' do + it 'should make method names passed to #interrupt_with stop the internal thread' do + subject.threaded { sleep } + subject.should_receive(:stop_thread) + + subject.interrupt_it + end + + it 'should not make other methods stop the internal thread' do + subject.threaded { sleep } + subject.should_not_receive(:stop_thread) + + subject.dont_interrupt_it + end + end + + context 'with multiple threads' do + it 'should let other threads interrupt the internal thread' do + subject.threaded {sleep} + subject.should_receive(:stop_thread) + + subject.interrupt_it + end + + it 'should not let the internal thread interrupt itself' do + subject.should_receive(:interrupt_it).exactly(10).times + subject.should_not_receive(:stop_thread) + + # Simulate being inside the internal thread. + subject.enable_interrupts + subject.instance_variable_set(:@thread, Thread.current) + 10.times { subject.interrupt_it } + end + end + + context 'when included and then subclassed' do + it 'should allow calling #interrupt_with again' do + class SubThreaded < ThreadedComponent + interrupt_with :stop_it + def stop_it; end + end + + component = SubThreaded.new + component.threaded { sleep } + component.should_receive(:stop_thread) + + component.stop_it + end + end + end + end + end +end diff --git a/spec/lib/components/led_spec.rb b/spec/lib/components/led_spec.rb index 9552e020..845c9eee 100644 --- a/spec/lib/components/led_spec.rb +++ b/spec/lib/components/led_spec.rb @@ -3,10 +3,6 @@ module Dino module Components describe Led do - describe '#blink' do - it 'should turn the led off if it is on' - it 'should not block' - end end end end From d93c624b1143ec5248297e02f3fba2552e9e3d9d Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Sun, 23 Jun 2013 18:06:00 -0400 Subject: [PATCH 028/296] Fix listeners on MEGA. Due support with default 12-bit analog read. --- lib/dino/components/core/threaded.rb | 2 +- src/lib/Dino.cpp | 16 ++++++++-------- src/lib/Dino.h | 4 ++++ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/dino/components/core/threaded.rb b/lib/dino/components/core/threaded.rb index a1b20329..a55c93fd 100644 --- a/lib/dino/components/core/threaded.rb +++ b/lib/dino/components/core/threaded.rb @@ -33,7 +33,7 @@ def stop_thread end def enable_interrupts - interrupts = self.class.class_eval('@@interrupts') + interrupts = self.class.class_eval('@@interrupts') rescue [] interrupts.each do |method_name| standard_method = self.method(method_name) diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index a61b579a..519d3547 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -116,7 +116,7 @@ void Dino::updateListeners() { } } void Dino::updateDigitalListeners() { - for (int i = 0; i < 22; i++) { + for (int i = 0; i < PIN_COUNT; i++) { if (digitalListeners[i]) { pin = i; dRead(); @@ -128,7 +128,7 @@ void Dino::updateDigitalListeners() { } } void Dino::updateAnalogListeners() { - for (int i = 0; i < 22; i++) { + for (int i = 0; i < PIN_COUNT; i++) { if (analogListeners[i]) { pin = i; aRead(); @@ -275,18 +275,18 @@ void Dino::shiftWrite() { // CMD = 90 void Dino::reset() { + #ifdef ANALOG_RESOLUTION + analogReadResolution(ANALOG_RESOLUTION); + #endif heartRate = 4000; // Default heartRate is ~4ms. loopCount = 0; analogDivider = 4; // Update analog listeners every ~16ms. - for (int i = 0; i < 22; i++) digitalListeners[i] = false; - for (int i = 0; i < 22; i++) digitalListenerValues[i] = 2; - for (int i = 0; i < 22; i++) analogListeners[i] = false; + for (int i = 0; i < PIN_COUNT; i++) digitalListeners[i] = false; + for (int i = 0; i < PIN_COUNT; i++) digitalListenerValues[i] = 2; + for (int i = 0; i < PIN_COUNT; i++) analogListeners[i] = false; lastUpdate = micros(); fragmentIndex = 0; charIndex = 0; - #ifdef debug - Serial.println("Reset the board to defaults. pin "); - #endif sprintf(response, "ACK:%02d", A0); } diff --git a/src/lib/Dino.h b/src/lib/Dino.h index 28b084a9..af07d51c 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -13,6 +13,10 @@ #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) # define PIN_COUNT 70 # define SERVO_OFFSET 22 +#elif defined(__SAM3X8E__) +# define PIN_COUNT 66 +# define SERVO_OFFSET 22 +# define ANALOG_RESOLUTION 12 #else # define PIN_COUNT 22 # define SERVO_OFFSET 2 From 7f522aa6dff09eb612ca7fad2308050c2e37e56d Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Mon, 24 Jun 2013 16:16:54 -0400 Subject: [PATCH 029/296] Full Due support: 12bit IO, DACs, use native ARM serial port by default --- lib/dino/board.rb | 42 +++++++++++++++++--- lib/dino/components/core/base_input.rb | 2 +- lib/dino/components/core/base_output.rb | 8 ++-- lib/dino/tx_rx/base.rb | 2 +- spec/lib/board_spec.rb | 12 +++++- spec/lib/components/core/base_input_spec.rb | 4 +- spec/lib/components/core/base_output_spec.rb | 20 +++++----- spec/lib/components/shift_register_spec.rb | 8 ++-- spec/lib/components/ssd_spec.rb | 4 +- spec/lib/components/stepper_spec.rb | 12 +++--- src/dino_serial.ino | 13 ++++-- src/lib/Dino.cpp | 21 ++++++++-- src/lib/Dino.h | 4 +- 13 files changed, 105 insertions(+), 47 deletions(-) diff --git a/lib/dino/board.rb b/lib/dino/board.rb index be83cb05..5989cb08 100644 --- a/lib/dino/board.rb +++ b/lib/dino/board.rb @@ -1,13 +1,25 @@ module Dino class Board - attr_reader :input_hardware, :analog_zero - LOW, HIGH = 000, 255 + attr_reader :high, :low, :input_hardware, :analog_zero, :dac_zero DIVIDERS = [1, 2, 4, 8, 16, 32, 64, 128] - def initialize(io) + def initialize(io, options={}) + @bits = options[:bits] || 8 @io, @input_hardware = io, [] io.add_observer(self) - @analog_zero = @io.handshake + + @analog_zero, @dac_zero = @io.handshake.to_s.split(",").map { |pin| pin.to_i } + define_logic + end + + def define_logic + @low = 0 + @high = (2 ** @bits) - 1 + self.analog_resolution = @bits + end + + def analog_resolution=(value) + write Dino::Message.encode(command: 96, value: value) end def analog_divider=(value) @@ -58,7 +70,7 @@ def set_pin_mode(pin, mode, pullup=nil) def set_pullup(pin, pullup) pin = convert_pin(pin) - pullup ? digital_write(pin, HIGH) : digital_write(pin, LOW) + pullup ? digital_write(pin, @high) : digital_write(pin, @low) end PIN_COMMANDS = { @@ -79,8 +91,26 @@ def set_pullup(pin, pullup) end end + DIGITAL_REGEX = /\A\d+\z/i + ANALOG_REGEX = /\A(a)\d+\z/i + DAC_REGEX = /\A(dac)\d+\z/i + def convert_pin(pin) - pin.to_s.match(/\Aa/i) ? @analog_zero + pin.to_s.gsub(/\Aa/i, '').to_i : pin.to_i + pin = pin.to_s + + return pin.to_i if pin.match(DIGITAL_REGEX) + return analog_pin_to_i(pin) if pin.match(ANALOG_REGEX) + return dac_pin_to_i(pin) if pin.match(DAC_REGEX) + + nil + end + + def analog_pin_to_i(pin) + @analog_zero + pin.gsub(/\Aa/i, '').to_i + end + + def dac_pin_to_i(pin) + @dac_zero + pin.gsub(/\Aa/i, '').to_i end end end diff --git a/lib/dino/components/core/base_input.rb b/lib/dino/components/core/base_input.rb index 63dd48cb..b0092d6b 100644 --- a/lib/dino/components/core/base_input.rb +++ b/lib/dino/components/core/base_input.rb @@ -40,7 +40,7 @@ def add_callback(key=nil, &block) alias :on_data :add_callback def remove_callback(key=nil) - key ? @callbacks[key] = nil : @callbacks = {} + key ? @callbacks[key] = [] : @callbacks = {} end alias :remove_callbacks :remove_callback diff --git a/lib/dino/components/core/base_output.rb b/lib/dino/components/core/base_output.rb index c73d83d8..ca61ab25 100644 --- a/lib/dino/components/core/base_output.rb +++ b/lib/dino/components/core/base_output.rb @@ -21,7 +21,7 @@ def analog_write(value) end def write(value) - unless [Board::LOW, Board::HIGH].include? value + unless [board.low, board.high].include? value analog_write(value) else digital_write(value) @@ -29,15 +29,15 @@ def write(value) end def low - digital_write Board::LOW + digital_write(board.low) end def high - digital_write Board::HIGH + digital_write(board.high) end def toggle - state == Board::LOW ? high : low + state == board.low ? high : low end alias :off :low diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index b0c117c3..43bea08f 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -42,7 +42,7 @@ def handshake line = gets.to_s if line.match /ACK:/ flush_read - return line.chop.split(/:/)[1].to_i + return line.chop.split(/:/)[1] end end rescue diff --git a/spec/lib/board_spec.rb b/spec/lib/board_spec.rb index 968885b8..ea15832e 100644 --- a/spec/lib/board_spec.rb +++ b/spec/lib/board_spec.rb @@ -24,6 +24,14 @@ def io_mock(methods = {}) io_mock.should_receive(:handshake) subject end + + it 'should define the logic properly' do + subject.should_receive(:analog_resolution=).with(8) + + subject.send(:initialize, io_mock) + subject.high.should == 255 + subject.low.should == 0 + end end describe '#update' do @@ -174,12 +182,12 @@ def io_mock(methods = {}) describe '#set_pullup' do it 'should write high if pullup is enabled' do - io_mock.should_receive(:write).with(Dino::Message.encode(command: 1, pin: 13, value: Board::HIGH)) + io_mock.should_receive(:write).with(Dino::Message.encode(command: 1, pin: 13, value: subject.high)) subject.set_pullup(13, true) end it 'should write low if pullup is disabled' do - io_mock.should_receive(:write).with(Dino::Message.encode(command: 1, pin: 13, value: Board::LOW)) + io_mock.should_receive(:write).with(Dino::Message.encode(command: 1, pin: 13, value: subject.low)) subject.set_pullup(13, false) end end diff --git a/spec/lib/components/core/base_input_spec.rb b/spec/lib/components/core/base_input_spec.rb index b6f75c7b..94deb03b 100644 --- a/spec/lib/components/core/base_input_spec.rb +++ b/spec/lib/components/core/base_input_spec.rb @@ -45,7 +45,7 @@ module Core subject.add_callback(:test) { mock } subject.remove_callback(:test) - subject.instance_variable_get(:@callbacks)[:test].should be_nil + subject.instance_variable_get(:@callbacks)[:test].should be_empty end end end @@ -110,7 +110,7 @@ module Core subject.add_callback(:read) { |data| first_block_data = data } subject.update("Some data") - subject.instance_variable_get(:@callbacks)[:read].should be_nil + subject.instance_variable_get(:@callbacks)[:read].should be_empty end end end diff --git a/spec/lib/components/core/base_output_spec.rb b/spec/lib/components/core/base_output_spec.rb index 5f166d10..99c1e5fb 100644 --- a/spec/lib/components/core/base_output_spec.rb +++ b/spec/lib/components/core/base_output_spec.rb @@ -14,7 +14,7 @@ module Core describe '#initialize' do it 'should set mode to out and go low' do board.should_receive(:set_pin_mode).with(14, :out, nil) - board.should_receive(:digital_write).with(14, Board::LOW) + board.should_receive(:digital_write).with(14, board.low) BaseOutput.new(options) end @@ -22,10 +22,10 @@ module Core describe '#digital_write' do it 'should update the @state instance variable and call #digital_write on the board' do - board.should_receive(:digital_write).with(subject.pin, Board::HIGH).once + board.should_receive(:digital_write).with(subject.pin, board.high).once - subject.digital_write(Board::HIGH) - subject.state.should == Board::HIGH + subject.digital_write(board.high) + subject.state.should == board.high end end @@ -40,14 +40,14 @@ module Core describe '#write' do it 'should call #digital_write if value is HIGH' do - subject.should_receive(:digital_write).with(Board::HIGH) - subject.write(Board::HIGH) + subject.should_receive(:digital_write).with(board.high) + subject.write(board.high) end it 'should call #digital_write if value is LOW' do - subject.should_receive(:digital_write).with(Board::LOW) - subject.write(Board::LOW) + subject.should_receive(:digital_write).with(board.low) + subject.write(board.low) end it 'should call #analog_write if value is anything else' do @@ -58,14 +58,14 @@ module Core describe '#high' do it 'should call #digital_write with HIGH' do - subject.should_receive(:digital_write).with(Board::HIGH) + subject.should_receive(:digital_write).with(board.high) subject.high end end describe '#low' do it 'should call #digital_write with LOW' do - subject.should_receive(:digital_write).with(Board::LOW) + subject.should_receive(:digital_write).with(board.low) subject.low end end diff --git a/spec/lib/components/shift_register_spec.rb b/spec/lib/components/shift_register_spec.rb index 7b38bbac..dce07888 100644 --- a/spec/lib/components/shift_register_spec.rb +++ b/spec/lib/components/shift_register_spec.rb @@ -20,18 +20,18 @@ module Components before(:each) { subject } it 'should write a single byte as value and clock pin as aux to the data pin' do - subject.latch.should_receive(:digital_write).with(Board::LOW) + subject.latch.should_receive(:digital_write).with(board.low) board.should_receive(:write).with "11.11.255.12\n" - subject.latch.should_receive(:digital_write).with(Board::HIGH) + subject.latch.should_receive(:digital_write).with(board.high) subject.write(255) end it 'should write an array of bytes as value and clock pin as aux to the data pin' do - subject.latch.should_receive(:digital_write).with(Board::LOW) + subject.latch.should_receive(:digital_write).with(board.low) board.should_receive(:write).with "11.11.255.12\n" board.should_receive(:write).with "11.11.0.12\n" - subject.latch.should_receive(:digital_write).with(Board::HIGH) + subject.latch.should_receive(:digital_write).with(board.high) subject.write([255,0]) end diff --git a/spec/lib/components/ssd_spec.rb b/spec/lib/components/ssd_spec.rb index ac0d90f8..ec39205b 100644 --- a/spec/lib/components/ssd_spec.rb +++ b/spec/lib/components/ssd_spec.rb @@ -41,14 +41,14 @@ module Components describe '#on' do it 'should turn the ssd on' do - subject.anode.should_receive(:digital_write).with(Board::HIGH) + subject.anode.should_receive(:digital_write).with(board.high) subject.on end end describe '#off' do it 'should turn the ssd off' do - subject.anode.should_receive(:digital_write).with(Board::LOW) + subject.anode.should_receive(:digital_write).with(board.low) subject.off end end diff --git a/spec/lib/components/stepper_spec.rb b/spec/lib/components/stepper_spec.rb index a37e1b23..082388a7 100644 --- a/spec/lib/components/stepper_spec.rb +++ b/spec/lib/components/stepper_spec.rb @@ -18,9 +18,9 @@ module Components describe '#step_cc' do it 'should send high to the step pin with the direction pin high' do - subject.direction.should_receive(:digital_write).with(Board::HIGH) - subject.step.should_receive(:digital_write).with(Board::HIGH) - subject.step.should_receive(:digital_write).with(Board::LOW) + subject.direction.should_receive(:digital_write).with(board.high) + subject.step.should_receive(:digital_write).with(board.high) + subject.step.should_receive(:digital_write).with(board.low) subject.step_cc end @@ -28,9 +28,9 @@ module Components describe '#step_cw' do it 'should send high to the board with the direction pin low' do - subject.direction.should_receive(:digital_write).with(Board::LOW) - subject.step.should_receive(:digital_write).with(Board::HIGH) - subject.step.should_receive(:digital_write).with(Board::LOW) + subject.direction.should_receive(:digital_write).with(board.low) + subject.step.should_receive(:digital_write).with(board.high) + subject.step.should_receive(:digital_write).with(board.low) subject.step_cw end diff --git a/src/dino_serial.ino b/src/dino_serial.ino index df4331c7..045a6646 100644 --- a/src/dino_serial.ino +++ b/src/dino_serial.ino @@ -3,8 +3,15 @@ #include Dino dino; +// Use the native serial port on the Arduino Due +#if defined(__SAM3X8E__) + Serial_ serial = SerialUSB; +#else + UARTClass serial = Serial; +#endif + // Dino.h doesn't handle TXRX. Setup a function to tell it to write to Serial. -void writeResponse(char *response) { Serial.print(response); Serial.print("\n"); } +void writeResponse(char *response) { serial.print(response); serial.print("\n"); } void (*writeCallback)(char *str) = writeResponse; void setup() { @@ -13,7 +20,7 @@ void setup() { } void loop() { - while(Serial.available() > 0) dino.parse(Serial.read()); + while(serial.available() > 0) dino.parse(serial.read()); dino.updateListeners(); - Serial.flush(); + serial.flush(); } diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 519d3547..3962f782 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -80,6 +80,7 @@ void Dino::process() { case 10: handleLCD (); break; case 11: shiftWrite (); break; case 90: reset (); break; + case 96: setAnalogResolution (); break; case 97: setAnalogDivider (); break; case 98: setHeartRate (); break; default: break; @@ -275,9 +276,6 @@ void Dino::shiftWrite() { // CMD = 90 void Dino::reset() { - #ifdef ANALOG_RESOLUTION - analogReadResolution(ANALOG_RESOLUTION); - #endif heartRate = 4000; // Default heartRate is ~4ms. loopCount = 0; analogDivider = 4; // Update analog listeners every ~16ms. @@ -287,7 +285,22 @@ void Dino::reset() { lastUpdate = micros(); fragmentIndex = 0; charIndex = 0; - sprintf(response, "ACK:%02d", A0); + + #if defined(__SAM3X8E__) + sprintf(response, "ACK:%d,%d", A0, DAC0); + #else + sprintf(response, "ACK:%d", A0); + #endif +} + +// CMD = 97 +// Set the analog read and write resolution. +void Dino::setAnalogResolution() { + analogReadResolution(val); + analogWriteResolution(val); + #ifdef debug + Serial.print("Analog R/W resolution set to "); Serial.println(val); + #endif } // CMD = 97 diff --git a/src/lib/Dino.h b/src/lib/Dino.h index af07d51c..2b3de085 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -16,7 +16,6 @@ #elif defined(__SAM3X8E__) # define PIN_COUNT 66 # define SERVO_OFFSET 22 -# define ANALOG_RESOLUTION 12 #else # define PIN_COUNT 22 # define SERVO_OFFSET 2 @@ -47,6 +46,7 @@ class Dino { void handleLCD (); //cmd = 10 void shiftWrite (); //cmd = 11 void reset (); //cmd = 90 + void setAnalogResolution (); //cmd = 96 void setAnalogDivider (); //cmd = 97 void setHeartRate (); //cmd = 98 @@ -66,7 +66,7 @@ class Dino { // Value and response storage. int rval; - char response[8]; + char response[16]; // Use a write callback from the main sketch to respond. void (*_writeCallback)(char *str); From 00c2e411775db104257879f7bd22884f8f49d2b4 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Mon, 24 Jun 2013 16:31:35 -0400 Subject: [PATCH 030/296] Fix a typo in the CLI uploader path --- lib/dino_cli/uploader.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dino_cli/uploader.rb b/lib/dino_cli/uploader.rb index d7d4327b..44ce1a16 100644 --- a/lib/dino_cli/uploader.rb +++ b/lib/dino_cli/uploader.rb @@ -28,7 +28,7 @@ def compile end def upload - `#{executable} --upload '#{options[:sketch_file]}'"` + `#{executable} --upload '#{options[:sketch_file]}'` end def executable From f3d3ef4d44946b344292dc6139e9d1f62c8f62d8 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Tue, 25 Jun 2013 12:57:21 -0400 Subject: [PATCH 031/296] Fix sketches not compiling for AVR boards --- src/dino_serial.ino | 2 +- src/lib/Dino.cpp | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/dino_serial.ino b/src/dino_serial.ino index 045a6646..10e8d8ed 100644 --- a/src/dino_serial.ino +++ b/src/dino_serial.ino @@ -7,7 +7,7 @@ Dino dino; #if defined(__SAM3X8E__) Serial_ serial = SerialUSB; #else - UARTClass serial = Serial; + HardwareSerial serial = Serial; #endif // Dino.h doesn't handle TXRX. Setup a function to tell it to write to Serial. diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 3962f782..49c2cabf 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -296,10 +296,12 @@ void Dino::reset() { // CMD = 97 // Set the analog read and write resolution. void Dino::setAnalogResolution() { - analogReadResolution(val); - analogWriteResolution(val); - #ifdef debug - Serial.print("Analog R/W resolution set to "); Serial.println(val); + #if defined(__SAM3X8E__) + analogReadResolution(val); + analogWriteResolution(val); + #ifdef debug + Serial.print("Analog R/W resolution set to "); Serial.println(val); + #endif #endif } From 43a8fe9045b6e9b851d5f1f9a729e9d1ffdd91a7 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Wed, 26 Jun 2013 22:32:43 -0400 Subject: [PATCH 032/296] Simpler more reliable handshake --- lib/dino/tx_rx/base.rb | 23 ++++++++--------------- lib/dino/tx_rx/serial.rb | 8 -------- 2 files changed, 8 insertions(+), 23 deletions(-) diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index 43bea08f..42d65494 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -34,19 +34,12 @@ def write(message) end def handshake - flush_read - 100.times do - begin - write Dino::Message.encode(command: 90) - Timeout::timeout(0.1) do - line = gets.to_s - if line.match /ACK:/ - flush_read - return line.chop.split(/:/)[1] - end - end - rescue - nil + 10.times do + write Dino::Message.encode(command: 90) + line = gets(1) + if line && line.match(/ACK:/) + flush_read + return line.chop.split(/:/)[1] end end raise BoardNotFound @@ -56,8 +49,8 @@ def flush_read gets until gets == nil end - def gets - IO.select([io], nil, nil, 0.005) && io.gets + def gets(timeout=0.005) + IO.select([io], nil, nil, timeout) && io.gets end end end diff --git a/lib/dino/tx_rx/serial.rb b/lib/dino/tx_rx/serial.rb index 8999b70f..c52ff7c0 100644 --- a/lib/dino/tx_rx/serial.rb +++ b/lib/dino/tx_rx/serial.rb @@ -15,14 +15,6 @@ def io @io ||= connect end - def handshake - if on_windows? - io; sleep 3 - end - - super - end - private def connect From c6a731878ad45b6b4f0fcfea65e88536e3495b73 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Mon, 8 Jul 2013 17:08:34 -0400 Subject: [PATCH 033/296] Make BaseInput#read block the main thread, add BaseInput#poll(interval, &block) --- examples/sensor/sensor.rb | 42 ++++++++++++------- lib/dino/board.rb | 1 + lib/dino/components/core/analog_input.rb | 4 +- lib/dino/components/core/base_input.rb | 41 +++++++++++------- lib/dino/components/core/digital_input.rb | 6 +-- lib/dino/tx_rx/base.rb | 1 + spec/lib/board_spec.rb | 9 +++- spec/lib/components/core/analog_input_spec.rb | 8 ++-- spec/lib/components/core/base_input_spec.rb | 28 ++++++++----- .../lib/components/core/digital_input_spec.rb | 10 ++--- 10 files changed, 92 insertions(+), 58 deletions(-) diff --git a/examples/sensor/sensor.rb b/examples/sensor/sensor.rb index 768cfd11..32a6872c 100644 --- a/examples/sensor/sensor.rb +++ b/examples/sensor/sensor.rb @@ -8,35 +8,45 @@ require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) -board.analog_divider = 128 sensor = Dino::Components::Sensor.new(pin: 'A0', board: board) -# Single read with block as callback. Fires only once. -sensor.read { |value| puts "Single read value: #{value}" }; sleep 1 +# Single read with block as callback. Blocks main thread. +# Callback fires only once then is removed automatically. +sensor.read { |value| puts "#{Time.now} Single read: #{value}" } -# Continuous listen with block as callback. Fires every time data is received until #stop_listening is called. -sensor.listen { |value| puts "Listening. Read value: #{value}" }; sleep 5 +# Poll the sensor every 1 second with block as callback. Does not block main thread. +# Callback fires every time data is received until #stop is called. +sensor.poll(1) { |value| puts "#{Time.now} Polling: #{value}" } +sleep 5 + +# Stop polling. Automatically removes the callback from the #poll block. +sensor.stop + +# Continuous listen with block as callback. Fires every time data is received until #stop is called. +sensor.listen { |value| puts "#{Time.now} Listening: #{value}" } +sleep 0.5 # Stop listening. Automatically removes the callback from the #listen block. -sensor.stop_listening; sleep 1 +sensor.stop # Add a persistent callback. -sensor.on_data { |value| puts "Persistent callback: #{value}" } +sensor.on_data { |value| puts "#{Time.now} Persistent callback: #{value}" } -# Add a keyed callback. -sensor.on_data(:test) { |value| puts "Keyed callback: #{value}"} +# Add a callback with a custom key. +sensor.on_data(:test) { |value| puts "#{Time.now} Keyed callback: #{value}"} -# Single read. All callbacks fire, block given fires only once. -sensor.read { |value| puts "#read block. Value: #{value}" }; sleep 1 +# Single read again. Block given fires only once. Callbacks added with #on_data fire also. +sensor.read { |value| puts "#{Time.now} Single read again: #{value}" } -# Continuous listen. All callbacks added with #on_data continue to fire, plus the block. -sensor.listen { |value| puts "#listen block. Value: #{value}" }; sleep 5 +# Continuous listen. Block fires each time. Callbacks added with #on_data continue to fire. +sensor.listen { |value| puts "#{Time.now } Listening again: #{value}" } +sleep 0.5 # Stop listening. Automatically removes the callback from the #listen block. -sensor.stop_listening +sensor.stop # Remove callbacks keyed with :test. -sensor.clear_callbacks(:test) +sensor.remove_callbacks(:test) # Remove all callbacks. -sensor.clear_callbacks +sensor.remove_callbacks diff --git a/lib/dino/board.rb b/lib/dino/board.rb index 5989cb08..eee126cc 100644 --- a/lib/dino/board.rb +++ b/lib/dino/board.rb @@ -53,6 +53,7 @@ def update(pin, msg) end def add_input_hardware(part) + start_read @input_hardware << part set_pin_mode(part.pin, :in, part.pullup) end diff --git a/lib/dino/components/core/analog_input.rb b/lib/dino/components/core/analog_input.rb index ba9b8f51..f15dc361 100644 --- a/lib/dino/components/core/analog_input.rb +++ b/lib/dino/components/core/analog_input.rb @@ -2,11 +2,11 @@ module Dino module Components module Core class AnalogInput < BaseInput - def poll + def _read board.analog_read(self.pin) end - def start_listening + def _listen board.analog_listen(self.pin) end end diff --git a/lib/dino/components/core/base_input.rb b/lib/dino/components/core/base_input.rb index b0092d6b..23b67f0a 100644 --- a/lib/dino/components/core/base_input.rb +++ b/lib/dino/components/core/base_input.rb @@ -2,54 +2,63 @@ module Dino module Components module Core class BaseInput < Base + include Threaded + def initialize(options={}) super options remove_callbacks self.mode = :in board.add_input_hardware(self) - board.start_read after_initialize(options) end def read(&block) add_callback(:read, &block) if block_given? - poll + _read + loop { break if @callbacks[:read].empty? } end def listen(&block) add_callback(:listen, &block) if block_given? - start_listening + _listen + end + + def poll(interval, &block) + add_callback(:poll, &block) if block_given? + threaded_loop do + _read; sleep interval + end + end + + def stop + stop_thread + board.stop_listener(pin) + remove_callback :listen; remove_callback :poll end # - # Define these in your subclass. - # Should correspond to Board#digital_read, Board#digital_listen for digital - # and Board#analog_read, Board#analog_listen for analog components. + # Defined in DigitalInput and AnalogInput subclasses. + # _read corresponds to Board#digital_read and Board#analog_read respectively. + # _listen corresponds to Board#digital_listen and Board#analog_listen respectively # - def poll ; end - def start_listening ; end + def _read; end + def _listen; end def add_callback(key=nil, &block) key ||= :persistent @callbacks[key] ||= [] @callbacks[key] << block end - - alias :on_data :add_callback - + def remove_callback(key=nil) key ? @callbacks[key] = [] : @callbacks = {} end + alias :on_data :add_callback alias :remove_callbacks :remove_callback - def stop_listening - board.stop_listener(pin) - remove_callback :listen - end - def update(data) @state = data @callbacks.each_value do |array| diff --git a/lib/dino/components/core/digital_input.rb b/lib/dino/components/core/digital_input.rb index 74b72a83..da7666c0 100644 --- a/lib/dino/components/core/digital_input.rb +++ b/lib/dino/components/core/digital_input.rb @@ -7,14 +7,14 @@ class DigitalInput < BaseInput def initialize(options={}) super(options) - start_listening + _listen end - def poll + def _read board.digital_read(self.pin) end - def start_listening + def _listen board.digital_listen(self.pin) end diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index 42d65494..9401b47a 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -34,6 +34,7 @@ def write(message) end def handshake + flush_read 10.times do write Dino::Message.encode(command: 90) line = gets(1) diff --git a/spec/lib/board_spec.rb b/spec/lib/board_spec.rb index ea15832e..34fe6ad2 100644 --- a/spec/lib/board_spec.rb +++ b/spec/lib/board_spec.rb @@ -3,7 +3,7 @@ module Dino describe Dino::Board do def io_mock(methods = {}) - @io ||= mock(:io, {write: nil, add_observer: nil, flush_read: nil, handshake: "14"}.merge(methods)) + @io ||= mock(:io, {add_observer: true, handshake: 14, write: true, read: true, write: nil}.merge(methods)) end subject { Board.new(io_mock) } @@ -63,7 +63,12 @@ def io_mock(methods = {}) end describe '#add_input_hardware' do - it 'should add digital hardware to the board' do + it 'should start the read thread' do + io_mock.should_receive(:read) + subject.add_input_hardware(mock(:part1, pin: 12, pullup: nil)) + end + + it 'should add input hardware to the board' do subject.add_input_hardware(mock1 = mock(:part1, pin: 12, pullup: nil)) subject.add_input_hardware(mock2 = mock(:part2, pin: 14, pullup: nil)) subject.input_hardware.should =~ [mock1, mock2] diff --git a/spec/lib/components/core/analog_input_spec.rb b/spec/lib/components/core/analog_input_spec.rb index 6be3c687..582dd847 100644 --- a/spec/lib/components/core/analog_input_spec.rb +++ b/spec/lib/components/core/analog_input_spec.rb @@ -9,17 +9,17 @@ module Core let(:options) { { pin: 'A0', board: board } } subject { AnalogInput.new(options) } - describe '#poll' do + describe '#_read' do it 'should send #analog_read to the board with its pin' do board.should_receive(:analog_read).with(subject.pin) - subject.read + subject._read end end - describe '#start_listening' do + describe '#_listen' do it 'should send #analog_listen to the board with its pin' do board.should_receive(:analog_listen).with(subject.pin) - subject.start_listening + subject._listen end end end diff --git a/spec/lib/components/core/base_input_spec.rb b/spec/lib/components/core/base_input_spec.rb index 94deb03b..d37281ab 100644 --- a/spec/lib/components/core/base_input_spec.rb +++ b/spec/lib/components/core/base_input_spec.rb @@ -13,7 +13,6 @@ module Core it 'should add itself as input hardware, set mode to in and start read' do board.should_receive(:set_pin_mode).with(14, :in, nil) board.should_receive(:add_input_hardware) - board.should_receive(:start_read) BaseInput.new(options) end @@ -54,38 +53,47 @@ module Core describe '#read' do it 'should add the block given as a callback to the :read key' do subject.should_receive(:add_callback).with(:read) + subject.instance_variable_set(:@callbacks, {read: []}) subject.read { mock } end - it 'should call #poll once' do - subject.should_receive(:poll) + it 'should call #_read once' do + subject.should_receive(:read) subject.read end end + describe '#poll' do + it 'should add the block given as a callback to the :poll key' do + subject.should_receive(:add_callback).with(:poll) + subject.poll(5) { mock } + end + end + describe '#listen' do it 'should add the block given as a callback to the :listen key' do subject.should_receive(:add_callback).with(:listen) subject.listen { mock } end - it 'should call #start_listening' do - subject.should_receive(:start_listening) + it 'should call #_listen' do + subject.should_receive(:_listen) subject.listen end end - describe '#stop_listening' do + describe '#stop' do it 'should tell the board to turn the listener off' do board.should_receive(:stop_listener).with(subject.pin) - subject.stop_listening + subject.stop end - it 'should remove all callbacks with the :listen key' do - subject.listen { mock } + it 'should remove all callbacks from the :listen and :poll keys and stop the read thread' do subject.should_receive(:remove_callback).with(:listen) + subject.should_receive(:remove_callback).with(:poll) + subject.should_receive(:stop_thread) - subject.stop_listening + subject.stop end end end diff --git a/spec/lib/components/core/digital_input_spec.rb b/spec/lib/components/core/digital_input_spec.rb index 4a73ca63..684aa844 100644 --- a/spec/lib/components/core/digital_input_spec.rb +++ b/spec/lib/components/core/digital_input_spec.rb @@ -17,20 +17,20 @@ module Core end end - describe '#poll' do + describe '#_read' do it 'should send #digital_read to the board with its pin' do board.should_receive(:digital_read).with(subject.pin) - subject.read + subject._read end end - describe '#start_listening' do + describe '#_listen' do it 'should send #digital_listen to the board with its pin' do subject - subject.stop_listening + subject.stop board.should_receive(:digital_listen).with(subject.pin) - subject.start_listening + subject.listen end end From 2c8e9db8b34d8f6d3dfe0de4efada6ebd1f3e536 Mon Sep 17 00:00:00 2001 From: Christian Carter Date: Sun, 14 Jul 2013 15:53:11 -0400 Subject: [PATCH 034/296] failing attempt to bring softwareserial to dino --- src/dino_serial.ino | 1 + src/lib/Dino.cpp | 11 +++++++++++ src/lib/Dino.h | 2 ++ src/lib/DinoSerial.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ src/lib/DinoSerial.h | 18 ++++++++++++++++++ 5 files changed, 72 insertions(+) create mode 100644 src/lib/DinoSerial.cpp create mode 100644 src/lib/DinoSerial.h diff --git a/src/dino_serial.ino b/src/dino_serial.ino index 10e8d8ed..d75080bc 100644 --- a/src/dino_serial.ino +++ b/src/dino_serial.ino @@ -1,6 +1,7 @@ #include "Dino.h" #include #include +#include Dino dino; // Use the native serial port on the Arduino Due diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 49c2cabf..d6cfdecb 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -5,6 +5,7 @@ #include "Arduino.h" #include "Dino.h" DinoLCD dinoLCD; +DinoSerial dinoSerial; Dino::Dino(){ messageFragments[0] = cmdStr; @@ -79,6 +80,7 @@ void Dino::process() { case 9: servoWrite (); break; case 10: handleLCD (); break; case 11: shiftWrite (); break; + case 12: handleSerial (); break; case 90: reset (); break; case 96: setAnalogResolution (); break; case 97: setAnalogDivider (); break; @@ -274,6 +276,15 @@ void Dino::shiftWrite() { shiftOut(pin, atoi(auxMsg), MSBFIRST, val); } +// CMD = 12 +// Write a value to the servo object. +void Dino::handleSerial() { + #ifdef debug + Serial.print("DinoSerial command: "); Serial.print(val); Serial.print(" with data: "); Serial.println(auxMsg); + #endif + dinoSerial.process(val, auxMsg); +} + // CMD = 90 void Dino::reset() { heartRate = 4000; // Default heartRate is ~4ms. diff --git a/src/lib/Dino.h b/src/lib/Dino.h index 2b3de085..3da7ab96 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -8,6 +8,7 @@ #include "Arduino.h" #include #include "DinoLCD.h" +#include "DinoSerial.h" // Allocate listener storage based on what board we're running. #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) @@ -45,6 +46,7 @@ class Dino { void servoWrite (); //cmd = 9 void handleLCD (); //cmd = 10 void shiftWrite (); //cmd = 11 + void handleSerial (); //cmd = 12 void reset (); //cmd = 90 void setAnalogResolution (); //cmd = 96 void setAnalogDivider (); //cmd = 97 diff --git a/src/lib/DinoSerial.cpp b/src/lib/DinoSerial.cpp new file mode 100644 index 00000000..5c57cf2f --- /dev/null +++ b/src/lib/DinoSerial.cpp @@ -0,0 +1,40 @@ +#include "Arduino.h" +#include "DinoSerial.h" + +SoftwareSerial serial(10,11); + +DinoSerial::DinoSerial(){ +} + +int *DinoSerial::parse(char *aux){ + int *values = new int[11]; + char *str; + int index = 0; + while ((str = strsep(&aux, ",")) != NULL) { + values[index] = atoi(str); + index++; + } + parseSize = index; + return values; +} + +void DinoSerial::process(int cmd, char *message) { + switch(cmd) { + case 0: setPins(message); break; + case 1: beginSerial(message); break; + default: break; + } +} + +void DinoSerial::setPins(char *aux) { + int *pins = parse(aux); + SoftwareSerial newSerial(pins[0],pins[1]); + serial = newSerial; + +} + +void DinoSerial::beginSerial(char *aux) { + int *values = parse(aux); + // set baud rate + serial.begin(values[0]); +} diff --git a/src/lib/DinoSerial.h b/src/lib/DinoSerial.h new file mode 100644 index 00000000..91acc9cf --- /dev/null +++ b/src/lib/DinoSerial.h @@ -0,0 +1,18 @@ +#ifndef DinoSerial_h +#define DinoSerial_h + +#include "Arduino.h" +#include + +class DinoSerial { + public: + DinoSerial(); + void process(int cmd, char *message); + private: + int *parse(char *aux); + void setPins(char *aux); + void beginSerial(char *aux); + int parseSize; +}; + +#endif From 25f1182c4f2ce63bf8cc8f231a7e1cb220991b48 Mon Sep 17 00:00:00 2001 From: Christian Carter Date: Sun, 14 Jul 2013 16:02:13 -0400 Subject: [PATCH 035/296] there's the dummy mistake. --- src/lib/DinoSerial.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/DinoSerial.cpp b/src/lib/DinoSerial.cpp index 5c57cf2f..48c457c7 100644 --- a/src/lib/DinoSerial.cpp +++ b/src/lib/DinoSerial.cpp @@ -1,7 +1,7 @@ #include "Arduino.h" #include "DinoSerial.h" -SoftwareSerial serial(10,11); +SoftwareSerial softSerial(10,11); DinoSerial::DinoSerial(){ } @@ -29,12 +29,12 @@ void DinoSerial::process(int cmd, char *message) { void DinoSerial::setPins(char *aux) { int *pins = parse(aux); SoftwareSerial newSerial(pins[0],pins[1]); - serial = newSerial; + softSerial = newSerial; } void DinoSerial::beginSerial(char *aux) { int *values = parse(aux); // set baud rate - serial.begin(values[0]); + softSerial.begin(values[0]); } From 6bca623425de4226830cd2fec5b56d93f1d4f379 Mon Sep 17 00:00:00 2001 From: Christian Carter Date: Sun, 14 Jul 2013 16:03:36 -0400 Subject: [PATCH 036/296] Ignoring DS_Store files... --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 29a849a7..973343b7 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ spec/reports test/tmp test/version_tmp tmp +*DS_Store* From 760cba4ee20c65d0e143eea801aeb092e1ec902d Mon Sep 17 00:00:00 2001 From: Christian Carter Date: Mon, 15 Jul 2013 12:17:03 -0400 Subject: [PATCH 037/296] add the print and println methods, still untested --- src/lib/DinoSerial.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lib/DinoSerial.cpp b/src/lib/DinoSerial.cpp index 48c457c7..bc6463a3 100644 --- a/src/lib/DinoSerial.cpp +++ b/src/lib/DinoSerial.cpp @@ -20,9 +20,11 @@ int *DinoSerial::parse(char *aux){ void DinoSerial::process(int cmd, char *message) { switch(cmd) { - case 0: setPins(message); break; - case 1: beginSerial(message); break; - default: break; + case 0: setPins(message); break; + case 1: beginSerial(message); break; + case 2: softSerial.print(message); break; + case 3: softserial.println(message); break; + default: break; } } @@ -37,4 +39,4 @@ void DinoSerial::beginSerial(char *aux) { int *values = parse(aux); // set baud rate softSerial.begin(values[0]); -} +} \ No newline at end of file From a32fd5d489981eda20dc6ff30a35c64d7909528d Mon Sep 17 00:00:00 2001 From: Christian Carter Date: Mon, 15 Jul 2013 22:32:09 -0400 Subject: [PATCH 038/296] very preliminary SoftwareSerial writing, and it works. --- lib/dino_cli/generator.rb | 2 +- src/lib/Dino.cpp | 2 +- src/lib/Dino.h | 2 ++ src/lib/DinoSerial.cpp | 12 +++++------- src/lib/DinoSerial.h | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/dino_cli/generator.rb b/lib/dino_cli/generator.rb index fab3e36d..0ab0744e 100644 --- a/lib/dino_cli/generator.rb +++ b/lib/dino_cli/generator.rb @@ -1,6 +1,6 @@ class DinoCLI::Generator require "fileutils" - LIB_FILENAMES = ["Dino.h", "Dino.cpp", "DinoLCD.h", "DinoLCD.cpp"] + LIB_FILENAMES = ["Dino.h", "Dino.cpp", "DinoLCD.h", "DinoLCD.cpp", "DinoSerial.cpp","DinoSerial.h"] attr_accessor :options def initialize(options={}) diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index d6cfdecb..d0aeeea5 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -277,7 +277,7 @@ void Dino::shiftWrite() { } // CMD = 12 -// Write a value to the servo object. +// Control the SoftwareSerial. void Dino::handleSerial() { #ifdef debug Serial.print("DinoSerial command: "); Serial.print(val); Serial.print(" with data: "); Serial.println(auxMsg); diff --git a/src/lib/Dino.h b/src/lib/Dino.h index 3da7ab96..f88b947f 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -5,6 +5,8 @@ #ifndef Dino_h #define Dino_h +#define debug + #include "Arduino.h" #include #include "DinoLCD.h" diff --git a/src/lib/DinoSerial.cpp b/src/lib/DinoSerial.cpp index bc6463a3..8ecffb9d 100644 --- a/src/lib/DinoSerial.cpp +++ b/src/lib/DinoSerial.cpp @@ -21,9 +21,9 @@ int *DinoSerial::parse(char *aux){ void DinoSerial::process(int cmd, char *message) { switch(cmd) { case 0: setPins(message); break; - case 1: beginSerial(message); break; + case 1: begin(message); break; case 2: softSerial.print(message); break; - case 3: softserial.println(message); break; + case 3: softSerial.println(message); break; default: break; } } @@ -32,11 +32,9 @@ void DinoSerial::setPins(char *aux) { int *pins = parse(aux); SoftwareSerial newSerial(pins[0],pins[1]); softSerial = newSerial; - } -void DinoSerial::beginSerial(char *aux) { - int *values = parse(aux); - // set baud rate - softSerial.begin(values[0]); +void DinoSerial::begin(char *aux) { + int baud = atoi(aux); + softSerial.begin(baud); } \ No newline at end of file diff --git a/src/lib/DinoSerial.h b/src/lib/DinoSerial.h index 91acc9cf..a4427767 100644 --- a/src/lib/DinoSerial.h +++ b/src/lib/DinoSerial.h @@ -11,7 +11,7 @@ class DinoSerial { private: int *parse(char *aux); void setPins(char *aux); - void beginSerial(char *aux); + void begin(char *aux); int parseSize; }; From 7d50e3d55ec2bdffc4e3f6b98a99084063d9302c Mon Sep 17 00:00:00 2001 From: Christian Carter Date: Mon, 15 Jul 2013 22:33:11 -0400 Subject: [PATCH 039/296] whoops, didn't need to define debug for the commit --- src/lib/Dino.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/Dino.h b/src/lib/Dino.h index f88b947f..3da7ab96 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -5,8 +5,6 @@ #ifndef Dino_h #define Dino_h -#define debug - #include "Arduino.h" #include #include "DinoLCD.h" From 4805ad26c5035475b8c8d74908fef837de688d91 Mon Sep 17 00:00:00 2001 From: Christian Carter Date: Mon, 15 Jul 2013 22:40:37 -0400 Subject: [PATCH 040/296] initial component class for the softwareserial --- lib/dino/components/softserial.rb | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 lib/dino/components/softserial.rb diff --git a/lib/dino/components/softserial.rb b/lib/dino/components/softserial.rb new file mode 100644 index 00000000..2dc86c4b --- /dev/null +++ b/lib/dino/components/softserial.rb @@ -0,0 +1,39 @@ +module Dino + module Components + class SoftwareSerial < Core::MultiPin + COMMAND = 12 + # Initialize + # + # Dino::Components::SoftwareSerial.new( + # board: board, + # pins: { rx:10, tx:11 }, + # baud: 9600 + # ) + # + def after_initialize(options) + board.write Dino::Message.encode command: COMMAND, value: 0, aux_message: encoded_pins + board.write Dino::Message.encode command: COMMAND, value: 1, aux_message: baud + end + + # A useful pattern to be implemented if needed + # LIBRARY_COMMANDS = { + # } + + # LIBRARY_COMMANDS.each_pair do |command, command_id| + # define_method(command) do + # board.write Dino::Message.encode(command: 13, value: command_id) + # end + # end + + def puts(string) + board.write Dino::Message.encode command: COMMAND, value: 3, aux_message: string + end + + private + + def encoded_pins + [pins[:rx], pins[:tx]].compact.join(',') + end + end + end +end From 1aefab5d1100cc68f94f11920badc8c2253c4b3f Mon Sep 17 00:00:00 2001 From: Christian Carter Date: Mon, 15 Jul 2013 22:44:56 -0400 Subject: [PATCH 041/296] autoload the serial --- lib/dino/components.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/dino/components.rb b/lib/dino/components.rb index 6ff4354a..cd3d0ca4 100644 --- a/lib/dino/components.rb +++ b/lib/dino/components.rb @@ -12,5 +12,6 @@ module Components autoload :LCD, 'dino/components/lcd' autoload :ShiftRegister, 'dino/components/shift_register' autoload :Relay, 'dino/components/relay' + autoload :SoftwareSerial, 'dino/components/softserial' end end From 5f7b9502727943da97e8d47327ea614e5b955ede Mon Sep 17 00:00:00 2001 From: Christian Carter Date: Mon, 15 Jul 2013 22:45:10 -0400 Subject: [PATCH 042/296] softwareserial should track the baud --- lib/dino/components/softserial.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/dino/components/softserial.rb b/lib/dino/components/softserial.rb index 2dc86c4b..d0906929 100644 --- a/lib/dino/components/softserial.rb +++ b/lib/dino/components/softserial.rb @@ -1,6 +1,8 @@ module Dino module Components class SoftwareSerial < Core::MultiPin + attr_accessor :baud + COMMAND = 12 # Initialize # @@ -11,8 +13,9 @@ class SoftwareSerial < Core::MultiPin # ) # def after_initialize(options) + self.baud = options[:baud] board.write Dino::Message.encode command: COMMAND, value: 0, aux_message: encoded_pins - board.write Dino::Message.encode command: COMMAND, value: 1, aux_message: baud + board.write Dino::Message.encode command: COMMAND, value: 1, aux_message: self.baud end # A useful pattern to be implemented if needed From 0558f2cc44beefa8507371cad54bcb2d53ab68f2 Mon Sep 17 00:00:00 2001 From: Christian Carter Date: Mon, 15 Jul 2013 22:52:57 -0400 Subject: [PATCH 043/296] a spec for the current level of detail softserial has --- spec/lib/components/softwareserial_spec.rb | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 spec/lib/components/softwareserial_spec.rb diff --git a/spec/lib/components/softwareserial_spec.rb b/spec/lib/components/softwareserial_spec.rb new file mode 100644 index 00000000..f318a74f --- /dev/null +++ b/spec/lib/components/softwareserial_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +module Dino + module Components + describe SoftwareSerial do + let(:board) { mock(:board, digital_write: true, set_pin_mode: true) } + + subject { SoftwareSerial.new board: board, pins: { rx: 10, tx: 11 }, baud: 4800 } + + before do + board.should_receive(:write).with("12..0.10,11\n") + board.should_receive(:write).with("12..1.4800\n") + end + + describe '#puts' do + it 'prints a string to the serial interface' do + board.should_receive(:write).with "12..3.Testing\n" + subject.puts("Testing") + end + end + end + end +end \ No newline at end of file From 0891d2e89eb3b6d8183b6d6106b768a2e6967713 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Sun, 18 Aug 2013 19:19:03 -0400 Subject: [PATCH 044/296] Don't depend on rbenv --- .ruby-version | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .ruby-version diff --git a/.ruby-version b/.ruby-version deleted file mode 100644 index 95a5ad2d..00000000 --- a/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -ruby-2.0.0-p195 From b020a37810ca6eff279d59d301d4afde2b3875ab Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Sun, 18 Aug 2013 19:41:58 -0400 Subject: [PATCH 045/296] Update readme for 0.12 --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c66e0c16..588dcfe3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Dino 0.11.2 +# Dino 0.12.0 [![Build Status](https://secure.travis-ci.org/austinbv/dino.png)](http://travis-ci.org/austinbv/dino) ## Get Started In No Time @@ -11,6 +11,14 @@ Dino lets you start programming your Arduino with Ruby in minutes. gem install dino ``` +#### Download the Arduino IDE + +Dino is all about writing Ruby, but we'll need to upload some C code to the Arduino so we can talk to it. You'll need the [Arduino IDE](http://arduino.cc/en/Main/Software) for that. Even though it's still in beta, version 1.5 is recommended since it lets you upload from the command line. + +#### Connect your Arduino + +Connect your board with a USB cable. Open the Arduino IDE and pull down the `Tools` menu. Make sure you have the right type of board, and the correct serial port selected. + #### Prepare the Bootstrapper Use the included command line tool to create a folder with the Arduino sketch you want to use and optionally configure it. From bb51629d08b68bde77480eb5aa81acb254c74441 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Fri, 6 Sep 2013 22:32:15 -0400 Subject: [PATCH 046/296] Untested support for DHT11/DHT22 sensors --- lib/dino/board.rb | 3 +- lib/dino/components.rb | 1 + lib/dino/components/dht.rb | 39 +++++++ lib/dino_cli/generator.rb | 2 +- src/dino_ethernet.ino | 11 +- src/dino_serial.ino | 3 +- src/dino_wifi.ino | 11 +- src/lib/DHT.cpp | 207 +++++++++++++++++++++++++++++++++++++ src/lib/DHT.h | 96 +++++++++++++++++ src/lib/Dino.cpp | 28 ++++- src/lib/Dino.h | 2 + 11 files changed, 384 insertions(+), 19 deletions(-) create mode 100644 lib/dino/components/dht.rb create mode 100644 src/lib/DHT.cpp create mode 100644 src/lib/DHT.h diff --git a/lib/dino/board.rb b/lib/dino/board.rb index eee126cc..60a8fe45 100644 --- a/lib/dino/board.rb +++ b/lib/dino/board.rb @@ -83,7 +83,8 @@ def set_pullup(pin, pullup) analog_listen: '6', stop_listener: '7', servo_toggle: '8', - servo_write: '9' + servo_write: '9', + dht_read: '13' } PIN_COMMANDS.each_key do |command| diff --git a/lib/dino/components.rb b/lib/dino/components.rb index cd3d0ca4..1baf331d 100644 --- a/lib/dino/components.rb +++ b/lib/dino/components.rb @@ -13,5 +13,6 @@ module Components autoload :ShiftRegister, 'dino/components/shift_register' autoload :Relay, 'dino/components/relay' autoload :SoftwareSerial, 'dino/components/softserial' + autoload :SoftwareSerial, 'dino/components/dht' end end diff --git a/lib/dino/components/dht.rb b/lib/dino/components/dht.rb new file mode 100644 index 00000000..69a0e1ff --- /dev/null +++ b/lib/dino/components/dht.rb @@ -0,0 +1,39 @@ +module Dino + module Components + module DHT + class Temperature < Core::BaseInput + # + # These components work too slowly to poll often enough for a listener. + # + def listen; end + + def _read + board.dht_read(self.pin, 0) + end + + def update(data) + return unless data.match /T/ + data.gsub!('T', '') + super(data) + end + end + + class Humidity < Core::BaseInput + # + # These components poll too slowly to poll often enough for a listener. + # + def listen; end + + def _read + board.dht_read(self.pin, 1) + end + + def update + return unless data.match /H/ + data.gsub!('T', '') + super(data) + end + end + end + end +end diff --git a/lib/dino_cli/generator.rb b/lib/dino_cli/generator.rb index 0ab0744e..b5a234fc 100644 --- a/lib/dino_cli/generator.rb +++ b/lib/dino_cli/generator.rb @@ -1,6 +1,6 @@ class DinoCLI::Generator require "fileutils" - LIB_FILENAMES = ["Dino.h", "Dino.cpp", "DinoLCD.h", "DinoLCD.cpp", "DinoSerial.cpp","DinoSerial.h"] + LIB_FILENAMES = ["Dino.h", "Dino.cpp", "DinoLCD.h", "DinoLCD.cpp", "DinoSerial.cpp","DinoSerial.h", "DHT.cpp", "DHT.h"] attr_accessor :options def initialize(options={}) diff --git a/src/dino_ethernet.ino b/src/dino_ethernet.ino index a63bd40a..d909e99a 100644 --- a/src/dino_ethernet.ino +++ b/src/dino_ethernet.ino @@ -3,6 +3,8 @@ #include #include #include +#include +#include "DHT.h" // Configure your MAC address, IP address, and HTTP port here. byte mac[] = { 0xDE, 0xAD, 0xBE, 0x30, 0x31, 0x32 }; @@ -18,13 +20,8 @@ char responseBuffer[65]; // Dino.h doesn't handle TXRX. // Setup a callback to buffer responses for writing. void bufferResponse(char *response) { - if (strlen(responseBuffer) > 56 ) { - writeResponses(); - strcpy(responseBuffer, response); - } else { - strcat(responseBuffer, response); - } - strcat(responseBuffer, "\n"); + if (strlen(responseBuffer) > 56 ) writeResponses(); + strcpy(responseBuffer, response); } void (*writeCallback)(char *str) = bufferResponse; diff --git a/src/dino_serial.ino b/src/dino_serial.ino index d75080bc..c9a92e6e 100644 --- a/src/dino_serial.ino +++ b/src/dino_serial.ino @@ -2,6 +2,7 @@ #include #include #include +#include "DHT.h" Dino dino; // Use the native serial port on the Arduino Due @@ -12,7 +13,7 @@ Dino dino; #endif // Dino.h doesn't handle TXRX. Setup a function to tell it to write to Serial. -void writeResponse(char *response) { serial.print(response); serial.print("\n"); } +void writeResponse(char *response) { serial.print(response); } void (*writeCallback)(char *str) = writeResponse; void setup() { diff --git a/src/dino_wifi.ino b/src/dino_wifi.ino index e9ebfc0d..9387e85b 100644 --- a/src/dino_wifi.ino +++ b/src/dino_wifi.ino @@ -3,6 +3,8 @@ #include #include #include +#include +#include "DHT.h" // Configure your WiFi options here. MAC address and IP address are not configurable. int port = 3466; @@ -20,13 +22,8 @@ char responseBuffer[65]; // Dino.h doesn't handle TXRX. // Setup a callback to buffer responses for writing. void bufferResponse(char *response) { - if (strlen(responseBuffer) > 56 ) { - writeResponses(); - strcpy(responseBuffer, response); - } else { - strcat(responseBuffer, response); - } - strcat(responseBuffer, "\n"); + if (strlen(responseBuffer) > 56 ) writeResponses(); + strcpy(responseBuffer, response); } void (*writeCallback)(char *str) = bufferResponse; diff --git a/src/lib/DHT.cpp b/src/lib/DHT.cpp new file mode 100644 index 00000000..90727246 --- /dev/null +++ b/src/lib/DHT.cpp @@ -0,0 +1,207 @@ +/****************************************************************** + DHT Temperature & Humidity Sensor library for Arduino. + + Features: + - Support for DHT11 and DHT22/AM2302/RHT03 + - Auto detect sensor model + - Very low memory footprint + - Very small code + + http://www.github.com/markruys/arduino-DHT + + Written by Mark Ruys, mark@paracas.nl. + + BSD license, check license.txt for more information. + All text above must be included in any redistribution. + + Datasheets: + - http://www.micro4you.com/files/sensor/DHT11.pdf + - http://www.adafruit.com/datasheets/DHT22.pdf + - http://dlnmh9ip6v2uc.cloudfront.net/datasheets/Sensors/Weather/RHT03.pdf + - http://meteobox.tk/files/AM2302.pdf + + Changelog: + 2013-06-10: Initial version + 2013-06-12: Refactored code + 2013-07-01: Add a resetTimer method + ******************************************************************/ + +#include "DHT.h" + +void DHT::setup(uint8_t pin, DHT_MODEL_t model) +{ + DHT::pin = pin; + DHT::model = model; + DHT::resetTimer(); // Make sure we do read the sensor in the next readSensor() + + if ( model == AUTO_DETECT) { + DHT::model = DHT22; + readSensor(); + if ( error == ERROR_TIMEOUT ) { + DHT::model = DHT11; + delay(1000); + // Warning: in case we auto detect a DHT11, you should wait at least 1000 msec + // before your first read request. Otherwise you will get a time out error. + } + } +} + +void DHT::resetTimer() +{ + DHT::lastReadTime = millis() - 3000; +} + +float DHT::getHumidity() +{ + readSensor(); + return humidity; +} + +float DHT::getTemperature() +{ + readSensor(); + return temperature; +} + +#ifndef OPTIMIZE_SRAM_SIZE + +const char* DHT::getStatusString() +{ + switch ( error ) { + case DHT::ERROR_TIMEOUT: + return "TIMEOUT"; + + case DHT::ERROR_CHECKSUM: + return "CHECKSUM"; + + default: + return "OK"; + } +} + +#else + +// At the expense of 26 bytes of extra PROGMEM, we save 11 bytes of +// SRAM by using the following method: + +prog_char P_OK[] PROGMEM = "OK"; +prog_char P_TIMEOUT[] PROGMEM = "TIMEOUT"; +prog_char P_CHECKSUM[] PROGMEM = "CHECKSUM"; + +const char *DHT::getStatusString() { + prog_char *c; + switch ( error ) { + case DHT::ERROR_CHECKSUM: + c = P_CHECKSUM; break; + + case DHT::ERROR_TIMEOUT: + c = P_TIMEOUT; break; + + default: + c = P_OK; break; + } + + static char buffer[9]; + strcpy_P(buffer, c); + + return buffer; +} + +#endif + +void DHT::readSensor() +{ + // Make sure we don't poll the sensor too often + // - Max sample rate DHT11 is 1 Hz (duty cicle 1000 ms) + // - Max sample rate DHT22 is 0.5 Hz (duty cicle 2000 ms) + unsigned long startTime = millis(); + if ( (unsigned long)(startTime - lastReadTime) < (model == DHT11 ? 999L : 1999L) ) { + return; + } + lastReadTime = startTime; + + temperature = NAN; + humidity = NAN; + + // Request sample + + digitalWrite(pin, LOW); // Send start signal + pinMode(pin, OUTPUT); + if ( model == DHT11 ) { + delay(18); + } + else { + // This will fail for a DHT11 - that's how we can detect such a device + delayMicroseconds(800); + } + + pinMode(pin, INPUT); + digitalWrite(pin, HIGH); // Switch bus to receive data + + // We're going to read 83 edges: + // - First a FALLING, RISING, and FALLING edge for the start bit + // - Then 40 bits: RISING and then a FALLING edge per bit + // To keep our code simple, we accept any HIGH or LOW reading if it's max 85 usecs long + + word rawHumidity; + word rawTemperature; + word data; + + for ( int8_t i = -3 ; i < 2 * 40; i++ ) { + byte age; + startTime = micros(); + + do { + age = (unsigned long)(micros() - startTime); + if ( age > 90 ) { + error = ERROR_TIMEOUT; + return; + } + } + while ( digitalRead(pin) == (i & 1) ? HIGH : LOW ); + + if ( i >= 0 && (i & 1) ) { + // Now we are being fed our 40 bits + data <<= 1; + + // A zero max 30 usecs, a one at least 68 usecs. + if ( age > 30 ) { + data |= 1; // we got a one + } + } + + switch ( i ) { + case 31: + rawHumidity = data; + break; + case 63: + rawTemperature = data; + data = 0; + break; + } + } + + // Verify checksum + + if ( (byte)(((byte)rawHumidity) + (rawHumidity >> 8) + ((byte)rawTemperature) + (rawTemperature >> 8)) != data ) { + error = ERROR_CHECKSUM; + return; + } + + // Store readings + + if ( model == DHT11 ) { + humidity = rawHumidity >> 8; + temperature = rawTemperature >> 8; + } + else { + humidity = rawHumidity * 0.1; + + if ( rawTemperature & 0x8000 ) { + rawTemperature = -(int16_t)(rawTemperature & 0x7FFF); + } + temperature = ((int16_t)rawTemperature) * 0.1; + } + + error = ERROR_NONE; +} \ No newline at end of file diff --git a/src/lib/DHT.h b/src/lib/DHT.h new file mode 100644 index 00000000..bf6113a0 --- /dev/null +++ b/src/lib/DHT.h @@ -0,0 +1,96 @@ +/****************************************************************** + DHT Temperature & Humidity Sensor library for Arduino. + + Features: + - Support for DHT11 and DHT22/AM2302/RHT03 + - Auto detect sensor model + - Very low memory footprint + - Very small code + + http://www.github.com/markruys/arduino-DHT + + Written by Mark Ruys, mark@paracas.nl. + + BSD license, check license.txt for more information. + All text above must be included in any redistribution. + + Datasheets: + - http://www.micro4you.com/files/sensor/DHT11.pdf + - http://www.adafruit.com/datasheets/DHT22.pdf + - http://dlnmh9ip6v2uc.cloudfront.net/datasheets/Sensors/Weather/RHT03.pdf + - http://meteobox.tk/files/AM2302.pdf + + Changelog: + 2013-06-10: Initial version + 2013-06-12: Refactored code + 2013-07-01: Add a resetTimer method + ******************************************************************/ + +#ifndef dht_h +#define dht_h + +#if ARDUINO < 100 + #include +#else + #include +#endif + +class DHT +{ +public: + + typedef enum { + AUTO_DETECT, + DHT11, + DHT22, + AM2302, // Packaged DHT22 + RHT03 // Equivalent to DHT22 + } + DHT_MODEL_t; + + typedef enum { + ERROR_NONE = 0, + ERROR_TIMEOUT, + ERROR_CHECKSUM + } + DHT_ERROR_t; + + void setup(uint8_t pin, DHT_MODEL_t model=AUTO_DETECT); + void resetTimer(); + + float getTemperature(); + float getHumidity(); + + DHT_ERROR_t getStatus() { return error; }; + const char* getStatusString(); + + DHT_MODEL_t getModel() { return model; } + + int getMinimumSamplingPeriod() { return model == DHT11 ? 1000 : 2000; } + + int8_t getNumberOfDecimalsTemperature() { return model == DHT11 ? 0 : 1; }; + int8_t getLowerBoundTemperature() { return model == DHT11 ? 0 : -40; }; + int8_t getUpperBoundTemperature() { return model == DHT11 ? 50 : 125; }; + + int8_t getNumberOfDecimalsHumidity() { return 0; }; + int8_t getLowerBoundHumidity() { return model == DHT11 ? 20 : 0; }; + int8_t getUpperBoundHumidity() { return model == DHT11 ? 90 : 100; }; + + static float toFahrenheit(float fromCelcius) { return 1.8 * fromCelcius + 32.0; }; + static float toCelsius(float fromFahrenheit) { return (fromFahrenheit - 32.0) / 1.8; }; + + uint8_t pin; + +protected: + void readSensor(); + + float temperature; + float humidity; + +private: + DHT_MODEL_t model; + DHT_ERROR_t error; + unsigned long lastReadTime; +}; + +#endif /*dht_h*/ \ No newline at end of file diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index d0aeeea5..135952c1 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -6,6 +6,7 @@ #include "Dino.h" DinoLCD dinoLCD; DinoSerial dinoSerial; +DHT dht; Dino::Dino(){ messageFragments[0] = cmdStr; @@ -81,6 +82,7 @@ void Dino::process() { case 10: handleLCD (); break; case 11: shiftWrite (); break; case 12: handleSerial (); break; + case 13: handleDHT (); break; case 90: reset (); break; case 96: setAnalogResolution (); break; case 97: setAnalogDivider (); break; @@ -105,10 +107,9 @@ void Dino::setupWrite(void (*writeCallback)(char *str)) { } void Dino::writeResponse() { _writeCallback(response); + _writeCallback("\n"); } - - // LISTNENERS void Dino::updateListeners() { if (timeSince(lastUpdate) > heartRate || timeSince(lastUpdate) < 0) { @@ -285,6 +286,29 @@ void Dino::handleSerial() { dinoSerial.process(val, auxMsg); } +// CMD = 13 +// Read a DHT sensor +void Dino::handleDHT() { + #ifdef debug + Serial.print("DinoDHT command: "); Serial.print(val); Serial.print(" with data: "); Serial.println(auxMsg); + #endif + if (pin != dht.pin) dht.setup(pin); + float reading; + char readingBuff[10]; + char prefix; + if (val == 0) { + reading = dht.getTemperature(); + prefix = 'T'; + } else { + reading = dht.getHumidity(); + prefix = 'H'; + } + if (! isnan(reading)) { + dtostrf(reading, 6, 4, readingBuff); + sprintf(response, "%d:%c%s", pin, prefix, readingBuff); + } +} + // CMD = 90 void Dino::reset() { heartRate = 4000; // Default heartRate is ~4ms. diff --git a/src/lib/Dino.h b/src/lib/Dino.h index 3da7ab96..cbd769e0 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -9,6 +9,7 @@ #include #include "DinoLCD.h" #include "DinoSerial.h" +#include "DHT.h" // Allocate listener storage based on what board we're running. #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) @@ -47,6 +48,7 @@ class Dino { void handleLCD (); //cmd = 10 void shiftWrite (); //cmd = 11 void handleSerial (); //cmd = 12 + void handleDHT (); //cmd = 13 void reset (); //cmd = 90 void setAnalogResolution (); //cmd = 96 void setAnalogDivider (); //cmd = 97 From eb997eef83fec44ab6d0d2cb9eca114e7b4e5cc5 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Fri, 6 Sep 2013 22:56:43 -0400 Subject: [PATCH 047/296] Fix stupid mistake --- lib/dino/components.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dino/components.rb b/lib/dino/components.rb index 1baf331d..301bd978 100644 --- a/lib/dino/components.rb +++ b/lib/dino/components.rb @@ -13,6 +13,6 @@ module Components autoload :ShiftRegister, 'dino/components/shift_register' autoload :Relay, 'dino/components/relay' autoload :SoftwareSerial, 'dino/components/softserial' - autoload :SoftwareSerial, 'dino/components/dht' + autoload :DHT, 'dino/components/dht' end end From 3b4422de2c457b7e17802e784eecf25d19a02e64 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Tue, 8 Oct 2013 23:52:58 -0400 Subject: [PATCH 048/296] Fix DHT sensor class and add an example. --- examples/dht/dht.rb | 20 ++++++++++++++++++++ lib/dino/components/dht.rb | 10 +++++----- 2 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 examples/dht/dht.rb diff --git a/examples/dht/dht.rb b/examples/dht/dht.rb new file mode 100644 index 00000000..dc556e03 --- /dev/null +++ b/examples/dht/dht.rb @@ -0,0 +1,20 @@ +# +# Example of how to use the DHT class for DHT 11 and DHT 22 sensors. +# +require 'bundler/setup' +require 'dino' + +board = Dino::Board.new(Dino::TxRx::Serial.new) + +# The temperature and humidity functions of the DHT sensors are +# modelled separately, but both isntances can be set up on the same pin. +temp = Dino::Components::DHT::Temperature.new(pin: 4, board: board) +humidity = Dino::Components::DHT::Humidity.new(pin: 4, board: board) + +temp.read do |temperature| + puts "The temperature is #{temperature} degrees C" +end + +humidity.read do |humidity| + puts "The relative humidity is #{humidity}%" +end diff --git a/lib/dino/components/dht.rb b/lib/dino/components/dht.rb index 69a0e1ff..aa53543d 100644 --- a/lib/dino/components/dht.rb +++ b/lib/dino/components/dht.rb @@ -14,13 +14,13 @@ def _read def update(data) return unless data.match /T/ data.gsub!('T', '') - super(data) + super(data.to_f) end end class Humidity < Core::BaseInput # - # These components poll too slowly to poll often enough for a listener. + # These components work too slowly to poll often enough for a listener. # def listen; end @@ -28,10 +28,10 @@ def _read board.dht_read(self.pin, 1) end - def update + def update(data) return unless data.match /H/ - data.gsub!('T', '') - super(data) + data.gsub!('H', '') + super(data.to_f) end end end From 273272c592d0966bddbe72c67997b71649a0b51c Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Tue, 24 Dec 2013 23:26:31 -0400 Subject: [PATCH 049/296] =?UTF-8?q?Leonardo=20support.=20Closes=20#65.=20T?= =?UTF-8?q?emporarily=20disable=20things=20that=20don=E2=80=99t=20work=20o?= =?UTF-8?q?n=20Due.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/dino_serial.ino | 27 ++++++++++++++++++++------- src/lib/Dino.cpp | 15 ++++++++++++++- src/lib/Dino.h | 7 ++++++- src/lib/DinoSerial.cpp | 7 ++++++- src/lib/DinoSerial.h | 5 +++++ 5 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/dino_serial.ino b/src/dino_serial.ino index c9a92e6e..f3edb943 100644 --- a/src/dino_serial.ino +++ b/src/dino_serial.ino @@ -1,28 +1,41 @@ #include "Dino.h" #include #include -#include #include "DHT.h" + +// SoftwareSerial doesn't work on the Due yet. +#if !defined(__SAM3X8E__) + #include +#endif + Dino dino; -// Use the native serial port on the Arduino Due +// Use 'serial' to reference the right interface depending on the device. +// Uses native USB connection on the Due by default. #if defined(__SAM3X8E__) - Serial_ serial = SerialUSB; + Serial_ &serial = SerialUSB; +#elif defined(__AVR_ATmega32U4__) + Serial_ &serial = Serial; #else - HardwareSerial serial = Serial; + HardwareSerial &serial = Serial; #endif -// Dino.h doesn't handle TXRX. Setup a function to tell it to write to Serial. +// Dino.h doesn't handle TXRX. Create a callback so it can write to serial. void writeResponse(char *response) { serial.print(response); } void (*writeCallback)(char *str) = writeResponse; void setup() { - Serial.begin(115200); + serial.begin(115200); + + // Wait for Leonardo serial port to connect. + #if defined(__AVR_ATmega32U4__) + while(!serial); + #endif + dino.setupWrite(writeCallback); } void loop() { while(serial.available() > 0) dino.parse(serial.read()); dino.updateListeners(); - serial.flush(); } diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 135952c1..eace6ba6 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -5,8 +5,12 @@ #include "Arduino.h" #include "Dino.h" DinoLCD dinoLCD; -DinoSerial dinoSerial; DHT dht; +// SoftwareSerial doesn't work on the Due yet. +#if !defined(__SAM3X8E__) + DinoSerial dinoSerial; +#endif + Dino::Dino(){ messageFragments[0] = cmdStr; @@ -277,21 +281,28 @@ void Dino::shiftWrite() { shiftOut(pin, atoi(auxMsg), MSBFIRST, val); } + // CMD = 12 // Control the SoftwareSerial. void Dino::handleSerial() { #ifdef debug Serial.print("DinoSerial command: "); Serial.print(val); Serial.print(" with data: "); Serial.println(auxMsg); #endif + // SoftwareSerial doesn't work on the Due yet. + #if !defined(__SAM3X8E__) dinoSerial.process(val, auxMsg); + #endif } + // CMD = 13 // Read a DHT sensor void Dino::handleDHT() { #ifdef debug Serial.print("DinoDHT command: "); Serial.print(val); Serial.print(" with data: "); Serial.println(auxMsg); #endif + // dtostrf doesn't work on the Due yet. + #if !defined(__SAM3X8E__) if (pin != dht.pin) dht.setup(pin); float reading; char readingBuff[10]; @@ -307,8 +318,10 @@ void Dino::handleDHT() { dtostrf(reading, 6, 4, readingBuff); sprintf(response, "%d:%c%s", pin, prefix, readingBuff); } + #endif } + // CMD = 90 void Dino::reset() { heartRate = 4000; // Default heartRate is ~4ms. diff --git a/src/lib/Dino.h b/src/lib/Dino.h index cbd769e0..7164eb21 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -8,9 +8,14 @@ #include "Arduino.h" #include #include "DinoLCD.h" -#include "DinoSerial.h" #include "DHT.h" +// SoftwareSerial doesn't work on the Due yet. +#if !defined(__SAM3X8E__) + #include "DinoSerial.h" +#endif + + // Allocate listener storage based on what board we're running. #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) # define PIN_COUNT 70 diff --git a/src/lib/DinoSerial.cpp b/src/lib/DinoSerial.cpp index 8ecffb9d..068344b7 100644 --- a/src/lib/DinoSerial.cpp +++ b/src/lib/DinoSerial.cpp @@ -1,3 +1,6 @@ +// SoftwareSerial doesn't work on the Due yet. +#if !defined(__SAM3X8E__) + #include "Arduino.h" #include "DinoSerial.h" @@ -37,4 +40,6 @@ void DinoSerial::setPins(char *aux) { void DinoSerial::begin(char *aux) { int baud = atoi(aux); softSerial.begin(baud); -} \ No newline at end of file +} + +#endif diff --git a/src/lib/DinoSerial.h b/src/lib/DinoSerial.h index a4427767..5160bc72 100644 --- a/src/lib/DinoSerial.h +++ b/src/lib/DinoSerial.h @@ -1,3 +1,6 @@ +// SoftwareSerial doesn't work on the Due yet. +#if !defined(__SAM3X8E__) + #ifndef DinoSerial_h #define DinoSerial_h @@ -16,3 +19,5 @@ class DinoSerial { }; #endif + +#endif From 6da2977c8d6e48fb0b1c336034344b339d50956b Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Tue, 24 Dec 2013 23:41:07 -0400 Subject: [PATCH 050/296] Disable software serial for Due on ethernet and wifi --- src/dino_ethernet.ino | 6 +++++- src/dino_wifi.ino | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/dino_ethernet.ino b/src/dino_ethernet.ino index d909e99a..1091c5db 100644 --- a/src/dino_ethernet.ino +++ b/src/dino_ethernet.ino @@ -3,9 +3,13 @@ #include #include #include -#include #include "DHT.h" +// SoftwareSerial doesn't work on the Due yet. +#if !defined(__SAM3X8E__) + #include +#endif + // Configure your MAC address, IP address, and HTTP port here. byte mac[] = { 0xDE, 0xAD, 0xBE, 0x30, 0x31, 0x32 }; IPAddress ip(192,168,0,77); diff --git a/src/dino_wifi.ino b/src/dino_wifi.ino index 9387e85b..50a16d3a 100644 --- a/src/dino_wifi.ino +++ b/src/dino_wifi.ino @@ -3,9 +3,13 @@ #include #include #include -#include #include "DHT.h" +// SoftwareSerial doesn't work on the Due yet. +#if !defined(__SAM3X8E__) + #include +#endif + // Configure your WiFi options here. MAC address and IP address are not configurable. int port = 3466; char ssid [] = "yourNetwork"; From 0ae5261e137c18a7925f131c1d7941e5e2b7a21c Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Thu, 26 Dec 2013 15:39:11 -0400 Subject: [PATCH 051/296] =?UTF-8?q?Refactor=20all=20the=20components?= =?UTF-8?q?=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dino/board.rb | 21 ++- lib/dino/components.rb | 4 +- lib/dino/components/basic.rb | 10 ++ lib/dino/components/basic/analog_input.rb | 21 +++ lib/dino/components/basic/analog_output.rb | 21 +++ .../{core => basic}/digital_input.rb | 17 ++- lib/dino/components/basic/digital_output.rb | 37 +++++ lib/dino/components/button.rb | 4 +- lib/dino/components/core.rb | 13 -- lib/dino/components/core/analog_input.rb | 15 --- lib/dino/components/core/base.rb | 41 ------ lib/dino/components/core/base_input.rb | 72 ---------- lib/dino/components/core/base_output.rb | 48 ------- lib/dino/components/core/multi_pin.rb | 65 --------- lib/dino/components/ir_receiver.rb | 9 +- lib/dino/components/lcd.rb | 4 +- lib/dino/components/led.rb | 2 +- lib/dino/components/mixins.rb | 11 ++ lib/dino/components/mixins/callbacks.rb | 33 +++++ lib/dino/components/mixins/listener.rb | 26 ++++ lib/dino/components/mixins/poller.rb | 29 ++++ lib/dino/components/mixins/reader.rb | 20 +++ .../components/{core => mixins}/threaded.rb | 12 +- lib/dino/components/relay.rb | 2 +- lib/dino/components/rgb_led.rb | 15 +-- lib/dino/components/sensor.rb | 2 +- lib/dino/components/servo.rb | 9 +- lib/dino/components/setup.rb | 11 ++ lib/dino/components/setup/base.rb | 48 +++++++ lib/dino/components/setup/input.rb | 23 ++++ lib/dino/components/setup/multi_pin.rb | 93 +++++++++++++ lib/dino/components/setup/output.rb | 14 ++ lib/dino/components/setup/single_pin.rb | 27 ++++ lib/dino/components/shift_register.rb | 13 +- lib/dino/components/softserial.rb | 3 +- lib/dino/components/ssd.rb | 97 ++++++------- lib/dino/components/stepper.rb | 12 +- spec/lib/board_spec.rb | 57 +++----- .../{core => basic}/analog_input_spec.rb | 2 +- .../{core => basic}/digital_input_spec.rb | 5 +- spec/lib/components/button_spec.rb | 8 -- spec/lib/components/core/base_input_spec.rb | 127 ------------------ spec/lib/components/core/base_output_spec.rb | 91 ------------- spec/lib/components/core/base_spec.rb | 71 ---------- spec/lib/components/core/multi_pin_spec.rb | 78 ----------- spec/lib/components/core/threaded_spec.rb | 104 -------------- spec/lib/components/lcd_spec.rb | 68 +++++----- spec/lib/components/led_spec.rb | 1 + spec/lib/components/rgb_led_spec.rb | 6 +- spec/lib/components/setup/base_spec.rb | 28 ++++ spec/lib/components/setup/input_spec.rb | 44 ++++++ spec/lib/components/setup/multi_pin_spec.rb | 29 ++++ spec/lib/components/setup/output_spec.rb | 25 ++++ spec/lib/components/setup/single_pin_spec.rb | 37 +++++ spec/lib/components/shift_register_spec.rb | 6 +- spec/lib/components/softwareserial_spec.rb | 3 +- spec/lib/components/ssd_spec.rb | 32 ++--- spec/lib/components/stepper_spec.rb | 4 +- 58 files changed, 781 insertions(+), 949 deletions(-) create mode 100644 lib/dino/components/basic.rb create mode 100644 lib/dino/components/basic/analog_input.rb create mode 100644 lib/dino/components/basic/analog_output.rb rename lib/dino/components/{core => basic}/digital_input.rb (69%) create mode 100644 lib/dino/components/basic/digital_output.rb delete mode 100644 lib/dino/components/core.rb delete mode 100644 lib/dino/components/core/analog_input.rb delete mode 100644 lib/dino/components/core/base.rb delete mode 100644 lib/dino/components/core/base_input.rb delete mode 100644 lib/dino/components/core/base_output.rb delete mode 100644 lib/dino/components/core/multi_pin.rb create mode 100644 lib/dino/components/mixins.rb create mode 100644 lib/dino/components/mixins/callbacks.rb create mode 100644 lib/dino/components/mixins/listener.rb create mode 100644 lib/dino/components/mixins/poller.rb create mode 100644 lib/dino/components/mixins/reader.rb rename lib/dino/components/{core => mixins}/threaded.rb (92%) create mode 100644 lib/dino/components/setup.rb create mode 100644 lib/dino/components/setup/base.rb create mode 100644 lib/dino/components/setup/input.rb create mode 100644 lib/dino/components/setup/multi_pin.rb create mode 100644 lib/dino/components/setup/output.rb create mode 100644 lib/dino/components/setup/single_pin.rb rename spec/lib/components/{core => basic}/analog_input_spec.rb (97%) rename spec/lib/components/{core => basic}/digital_input_spec.rb (96%) delete mode 100644 spec/lib/components/button_spec.rb delete mode 100644 spec/lib/components/core/base_input_spec.rb delete mode 100644 spec/lib/components/core/base_output_spec.rb delete mode 100644 spec/lib/components/core/base_spec.rb delete mode 100644 spec/lib/components/core/multi_pin_spec.rb delete mode 100644 spec/lib/components/core/threaded_spec.rb create mode 100644 spec/lib/components/setup/base_spec.rb create mode 100644 spec/lib/components/setup/input_spec.rb create mode 100644 spec/lib/components/setup/multi_pin_spec.rb create mode 100644 spec/lib/components/setup/output_spec.rb create mode 100644 spec/lib/components/setup/single_pin_spec.rb diff --git a/lib/dino/board.rb b/lib/dino/board.rb index eee126cc..cfc70519 100644 --- a/lib/dino/board.rb +++ b/lib/dino/board.rb @@ -1,11 +1,11 @@ module Dino class Board - attr_reader :high, :low, :input_hardware, :analog_zero, :dac_zero + attr_reader :high, :low, :components, :analog_zero, :dac_zero DIVIDERS = [1, 2, 4, 8, 16, 32, 64, 128] def initialize(io, options={}) @bits = options[:bits] || 8 - @io, @input_hardware = io, [] + @io, @components = io, [] io.add_observer(self) @analog_zero, @dac_zero = @io.handshake.to_s.split(",").map { |pin| pin.to_i } @@ -47,26 +47,23 @@ def write(msg) end def update(pin, msg) - @input_hardware.each do |part| + @components.each do |part| part.update(msg) if convert_pin(pin) == convert_pin(part.pin) end end - def add_input_hardware(part) - start_read - @input_hardware << part - set_pin_mode(part.pin, :in, part.pullup) + def add_component(component) + @components << component end - def remove_input_hardware(part) - stop_listener(part.pin) - @input_hardware.delete(part) + def remove_component(component) + stop_listener(component.pin) + @components.delete(component) end - def set_pin_mode(pin, mode, pullup=nil) + def set_pin_mode(pin, mode) pin, value = convert_pin(pin), mode == :out ? 0 : 1 write Dino::Message.encode(command: 0, pin: pin, value: value) - set_pullup(pin, pullup) if mode == :in end def set_pullup(pin, pullup) diff --git a/lib/dino/components.rb b/lib/dino/components.rb index cd3d0ca4..c80915e8 100644 --- a/lib/dino/components.rb +++ b/lib/dino/components.rb @@ -1,6 +1,8 @@ module Dino module Components - require 'dino/components/core' + require 'dino/components/setup' + require 'dino/components/mixins' + require 'dino/components/basic' autoload :Led, 'dino/components/led' autoload :Button, 'dino/components/button' autoload :Sensor, 'dino/components/sensor' diff --git a/lib/dino/components/basic.rb b/lib/dino/components/basic.rb new file mode 100644 index 00000000..79463819 --- /dev/null +++ b/lib/dino/components/basic.rb @@ -0,0 +1,10 @@ +module Dino + module Components + module Basic + require 'dino/components/basic/digital_output' + require 'dino/components/basic/analog_input' + require 'dino/components/basic/digital_input' + require 'dino/components/basic/analog_output' + end + end +end \ No newline at end of file diff --git a/lib/dino/components/basic/analog_input.rb b/lib/dino/components/basic/analog_input.rb new file mode 100644 index 00000000..3d46c6b3 --- /dev/null +++ b/lib/dino/components/basic/analog_input.rb @@ -0,0 +1,21 @@ +module Dino + module Components + module Basic + class AnalogInput + include Setup::SinglePin + include Setup::Input + include Mixins::Reader + include Mixins::Poller + include Mixins::Listener + + def _read + board.analog_read(self.pin) + end + + def _listen + board.analog_listen(self.pin) + end + end + end + end +end diff --git a/lib/dino/components/basic/analog_output.rb b/lib/dino/components/basic/analog_output.rb new file mode 100644 index 00000000..fd7a57db --- /dev/null +++ b/lib/dino/components/basic/analog_output.rb @@ -0,0 +1,21 @@ +module Dino + module Components + module Basic + class AnalogOutput < DigitalOutput + interrupt_with :analog_write + + def analog_write(value) + board.analog_write(pin, @state = value) + end + + def write(value) + unless [board.low, board.high].include? value + analog_write(value) + else + digital_write(value) + end + end + end + end + end +end diff --git a/lib/dino/components/core/digital_input.rb b/lib/dino/components/basic/digital_input.rb similarity index 69% rename from lib/dino/components/core/digital_input.rb rename to lib/dino/components/basic/digital_input.rb index da7666c0..23808aa1 100644 --- a/lib/dino/components/core/digital_input.rb +++ b/lib/dino/components/basic/digital_input.rb @@ -1,15 +1,20 @@ module Dino module Components - module Core - class DigitalInput < BaseInput - HIGH = 1 - LOW = 0 + module Basic + class DigitalInput + include Setup::SinglePin + include Setup::Input + include Mixins::Reader + include Mixins::Poller + include Mixins::Listener - def initialize(options={}) - super(options) + def after_initialize(options={}) _listen end + HIGH = 1 + LOW = 0 + def _read board.digital_read(self.pin) end diff --git a/lib/dino/components/basic/digital_output.rb b/lib/dino/components/basic/digital_output.rb new file mode 100644 index 00000000..13c88136 --- /dev/null +++ b/lib/dino/components/basic/digital_output.rb @@ -0,0 +1,37 @@ +module Dino + module Components + module Basic + class DigitalOutput + include Setup::SinglePin + include Setup::Output + include Mixins::Threaded + interrupt_with :digital_write + + def after_initialize(options={}) + low + end + + def digital_write(value) + board.digital_write(pin, @state = value) + end + + alias :write :digital_write + + def low + digital_write(board.low) + end + + def high + digital_write(board.high) + end + + def toggle + state == board.low ? high : low + end + + alias :off :low + alias :on :high + end + end + end +end diff --git a/lib/dino/components/button.rb b/lib/dino/components/button.rb index 90b22cae..a70dbe31 100644 --- a/lib/dino/components/button.rb +++ b/lib/dino/components/button.rb @@ -1,8 +1,8 @@ module Dino module Components - class Button < Core::DigitalInput + class Button < Basic::DigitalInput alias :down :on_low - alias :up :on_high + alias :up :on_high end end end diff --git a/lib/dino/components/core.rb b/lib/dino/components/core.rb deleted file mode 100644 index f0eac0e8..00000000 --- a/lib/dino/components/core.rb +++ /dev/null @@ -1,13 +0,0 @@ -module Dino - module Components - module Core - require 'dino/components/core/base' - require 'dino/components/core/multi_pin' - require 'dino/components/core/threaded' - require 'dino/components/core/base_input' - require 'dino/components/core/analog_input' - require 'dino/components/core/digital_input' - require 'dino/components/core/base_output' - end - end -end \ No newline at end of file diff --git a/lib/dino/components/core/analog_input.rb b/lib/dino/components/core/analog_input.rb deleted file mode 100644 index f15dc361..00000000 --- a/lib/dino/components/core/analog_input.rb +++ /dev/null @@ -1,15 +0,0 @@ -module Dino - module Components - module Core - class AnalogInput < BaseInput - def _read - board.analog_read(self.pin) - end - - def _listen - board.analog_listen(self.pin) - end - end - end - end -end diff --git a/lib/dino/components/core/base.rb b/lib/dino/components/core/base.rb deleted file mode 100644 index e9eec9fa..00000000 --- a/lib/dino/components/core/base.rb +++ /dev/null @@ -1,41 +0,0 @@ -module Dino - module Components - module Core - class Base - attr_reader :board, :pin, :mode, :pullup, :state - - def initialize(options={}) - raise 'board and pin are required for a component' if options[:board].nil? || options[:pin].nil? - - self.board = options[:board] - self.pin = board.convert_pin(options[:pin]) - @pullup = options[:pullup] - - after_initialize(options) - end - - protected - - attr_writer :board, :pin - - # - # Components::Core::Base does a lot of setup work for you. - # Define #after_initialize in your subclass instead of overriding #initialize - # - # @note This method should be implemented in the BaseComponent subclass. - # - def after_initialize(options={}) ; end - - def mode=(mode) - @mode = mode - board.set_pin_mode(self.pin, mode, pullup) - end - - def pullup=(pullup) - @pullup = pullup - board.set_pullup(self.pin, pullup) - end - end - end - end -end diff --git a/lib/dino/components/core/base_input.rb b/lib/dino/components/core/base_input.rb deleted file mode 100644 index 23b67f0a..00000000 --- a/lib/dino/components/core/base_input.rb +++ /dev/null @@ -1,72 +0,0 @@ -module Dino - module Components - module Core - class BaseInput < Base - include Threaded - - def initialize(options={}) - super options - - remove_callbacks - self.mode = :in - board.add_input_hardware(self) - - after_initialize(options) - end - - def read(&block) - add_callback(:read, &block) if block_given? - _read - loop { break if @callbacks[:read].empty? } - end - - def listen(&block) - add_callback(:listen, &block) if block_given? - _listen - end - - def poll(interval, &block) - add_callback(:poll, &block) if block_given? - threaded_loop do - _read; sleep interval - end - end - - def stop - stop_thread - board.stop_listener(pin) - remove_callback :listen; remove_callback :poll - end - - # - # Defined in DigitalInput and AnalogInput subclasses. - # _read corresponds to Board#digital_read and Board#analog_read respectively. - # _listen corresponds to Board#digital_listen and Board#analog_listen respectively - # - def _read; end - def _listen; end - - def add_callback(key=nil, &block) - key ||= :persistent - @callbacks[key] ||= [] - @callbacks[key] << block - end - - def remove_callback(key=nil) - key ? @callbacks[key] = [] : @callbacks = {} - end - - alias :on_data :add_callback - alias :remove_callbacks :remove_callback - - def update(data) - @state = data - @callbacks.each_value do |array| - array.each { |callback| callback.call(@state) } - end - remove_callback :read - end - end - end - end -end diff --git a/lib/dino/components/core/base_output.rb b/lib/dino/components/core/base_output.rb deleted file mode 100644 index ca61ab25..00000000 --- a/lib/dino/components/core/base_output.rb +++ /dev/null @@ -1,48 +0,0 @@ -module Dino - module Components - module Core - class BaseOutput < Base - include Threaded - interrupt_with :digital_write, :analog_write - - def initialize(options={}) - super options - - self.mode = :out - low - end - - def digital_write(value) - board.digital_write(pin, @state = value) - end - - def analog_write(value) - board.analog_write(pin, @state = value) - end - - def write(value) - unless [board.low, board.high].include? value - analog_write(value) - else - digital_write(value) - end - end - - def low - digital_write(board.low) - end - - def high - digital_write(board.high) - end - - def toggle - state == board.low ? high : low - end - - alias :off :low - alias :on :high - end - end - end -end diff --git a/lib/dino/components/core/multi_pin.rb b/lib/dino/components/core/multi_pin.rb deleted file mode 100644 index 23294583..00000000 --- a/lib/dino/components/core/multi_pin.rb +++ /dev/null @@ -1,65 +0,0 @@ -module Dino - module Components - module Core - class MultiPin - attr_reader :board, :pins, :pullups - - def initialize(options={}) - raise 'board and pins are required for a component' if options[:board].nil? || options[:pins].nil? - - self.board = options[:board] - self.pins = options[:pins] - self.pullups = options[:pullups] || {} - - after_initialize(options) - end - - def state - state = {} - pins.each_key do |pin| - state[pin] = self.send(pin).state - end - state - end - - protected - - attr_writer :board, :pins, :pullups - - # - # @note This method should be implemented in your subclass. - # - def after_initialize(options={}) ; end - - # - # Build complex components by proxying each pin to a basic, single pin component. - # - # Call #proxy with a hash like: - # class MyComponent < MultiPin - # proxy {lamp: Core::BaseOutput, motor: Core::BaseOutput} - # end - # - # Checks the @pins hash for the pins corresponding to the keys like: - # pins => {lamp: 9, motor: 10} - No error - # pins => {lamp: 9} - Error raised - # - # It instantiates a new instance of the class passed in for each key in an instance variable. - # Sets up an attr_reader on the singleton class, so the proxy component can be accessed like: - # my_component.lamp.on; my_component.motor.analog_write(128) - # - def proxy(proxies={}) - proxies.each_pair do |key, klass| - raise "missing pins[:#{key}] pin" unless pins[key] - - proxy_component = klass.new(board: board, pin: pins[key], pullup: pullups[key]) - instance_variable_set("@#{key}", proxy_component) - - singleton_class.class_eval { attr_reader key } - end - end - - alias :proxies :proxy - end - end - end -end diff --git a/lib/dino/components/ir_receiver.rb b/lib/dino/components/ir_receiver.rb index ccbd10a2..5dcf44a0 100644 --- a/lib/dino/components/ir_receiver.rb +++ b/lib/dino/components/ir_receiver.rb @@ -1,12 +1,7 @@ module Dino module Components - class IrReceiver < Core::DigitalInput - alias :flash :on_low - - def update(data) - return if data.to_i == HIGH - super data - end + class IrReceiver < Basic::DigitalInput + alias :on_flash :on_low end end end diff --git a/lib/dino/components/lcd.rb b/lib/dino/components/lcd.rb index f46dc4dc..6d4d85b2 100644 --- a/lib/dino/components/lcd.rb +++ b/lib/dino/components/lcd.rb @@ -1,7 +1,9 @@ module Dino module Components - class LCD < Core::MultiPin + class LCD + include Setup::MultiPin + require_pins :rs, :enable, :d4, :d5, :d6, :d7 # Initialize in 4 bits mode # # Dino::Componentes::LCD.new( diff --git a/lib/dino/components/led.rb b/lib/dino/components/led.rb index b8f8dff8..ff917576 100644 --- a/lib/dino/components/led.rb +++ b/lib/dino/components/led.rb @@ -1,6 +1,6 @@ module Dino module Components - class Led < Core::BaseOutput + class Led < Basic::AnalogOutput def blink(interval=0.5) threaded_loop do toggle diff --git a/lib/dino/components/mixins.rb b/lib/dino/components/mixins.rb new file mode 100644 index 00000000..f889b4e0 --- /dev/null +++ b/lib/dino/components/mixins.rb @@ -0,0 +1,11 @@ +module Dino + module Components + module Mixins + require 'dino/components/mixins/threaded' + require 'dino/components/mixins/callbacks' + require 'dino/components/mixins/reader' + require 'dino/components/mixins/poller' + require 'dino/components/mixins/listener' + end + end +end \ No newline at end of file diff --git a/lib/dino/components/mixins/callbacks.rb b/lib/dino/components/mixins/callbacks.rb new file mode 100644 index 00000000..dd1006d1 --- /dev/null +++ b/lib/dino/components/mixins/callbacks.rb @@ -0,0 +1,33 @@ +module Dino + module Components + module Mixins + module Callbacks + def callbacks + @callbacks ||= {} + end + + def add_callback(key=:persistent, &block) + callbacks + @callbacks[key] ||= [] + @callbacks[key] << block + end + + def remove_callback(key=nil) + callbacks + key ? @callbacks[key] = [] : @callbacks = {} + end + + alias :on_data :add_callback + alias :remove_callbacks :remove_callback + + def update(data) + @state = data + callbacks.each_value do |array| + array.each { |callback| callback.call(@state) } + end + remove_callback :read + end + end + end + end +end diff --git a/lib/dino/components/mixins/listener.rb b/lib/dino/components/mixins/listener.rb new file mode 100644 index 00000000..9e1e040e --- /dev/null +++ b/lib/dino/components/mixins/listener.rb @@ -0,0 +1,26 @@ +module Dino + module Components + module Mixins + module Listener + include Callbacks + + def listen(&block) + stop + add_callback(:listen, &block) if block_given? + _listen + end + + def stop + super if defined?(super) + board.stop_listener(pin) + remove_callbacks :listen + end + + # + # Including component should define this to start a listener on the board. + # + def _listen; end + end + end + end +end diff --git a/lib/dino/components/mixins/poller.rb b/lib/dino/components/mixins/poller.rb new file mode 100644 index 00000000..ac478a1f --- /dev/null +++ b/lib/dino/components/mixins/poller.rb @@ -0,0 +1,29 @@ +module Dino + module Components + module Mixins + module Poller + include Reader + # + # Make sure Threaded is in the including class. + # + def self.included(base) + base.class_eval { include Threaded } + end + + def poll(interval, &block) + stop + add_callback(:poll, &block) if block_given? + threaded_loop do + _read; sleep interval + end + end + + def stop + super if defined?(super) + stop_thread + remove_callback :poll + end + end + end + end +end diff --git a/lib/dino/components/mixins/reader.rb b/lib/dino/components/mixins/reader.rb new file mode 100644 index 00000000..89810135 --- /dev/null +++ b/lib/dino/components/mixins/reader.rb @@ -0,0 +1,20 @@ +module Dino + module Components + module Mixins + module Reader + include Callbacks + + def read(&block) + add_callback(:read, &block) if block_given? + _read + loop { break if @callbacks[:read].empty? } + end + + # + # Including component should define this to perform a single read on the board. + # + def _read; end + end + end + end +end diff --git a/lib/dino/components/core/threaded.rb b/lib/dino/components/mixins/threaded.rb similarity index 92% rename from lib/dino/components/core/threaded.rb rename to lib/dino/components/mixins/threaded.rb index a55c93fd..be981dea 100644 --- a/lib/dino/components/core/threaded.rb +++ b/lib/dino/components/mixins/threaded.rb @@ -1,21 +1,21 @@ module Dino module Components - module Core + module Mixins module Threaded attr_reader :thread, :interrupts_enabled - def self.included(base) - base.extend(ClassMethods) - end - module ClassMethods def interrupt_with(*args) interrupts = self.class_eval('@@interrupts') rescue [] - args.each { |a| interrupts << a } + interrupts = (interrupts + args).uniq self.class_variable_set(:@@interrupts, interrupts) end end + def self.included(base) + base.extend ClassMethods + end + def threaded(&block) stop_thread enable_interrupts unless interrupts_enabled diff --git a/lib/dino/components/relay.rb b/lib/dino/components/relay.rb index 3b285532..066b81e4 100644 --- a/lib/dino/components/relay.rb +++ b/lib/dino/components/relay.rb @@ -1,6 +1,6 @@ module Dino module Components - class Relay < Core::BaseOutput + class Relay < Basic::DigitalOutput end end end diff --git a/lib/dino/components/rgb_led.rb b/lib/dino/components/rgb_led.rb index d91ddacd..2c92cc08 100644 --- a/lib/dino/components/rgb_led.rb +++ b/lib/dino/components/rgb_led.rb @@ -1,15 +1,12 @@ module Dino module Components - class RgbLed < Core::MultiPin - # - # options = {board: my_board, pins: {red: red_pin, green: green_pin, blue: blue_pin} - # - def after_initialize(options={}) - proxies red: Core::BaseOutput, - green: Core::BaseOutput, - blue: Core::BaseOutput - end + class RgbLed + include Setup::MultiPin + proxy_pins red: Basic::AnalogOutput, + green: Basic::AnalogOutput, + blue: Basic::AnalogOutput + # Format: [R, G, B] COLORS = { red: [255, 000, 000], diff --git a/lib/dino/components/sensor.rb b/lib/dino/components/sensor.rb index ec96d532..4cb7f949 100644 --- a/lib/dino/components/sensor.rb +++ b/lib/dino/components/sensor.rb @@ -1,6 +1,6 @@ module Dino module Components - class Sensor < Core::AnalogInput + class Sensor < Basic::AnalogInput end end end diff --git a/lib/dino/components/servo.rb b/lib/dino/components/servo.rb index e0461d71..f6902cf6 100644 --- a/lib/dino/components/servo.rb +++ b/lib/dino/components/servo.rb @@ -1,6 +1,9 @@ module Dino module Components - class Servo < Core::BaseOutput + class Servo + include Setup::SinglePin + include Mixins::Threaded + def after_initialize(options={}) board.servo_toggle(pin, 1) end @@ -10,9 +13,7 @@ def position=(value) board.servo_write(pin, @state) end - def position - @state - end + alias :position :state def angle(value) value == 180 ? value : value % 180 diff --git a/lib/dino/components/setup.rb b/lib/dino/components/setup.rb new file mode 100644 index 00000000..44af3b68 --- /dev/null +++ b/lib/dino/components/setup.rb @@ -0,0 +1,11 @@ +module Dino + module Components + module Setup + require 'dino/components/setup/base' + require 'dino/components/setup/single_pin' + require 'dino/components/setup/multi_pin' + require 'dino/components/setup/input' + require 'dino/components/setup/output' + end + end +end diff --git a/lib/dino/components/setup/base.rb b/lib/dino/components/setup/base.rb new file mode 100644 index 00000000..acf53c6b --- /dev/null +++ b/lib/dino/components/setup/base.rb @@ -0,0 +1,48 @@ +module Dino + module Components + module Setup + module Base + attr_reader :board, :state + + def initialize(options={}) + initialize_board(options) + initialize_pins(options) + register + + after_initialize(options) + end + + protected + + attr_writer :board, :state + + def initialize_board(options={}) + raise 'a board is required for a component' unless options[:board] + self.board = options[:board] + end + + def register + board.add_component(self) + end + + def unregister + board.remove_component(self) + end + + # + # Setup::Base just requires a board and adds the component to the list of components. + # Mix in other modules from Setup or define this method in your class to initialize the pin(s). + # + def initialize_pins(options={}) ; end + alias :initialize_pin :initialize_pins + + # + # Define #after_initialize in your component class. + # + # @note This method should be implemented in the including class. + # + def after_initialize(options={}) ; end + end + end + end +end diff --git a/lib/dino/components/setup/input.rb b/lib/dino/components/setup/input.rb new file mode 100644 index 00000000..b5a00557 --- /dev/null +++ b/lib/dino/components/setup/input.rb @@ -0,0 +1,23 @@ +module Dino + module Components + module Setup + module Input + attr_reader :pullup + + def pullup=(pullup) + @pullup = pullup + board.set_pullup(self.pin, pullup) + end + + protected + + def initialize_pins(options={}) + super(options) + self.mode = :in + self.pullup = options[:pullup] + board.start_read + end + end + end + end +end diff --git a/lib/dino/components/setup/multi_pin.rb b/lib/dino/components/setup/multi_pin.rb new file mode 100644 index 00000000..0d1b89df --- /dev/null +++ b/lib/dino/components/setup/multi_pin.rb @@ -0,0 +1,93 @@ +module Dino + module Components + module Setup + module MultiPin + # + # Build complex components by modeling them as separate single pin subcomponents. + # + include Base + attr_reader :pins, :pullups, :proxies + + # + # Return a hash with the state of each proxy component. + # + def state + hash = {} + proxies.each_key do |key| + hash[key] = self.proxies[key].state rescue nil + end + hash + end + + protected + attr_writer :pins, :pullups, :proxies + + def self.included(base) + base.extend ClassMethods + end + + # + # A subcomponent's pin can be required, proxied, or both. + # + # Requiring a pin simply raises an error if a value for it is not specified in the options[:pins] hash. + # It will NOT automatically set up a subcomponent or do anything further. + # This can be useful for native libraries where modeling the pin as a subcomponent is not necessary. + # Or for components with pins that may be left disconnected. + # + # Proxying a pin automatically requires it, and specifies what single-pin component it should be modeled as. + # This module will automatically intialize the proxy component and store it in the @proxies instance variable. + # It also creates an attr_accessor on the singleton class for the subcomponent. + # The accessor and the hash element are both named using the pin's key from the options[:pins] hash. + # + # A proxied pin can be NOT required by including `optional: true` in the options hash passed to #proxy_pins. + # Please see the source for the SSD and RgbLed components for good examples of how all this works. + # + module ClassMethods + def require_pins(*args) + required_pins = self.class_eval('@@required_pins') rescue [] + required_pins = (required_pins + args).uniq + self.class_variable_set(:@@required_pins, required_pins) + end + alias :require_pin :require_pins + + def proxy_pins(options={}) + if options[:optional] + options.reject! { |k| k == :optional } + else + options.reject! { |k| k == :optional } if (options[:optional] == false) + require_pins(*options.keys) + end + + proxied_pins = self.class_eval('@@proxied_pins') rescue {} + proxied_pins.merge!(options) + self.class_variable_set(:@@proxied_pins, proxied_pins) + end + alias :proxy_pin :proxy_pins + end + + def initialize_pins(options={}) + self.pins = options[:pins] + self.pullups = options[:pullups] || {} + self.proxies = {} + validate_pins + build_proxies + end + + def validate_pins + required_pins = self.class.class_eval('@@required_pins') rescue [] + required_pins.each { |key| raise "missing pins[:#{key}] pin" unless pins[key] } + end + + def build_proxies + proxied_pins = self.class.class_eval('@@proxied_pins') rescue {} + proxied_pins.each_pair do |key, klass| + component = klass.new(board: board, pin: pins[key], pullup: pullups[key]) rescue nil + self.proxies[key] = component + instance_variable_set("@#{key}", component) + singleton_class.class_eval { attr_reader key } + end + end + end + end + end +end diff --git a/lib/dino/components/setup/output.rb b/lib/dino/components/setup/output.rb new file mode 100644 index 00000000..4ef8bca3 --- /dev/null +++ b/lib/dino/components/setup/output.rb @@ -0,0 +1,14 @@ +module Dino + module Components + module Setup + module Output + protected + + def initialize_pins(options={}) + super(options) + self.mode = :out + end + end + end + end +end diff --git a/lib/dino/components/setup/single_pin.rb b/lib/dino/components/setup/single_pin.rb new file mode 100644 index 00000000..6088b3e0 --- /dev/null +++ b/lib/dino/components/setup/single_pin.rb @@ -0,0 +1,27 @@ +module Dino + module Components + module Setup + module SinglePin + include Base + attr_reader :pin, :mode + + protected + + attr_writer :pin + + # + # Require a single pin for single pin components. + # + def initialize_pins(options={}) + raise 'a pin is required for this component' unless options[:pin] + self.pin = board.convert_pin(options[:pin]) + end + + def mode=(mode) + @mode = mode + board.set_pin_mode(self.pin, mode) + end + end + end + end +end diff --git a/lib/dino/components/shift_register.rb b/lib/dino/components/shift_register.rb index 11418c69..85730234 100644 --- a/lib/dino/components/shift_register.rb +++ b/lib/dino/components/shift_register.rb @@ -1,15 +1,14 @@ module Dino module Components - class ShiftRegister < Core::MultiPin + class ShiftRegister # # options = {board: my_board, pins: {clock: clock_pin, latch: latch_pin, data: data_pin} # - def after_initialize(options={}) - proxy clock: Core::BaseOutput, - latch: Core::BaseOutput, - data: Core::BaseOutput - end - + include Setup::MultiPin + proxy_pins clock: Basic::DigitalOutput, + latch: Basic::DigitalOutput, + data: Basic::DigitalOutput + def write(bytes) bytes = [bytes] unless bytes.class == Array diff --git a/lib/dino/components/softserial.rb b/lib/dino/components/softserial.rb index d0906929..1dd2db38 100644 --- a/lib/dino/components/softserial.rb +++ b/lib/dino/components/softserial.rb @@ -1,6 +1,7 @@ module Dino module Components - class SoftwareSerial < Core::MultiPin + class SoftwareSerial + include Setup::MultiPin attr_accessor :baud COMMAND = 12 diff --git a/lib/dino/components/ssd.rb b/lib/dino/components/ssd.rb index edae8f79..c363cd21 100644 --- a/lib/dino/components/ssd.rb +++ b/lib/dino/components/ssd.rb @@ -1,53 +1,52 @@ -# Connect to the Arduino and -# take control of the SSD -# -# ssd = SevenSegmentDisplay.new( -# board: Board.new(TxRx.new), -# pins: [12,13,3,4,5,10,9], -# anode: 11 -# ) - module Dino module Components - class SSD < Core::MultiPin - attr_reader :anode - + class SSD + include Setup::MultiPin + + proxy_pins cathode: Basic::DigitalOutput, + anode: Basic::DigitalOutput, + optional: true + + proxy_pins a: Basic::DigitalOutput, + b: Basic::DigitalOutput, + c: Basic::DigitalOutput, + d: Basic::DigitalOutput, + e: Basic::DigitalOutput, + f: Basic::DigitalOutput, + g: Basic::DigitalOutput + + # ssd = SevenSegmentDisplay.new( + # board: board, + # pins: {anode: 11, a: 12, b: 13, c: 3,d: 4,e: 5,f: 10,g: 9} + # ) def after_initialize(options={}) - raise Exception.new('anode must be specified') unless options[:anode] - @anode = Core::BaseOutput.new(board: board, pin: options[:anode]) - - # Create a Core::BaseOutput for every pin. - @pins.map! { |pin| Core::BaseOutput.new(board: board, pin: pin) } - - clear - on + @segments = [a,b,c,d,e,f,g] + clear; on end + attr_reader :segments + def clear - 7.times { |pin| toggle pin, 0 } + segments.each do |pin| + pin.low if cathode + pin.high if anode + end end def display(char) - key = char.to_s.upcase - - return scroll(key) if key.length > 1 - - # Make sure the ssd is turned on. - on - - if chars = CHARACTERS[key] - chars.each_with_index { |s,i| toggle i, s } - else - clear - end + char = char.to_s + return scroll(char) if char.length > 1 + off; write(char); on end def on - @anode.high + anode.high if anode + cathode.low if cathode end def off - @anode.low + anode.low if anode + cathode.high if cathode end CHARACTERS = { @@ -82,7 +81,7 @@ def off 'P' => [1,1,0,0,1,1,1], 'Q' => [1,1,1,0,0,1,1], 'R' => [0,0,0,0,1,0,1], - 'S' => [0,0,1,1,0,1,1], + 'S' => [1,0,1,1,0,1,1], 'T' => [0,0,0,1,1,1,1], 'U' => [0,0,1,1,1,0,0], 'V' => [0,1,1,1,1,1,0], @@ -94,19 +93,25 @@ def off private - def scroll(string) - string.chars.each do |chr| - off - sleep 0.05 - display chr - sleep 0.5 + def write(char) + bits = CHARACTERS[char.to_s.upcase] + unless bits + clear + else + bits.each_with_index do |bit, index| + if anode + bit == 0 ? bit = 1 : bit = 0 + end + segments[index].write(bit) unless (segments[index].state == bit) + end end - - clear end - def toggle(number, state) - @pins[number].write (state == 1 ? 0 : 1) + def scroll(string) + string.chars.each do |char| + display(char) + sleep(0.5) + end end end end diff --git a/lib/dino/components/stepper.rb b/lib/dino/components/stepper.rb index 165d1ef1..3f739f76 100644 --- a/lib/dino/components/stepper.rb +++ b/lib/dino/components/stepper.rb @@ -1,11 +1,11 @@ module Dino module Components - class Stepper < Core::MultiPin - def after_initialize(options={}) - proxies step: Core::BaseOutput, - direction: Core::BaseOutput - end - + class Stepper + include Setup::MultiPin + + proxy_pins step: Basic::DigitalOutput, + direction: Basic::DigitalOutput + def step_cc direction.high step.high diff --git a/spec/lib/board_spec.rb b/spec/lib/board_spec.rb index 34fe6ad2..3b99785f 100644 --- a/spec/lib/board_spec.rb +++ b/spec/lib/board_spec.rb @@ -35,19 +35,19 @@ def io_mock(methods = {}) end describe '#update' do - context 'when the given pin connects to input hardware part' do - it 'should call update with the message on the part' do + context 'when a component is connected to the pin' do + it 'should call update with the message on the component' do part = mock(:part, pin: 7, pullup: nil) - subject.add_input_hardware(part) + subject.add_component(part) other_part = mock(:part, pin: 9, pullup: nil) - subject.add_input_hardware(other_part) + subject.add_component(other_part) part.should_receive(:update).with('wake up!') subject.update(7, 'wake up!') end end - context 'when the given pin is not connected' do + context 'when a component is not connected to the pin' do it 'should not do anything' do expect { subject.update(5, 'wake up!') @@ -56,39 +56,23 @@ def io_mock(methods = {}) end end - describe '#input_hardware' do - it 'should initialize as empty' do - subject.input_hardware.should == [] + describe '#add_component' do + it 'should add the component to the board' do + subject.add_component(mock1 = mock(:part1, pin: 12, pullup: nil)) + subject.add_component(mock2 = mock(:part2, pin: 14, pullup: nil)) + subject.components.should =~ [mock1, mock2] end end - describe '#add_input_hardware' do - it 'should start the read thread' do - io_mock.should_receive(:read) - subject.add_input_hardware(mock(:part1, pin: 12, pullup: nil)) - end - - it 'should add input hardware to the board' do - subject.add_input_hardware(mock1 = mock(:part1, pin: 12, pullup: nil)) - subject.add_input_hardware(mock2 = mock(:part2, pin: 14, pullup: nil)) - subject.input_hardware.should =~ [mock1, mock2] - end - - it 'should set the mode for the given pin to "in"' do - subject.should_receive(:set_pin_mode).with(12, :in, nil) - subject.add_input_hardware(mock(:part1, pin: 12, pullup: nil)) - end - end - - describe '#remove_input_hardware' do + describe '#remove_component' do it 'should remove the given part from the hardware of the board and stop listening' do mock = mock(:part1, pin: 12, pullup: nil) - subject.add_input_hardware(mock) + subject.add_component(mock) subject.should_receive(:stop_listener).with(12) - subject.remove_input_hardware(mock) + subject.remove_component(mock) - subject.input_hardware.should == [] + subject.components.should == [] end end @@ -114,6 +98,9 @@ def io_mock(methods = {}) end end + # + # Board commands + # describe '#digital_write' do it 'should write the value to the right pin' do io_mock.should_receive(:write).with(Dino::Message.encode(command: 1, pin: 1, value: 3)) @@ -173,16 +160,6 @@ def io_mock(methods = {}) io_mock.should_receive(:write).with(Dino::Message.encode(command: 0, pin: 13, value: 1)) subject.set_pin_mode(13, :in) end - - it 'should set the pullup correctly if mode is in' do - subject.should_receive(:set_pullup).with(13, nil) - subject.set_pin_mode(13, :in) - end - - it 'shouldnt affect the pullup if mode is out' do - subject.should_not_receive(:set_pullup).with(13, nil) - subject.set_pin_mode(13, :out) - end end describe '#set_pullup' do diff --git a/spec/lib/components/core/analog_input_spec.rb b/spec/lib/components/basic/analog_input_spec.rb similarity index 97% rename from spec/lib/components/core/analog_input_spec.rb rename to spec/lib/components/basic/analog_input_spec.rb index 582dd847..87f8c2b1 100644 --- a/spec/lib/components/core/analog_input_spec.rb +++ b/spec/lib/components/basic/analog_input_spec.rb @@ -2,7 +2,7 @@ module Dino module Components - module Core + module Basic describe AnalogInput do let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } let(:board) { Board.new(txrx) } diff --git a/spec/lib/components/core/digital_input_spec.rb b/spec/lib/components/basic/digital_input_spec.rb similarity index 96% rename from spec/lib/components/core/digital_input_spec.rb rename to spec/lib/components/basic/digital_input_spec.rb index 684aa844..132d1c76 100644 --- a/spec/lib/components/core/digital_input_spec.rb +++ b/spec/lib/components/basic/digital_input_spec.rb @@ -2,7 +2,7 @@ module Dino module Components - module Core + module Basic describe DigitalInput do let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } let(:board) { Board.new(txrx) } @@ -26,11 +26,10 @@ module Core describe '#_listen' do it 'should send #digital_listen to the board with its pin' do - subject subject.stop board.should_receive(:digital_listen).with(subject.pin) - subject.listen + subject._listen end end diff --git a/spec/lib/components/button_spec.rb b/spec/lib/components/button_spec.rb deleted file mode 100644 index d9cc1e48..00000000 --- a/spec/lib/components/button_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - describe Button do - end - end -end diff --git a/spec/lib/components/core/base_input_spec.rb b/spec/lib/components/core/base_input_spec.rb deleted file mode 100644 index d37281ab..00000000 --- a/spec/lib/components/core/base_input_spec.rb +++ /dev/null @@ -1,127 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module Core - describe BaseInput do - let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } - let(:board) { Board.new(txrx) } - let(:options) { { pin: 'A0', board: board } } - subject { BaseInput.new(options) } - - describe '#initialize' do - it 'should add itself as input hardware, set mode to in and start read' do - board.should_receive(:set_pin_mode).with(14, :in, nil) - board.should_receive(:add_input_hardware) - - BaseInput.new(options) - end - end - - context 'callbacks' do - describe '#add_callback' do - it 'should add a callback to the :persistent key if no key is given' do - subject.add_callback { mock } - subject.instance_variable_get(:@callbacks)[:persistent].should_not be_empty - end - - it 'should add a callback to the key if a key is given' do - subject.add_callback(:test) { mock } - subject.instance_variable_get(:@callbacks)[:test].should_not be_empty - end - end - - describe '#remove_callback' do - it 'should remove all callbacks if no key is given' do - subject.add_callback { mock } - subject.remove_callback - - subject.instance_variable_get(:@callbacks).should == {} - end - - it 'should remove callbacks for the given key if key is given' do - subject.add_callback { mock } - subject.add_callback(:test) { mock } - subject.remove_callback(:test) - - subject.instance_variable_get(:@callbacks)[:test].should be_empty - end - end - end - - context 'reading' do - describe '#read' do - it 'should add the block given as a callback to the :read key' do - subject.should_receive(:add_callback).with(:read) - subject.instance_variable_set(:@callbacks, {read: []}) - subject.read { mock } - end - - it 'should call #_read once' do - subject.should_receive(:read) - subject.read - end - end - - describe '#poll' do - it 'should add the block given as a callback to the :poll key' do - subject.should_receive(:add_callback).with(:poll) - subject.poll(5) { mock } - end - end - - describe '#listen' do - it 'should add the block given as a callback to the :listen key' do - subject.should_receive(:add_callback).with(:listen) - subject.listen { mock } - end - - it 'should call #_listen' do - subject.should_receive(:_listen) - subject.listen - end - end - - describe '#stop' do - it 'should tell the board to turn the listener off' do - board.should_receive(:stop_listener).with(subject.pin) - subject.stop - end - - it 'should remove all callbacks from the :listen and :poll keys and stop the read thread' do - subject.should_receive(:remove_callback).with(:listen) - subject.should_receive(:remove_callback).with(:poll) - subject.should_receive(:stop_thread) - - subject.stop - end - end - end - - describe '#update' do - it 'should update @state' do - subject.update("something") - subject.instance_variable_get(:@state).should == "something" - end - - it 'should call all callbacks passing in the given data' do - first_block_data = nil - second_block_data = nil - subject.add_callback { |data| first_block_data = data } - subject.add_callback { |data| second_block_data = data } - - subject.update('Some data') - [first_block_data, second_block_data].each { |block_data| block_data.should == "Some data" } - end - - it 'should remove any callbacks keyed with :read' do - subject.add_callback(:read) { |data| first_block_data = data } - - subject.update("Some data") - subject.instance_variable_get(:@callbacks)[:read].should be_empty - end - end - end - end - end -end diff --git a/spec/lib/components/core/base_output_spec.rb b/spec/lib/components/core/base_output_spec.rb deleted file mode 100644 index 99c1e5fb..00000000 --- a/spec/lib/components/core/base_output_spec.rb +++ /dev/null @@ -1,91 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module Core - describe BaseOutput do - let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } - let(:board) { Board.new(txrx) } - let(:options) { { pin: 'A0', board: board } } - subject { BaseOutput.new(options) } - - before(:each) { subject } - - describe '#initialize' do - it 'should set mode to out and go low' do - board.should_receive(:set_pin_mode).with(14, :out, nil) - board.should_receive(:digital_write).with(14, board.low) - - BaseOutput.new(options) - end - end - - describe '#digital_write' do - it 'should update the @state instance variable and call #digital_write on the board' do - board.should_receive(:digital_write).with(subject.pin, board.high).once - - subject.digital_write(board.high) - subject.state.should == board.high - end - end - - describe '#analog_write' do - it 'should update the @state instance variable and call #analog_write on the board' do - board.should_receive(:analog_write).with(subject.pin, 128).once - - subject.analog_write(128) - subject.state.should == 128 - end - end - - describe '#write' do - it 'should call #digital_write if value is HIGH' do - subject.should_receive(:digital_write).with(board.high) - subject.write(board.high) - end - - - it 'should call #digital_write if value is LOW' do - subject.should_receive(:digital_write).with(board.low) - subject.write(board.low) - end - - it 'should call #analog_write if value is anything else' do - subject.should_receive(:analog_write).with(128) - subject.write(128) - end - end - - describe '#high' do - it 'should call #digital_write with HIGH' do - subject.should_receive(:digital_write).with(board.high) - subject.high - end - end - - describe '#low' do - it 'should call #digital_write with LOW' do - subject.should_receive(:digital_write).with(board.low) - subject.low - end - end - - describe '#toggle' do - it 'should call high if currently LOW' do - subject.low - subject.should_receive(:high) - - subject.toggle - end - - it 'should call LOW if anything else' do - subject.high - subject.should_receive(:low) - - subject.toggle - end - end - end - end - end -end diff --git a/spec/lib/components/core/base_spec.rb b/spec/lib/components/core/base_spec.rb deleted file mode 100644 index 61147f4b..00000000 --- a/spec/lib/components/core/base_spec.rb +++ /dev/null @@ -1,71 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module Core - describe Base do - let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } - let(:board) { Board.new(txrx) } - let(:options) { { pin: 'A0', board: board } } - subject { Base.new(options) } - - describe '#initialize' do - it 'should convert the pin to an integer' do - board.should_receive(:convert_pin).with(options[:pin]) - - component = Base.new(options) - end - - it 'should require a pin' do - expect { - Base.new(board: board) - }.to raise_exception - end - - it 'should require a board' do - expect { - Base.new(pin: 'A0') - }.to raise_exception - end - end - - context "when subclassed" do - class SpecComponent < Base - def sucessfully_initialized? ; @success ; end - - def options ; @options ; end - - def after_initialize(options={}) - @success = true - @options = options - end - end - - it "should call #after_initialize with options" do - component = SpecComponent.new(options) - component.should be_sucessfully_initialized - component.options.should eq options - end - end - - describe '#mode=' do - it 'should tell the board to set the pin mode' do - board.should_receive(:set_pin_mode).with(subject.pin, :out, nil) - - subject.send(:mode=, :out) - subject.mode.should == :out - end - end - - describe '#pullup=' do - it 'should tell the board to set the pullup mode' do - board.should_receive(:set_pullup).with(subject.pin, true) - - subject.send(:pullup=, true) - subject.pullup.should == true - end - end - end - end - end -end diff --git a/spec/lib/components/core/multi_pin_spec.rb b/spec/lib/components/core/multi_pin_spec.rb deleted file mode 100644 index 8657f4e1..00000000 --- a/spec/lib/components/core/multi_pin_spec.rb +++ /dev/null @@ -1,78 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module Core - describe MultiPin do - let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } - let(:board) { Board.new(txrx) } - let(:options) { { pins: {red: 9, blue: 10}, board: board } } - - describe '#initialize' do - it 'should require a pin' do - expect { - MultiPin.new(board: board) - }.to raise_exception - end - - it 'should require a board' do - expect { - MultiPin.new(pins: options[:pins]) - }.to raise_exception - end - end - - context "when subclassed" do - class MPComponent < MultiPin - def sucessfully_initialized? ; @success ; end - - def options ; @options ; end - - def after_initialize(options={}) - @success = true - @options = options - proxy red: BaseOutput, blue: BaseInput - end - end - - it "should call #after_initialize with options" do - component = MPComponent.new(options) - component.should be_sucessfully_initialized - component.options.should eq options - end - - describe '#proxy' do - it 'should raise if any of the required pins are missing' do - expect { - MPComponent.new(board: board, pins: {blue: 11}) - }.to raise_exception(/red/) - end - - it 'should create the right class of proxy component for each pin with the right options' do - BaseOutput.should_receive(:new).with({board: board, pin: options[:pins][:red], pullup: nil }) - BaseInput.should_receive(:new).with({board: board, pin: options[:pins][:blue], pullup: true}) - - MPComponent.new options.merge(pullups: {blue: true}) - end - - it 'should assign the proxy commponent to the right instance variable' do - component = MPComponent.new(options) - component.red.class.should == BaseOutput - component.blue.class.should == BaseInput - end - - describe '#states' do - it 'should return a hash corresponding to the state of each proxy component (pin)' do - component = MPComponent.new(options) - component.red.write(128) - component.blue.update(555) - - component.state.should == {red: 128, blue: 555} - end - end - end - end - end - end - end -end diff --git a/spec/lib/components/core/threaded_spec.rb b/spec/lib/components/core/threaded_spec.rb deleted file mode 100644 index 4297428e..00000000 --- a/spec/lib/components/core/threaded_spec.rb +++ /dev/null @@ -1,104 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module Core - describe Threaded do - class ThreadedComponent - include Threaded - interrupt_with :interrupt_it - def interrupt_it; end - def dont_interrupt_it; end - end - - subject { ThreadedComponent.new } - - describe '#threaded' do - it 'should stop the existing thread' do - subject.should_receive(:stop_thread) - subject.threaded { mock } - end - - it 'should enable interrupts on the first threaded call' do - subject.should_receive(:enable_interrupts).once - thread = subject.threaded { mock } - end - - it 'should start a thread and call the block passed.' do - block = mock - Thread.should_receive(:new).once.and_yield - block.should_receive(:call) - - subject.threaded { block.call } - end - end - - describe '#stop_thread' do - it 'should kill the internal thread' do - subject.threaded { sleep } - subject.thread.should_receive(:kill) - - subject.stop_thread - end - end - - describe '#enable_interrupts' do - it 'should set @enable_interrupts to true' do - subject.enable_interrupts - subject.interrupts_enabled.should == true - end - end - - context 'interrupts' do - it 'should make method names passed to #interrupt_with stop the internal thread' do - subject.threaded { sleep } - subject.should_receive(:stop_thread) - - subject.interrupt_it - end - - it 'should not make other methods stop the internal thread' do - subject.threaded { sleep } - subject.should_not_receive(:stop_thread) - - subject.dont_interrupt_it - end - end - - context 'with multiple threads' do - it 'should let other threads interrupt the internal thread' do - subject.threaded {sleep} - subject.should_receive(:stop_thread) - - subject.interrupt_it - end - - it 'should not let the internal thread interrupt itself' do - subject.should_receive(:interrupt_it).exactly(10).times - subject.should_not_receive(:stop_thread) - - # Simulate being inside the internal thread. - subject.enable_interrupts - subject.instance_variable_set(:@thread, Thread.current) - 10.times { subject.interrupt_it } - end - end - - context 'when included and then subclassed' do - it 'should allow calling #interrupt_with again' do - class SubThreaded < ThreadedComponent - interrupt_with :stop_it - def stop_it; end - end - - component = SubThreaded.new - component.threaded { sleep } - component.should_receive(:stop_thread) - - component.stop_it - end - end - end - end - end -end diff --git a/spec/lib/components/lcd_spec.rb b/spec/lib/components/lcd_spec.rb index dc9b2991..86b7468d 100644 --- a/spec/lib/components/lcd_spec.rb +++ b/spec/lib/components/lcd_spec.rb @@ -3,8 +3,8 @@ module Dino module Components describe LCD do - let(:board) { mock(:board, digital_write: true, set_pin_mode: true) } - + let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } + let(:board) { Board.new(txrx) } subject { LCD.new board: board, pins: { rs: 12, enable: 11, d4: 5, d5: 4, d6: 3, d7: 2 }, cols: 16, rows: 2 } before do @@ -13,116 +13,116 @@ module Components end describe '#clear' do - it 'clears the display sending the command "10..2\n" to the board' do - board.should_receive(:write).with "10..2\n" + it 'clears the display' do + board.should_receive(:write).with Dino::Message.encode(command: 10, value: 2) subject.clear end end describe '#home' do - it 'Moves the cursor to the first position with the command "10..3\n"' do - board.should_receive(:write).with "10..3\n" + it 'Moves the cursor to the first position' do + board.should_receive(:write).with Dino::Message.encode(command: 10, value: 3) subject.home end end describe '#set_cursor' do - it 'moves the cursor to the given position sending the command "10..4.0,1\n"' do - board.should_receive(:write).with "10..4.0,1\n" + it 'moves the cursor to the given position' do + board.should_receive(:write).with Dino::Message.encode(command: 10, value: 4, aux_message: "0,1") subject.set_cursor(0,1) end end describe '#puts' do it 'prints a string in the display' do - board.should_receive(:write).with "10..5.AB\n" + board.should_receive(:write).with Dino::Message.encode(command: 10, value: 5, aux_message: "AB") subject.puts("AB") end end describe '#show_cursor' do - it 'shows the cursor with the command "10..6\n"' do - board.should_receive(:write).with "10..6\n" + it 'shows the cursor' do + board.should_receive(:write).with Dino::Message.encode(command: 10, value: 6) subject.show_cursor end end describe '#hide_cursor' do - it 'hides the cursor with the command "10..7\n"' do - board.should_receive(:write).with "10..7\n" + it 'hides the cursor' do + board.should_receive(:write).with Dino::Message.encode(command: 10, value: 7) subject.hide_cursor end end describe '#blink' do - it 'shows a blinking cursor with the command "10..8\n"' do - board.should_receive(:write).with "10..8\n" + it 'shows a blinking cursor' do + board.should_receive(:write).with Dino::Message.encode(command: 10, value: 8) subject.blink end end describe '#no_blink' do - it 'stops a blinking cursor with the command "10..9\n"' do - board.should_receive(:write).with "10..9\n" + it 'stops a blinking cursor' do + board.should_receive(:write).with Dino::Message.encode(command: 10, value: 9) subject.no_blink end end describe '#on' do - it 'turn on the display with the command "10..10\n"' do - board.should_receive(:write).with "10..10\n" + it 'turn on the display' do + board.should_receive(:write).with Dino::Message.encode(command: 10, value: 10) subject.on end end describe '#off' do - it 'turn off the display with the command "10..11\n"' do - board.should_receive(:write).with "10..11\n" + it 'turn off the display' do + board.should_receive(:write).with Dino::Message.encode(command: 10, value: 11) subject.off end end describe '#scroll_left' do - it 'move the text in the display one position to the left the command "10..12\n"' do - board.should_receive(:write).with "10..12\n" + it 'move the text in the display one position to the left' do + board.should_receive(:write).with Dino::Message.encode(command: 10, value: 12) subject.scroll_left end end describe '#scroll_right' do - it 'move the text in the display one position to the right the command "10..13\n"' do - board.should_receive(:write).with "10..13\n" + it 'move the text in the display one position to the right' do + board.should_receive(:write).with Dino::Message.encode(command: 10, value: 13) subject.scroll_right end end describe '#enable_autoscroll' do - it 'enable autoscroll with the command "10..14\n"' do - board.should_receive(:write).with "10..14\n" + it 'enable autoscroll' do + board.should_receive(:write).with Dino::Message.encode(command: 10, value: 14) subject.enable_autoscroll end end describe '#disable_autoscroll' do - it 'disable autoscroll with the command "10..15\n"' do - board.should_receive(:write).with "10..15\n" + it 'disable autoscroll' do + board.should_receive(:write).with Dino::Message.encode(command: 10, value: 15) subject.disable_autoscroll end end describe '#left_to_right' do - it 'set the display writing to start from the left with the command "10..16\n"' do - board.should_receive(:write).with "10..16\n" + it 'set the display writing to start from the left' do + board.should_receive(:write).with Dino::Message.encode(command: 10, value: 16) subject.left_to_right end end describe '#right_to_left' do - it 'set the display writing to start from the right with the command "10..17\n"' do - board.should_receive(:write).with "10..17\n" + it 'set the display writing to start from the right' do + board.should_receive(:write).with Dino::Message.encode(command: 10, value: 17) subject.right_to_left end end end end -end +end \ No newline at end of file diff --git a/spec/lib/components/led_spec.rb b/spec/lib/components/led_spec.rb index 845c9eee..32317773 100644 --- a/spec/lib/components/led_spec.rb +++ b/spec/lib/components/led_spec.rb @@ -3,6 +3,7 @@ module Dino module Components describe Led do + it 'should start blinking in a new thread at the given interval' end end end diff --git a/spec/lib/components/rgb_led_spec.rb b/spec/lib/components/rgb_led_spec.rb index 03e18fb6..ee31f4ab 100644 --- a/spec/lib/components/rgb_led_spec.rb +++ b/spec/lib/components/rgb_led_spec.rb @@ -12,9 +12,9 @@ module Components it 'should create a BaseOutput instance for each pin' do led = RgbLed.new(options) - led.red.class.should == Core::BaseOutput - led.green.class.should == Core::BaseOutput - led.blue.class.should == Core::BaseOutput + led.red.class.should == Basic::AnalogOutput + led.green.class.should == Basic::AnalogOutput + led.blue.class.should == Basic::AnalogOutput end end diff --git a/spec/lib/components/setup/base_spec.rb b/spec/lib/components/setup/base_spec.rb new file mode 100644 index 00000000..681a90a3 --- /dev/null +++ b/spec/lib/components/setup/base_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +module Dino + module Components + module Setup + describe Base do + let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } + let(:board) { Board.new(txrx) } + let(:options) { { pin: 'A0', board: board } } + class BaseComponent; include Base; end + subject { BaseComponent.new(options) } + + describe '#initialize' do + it 'should require a board' do + expect { + BaseComponent.new(pin: 'A0') + }.to raise_exception + end + + it 'should add itself to the board' do + board.should_receive(:add_component) + BaseComponent.new(options) + end + end + end + end + end +end diff --git a/spec/lib/components/setup/input_spec.rb b/spec/lib/components/setup/input_spec.rb new file mode 100644 index 00000000..7d7b47c5 --- /dev/null +++ b/spec/lib/components/setup/input_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +module Dino + module Components + module Setup + describe Input do + let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } + let(:board) { Board.new(txrx) } + let(:options) { { pin: 'A0', board: board, pullup: true} } + class InputComponent + include SinglePin + include Input + end + subject { InputComponent.new(options) } + + describe '#pullup=' do + it 'should tell the board to set the pullup mode correctly' do + subject + board.should_receive(:set_pullup).with(subject.pin, false) + subject.pullup = false + subject.pullup.should == false + end + end + + describe '#initialize_pins' do + it 'should set the pin mode to in' do + board.should_receive(:set_pin_mode).with(board.convert_pin(options[:pin]), :in) + subject + end + + it 'should set the pulllup if included in options' do + board.should_receive(:set_pullup).with(subject.pin, true) + subject + end + + it 'should tell the board to start reading' do + board.should_receive(:start_read) + subject + end + end + end + end + end +end diff --git a/spec/lib/components/setup/multi_pin_spec.rb b/spec/lib/components/setup/multi_pin_spec.rb new file mode 100644 index 00000000..e1fbe547 --- /dev/null +++ b/spec/lib/components/setup/multi_pin_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +module Dino + module Components + module Setup + describe MultiPin do + let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } + let(:board) { Board.new(txrx) } + let(:options) { { pins: {red: 9, blue: 10}, board: board } } + class MultiPinComponent; include MultiPin; end + subject { MultiPinComponent.new(options) } + + describe "::require_pins" do + it 'should add the pins to the @@required_pins class variable' + end + + describe "::proxy_pins" do + it 'should also call ::require_pins unless the pins are optional' + it 'should add the pins to the @@proxied_pins class variable' + end + + describe '#initialize' do + it 'should require every pin in the @@require_pin class variable' + it 'should create proxies for each of the pins in @@proxy_pins' + end + end + end + end +end diff --git a/spec/lib/components/setup/output_spec.rb b/spec/lib/components/setup/output_spec.rb new file mode 100644 index 00000000..9a85d28e --- /dev/null +++ b/spec/lib/components/setup/output_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +module Dino + module Components + module Setup + describe Input do + let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } + let(:board) { Board.new(txrx) } + let(:options) { { pin: 'A0', board: board, pullup: true} } + class OutputComponent + include SinglePin + include Output + end + subject { OutputComponent.new(options) } + + describe '#initialize_pins' do + it 'should set the pin mode to in' do + board.should_receive(:set_pin_mode).with(board.convert_pin(options[:pin]), :out) + subject + end + end + end + end + end +end diff --git a/spec/lib/components/setup/single_pin_spec.rb b/spec/lib/components/setup/single_pin_spec.rb new file mode 100644 index 00000000..34a57dc6 --- /dev/null +++ b/spec/lib/components/setup/single_pin_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +module Dino + module Components + module Setup + describe SinglePin do + let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } + let(:board) { Board.new(txrx) } + let(:options) { { pin: 'A0', board: board } } + class SinglePinComponent; include SinglePin; end + subject { SinglePinComponent.new(options) } + + describe '#initialize' do + it 'should require a pin' do + expect { + SinglePinComponent.new(board: board) + }.to raise_exception + end + + it 'should convert the pin to an integer' do + board.should_receive(:convert_pin).with(options[:pin]) + component = SinglePinComponent.new(options) + end + end + + describe '#mode=' do + it 'should tell the board to set the pin mode' do + board.should_receive(:set_pin_mode).with(subject.pin, :out) + + subject.send(:mode=, :out) + subject.mode.should == :out + end + end + end + end + end +end diff --git a/spec/lib/components/shift_register_spec.rb b/spec/lib/components/shift_register_spec.rb index dce07888..cd7f37dd 100644 --- a/spec/lib/components/shift_register_spec.rb +++ b/spec/lib/components/shift_register_spec.rb @@ -10,9 +10,9 @@ module Components describe '#initialize' do it 'should create a BaseOutput instance for each pin' do - subject.clock.class.should == Core::BaseOutput - subject.latch.class.should == Core::BaseOutput - subject.data.class.should == Core::BaseOutput + subject.clock.class.should == Basic::DigitalOutput + subject.latch.class.should == Basic::DigitalOutput + subject.data.class.should == Basic::DigitalOutput end end diff --git a/spec/lib/components/softwareserial_spec.rb b/spec/lib/components/softwareserial_spec.rb index f318a74f..e846e92b 100644 --- a/spec/lib/components/softwareserial_spec.rb +++ b/spec/lib/components/softwareserial_spec.rb @@ -3,7 +3,8 @@ module Dino module Components describe SoftwareSerial do - let(:board) { mock(:board, digital_write: true, set_pin_mode: true) } + let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } + let(:board) { Board.new(txrx) } subject { SoftwareSerial.new board: board, pins: { rx: 10, tx: 11 }, baud: 4800 } diff --git a/spec/lib/components/ssd_spec.rb b/spec/lib/components/ssd_spec.rb index ec39205b..f11e89ce 100644 --- a/spec/lib/components/ssd_spec.rb +++ b/spec/lib/components/ssd_spec.rb @@ -5,36 +5,33 @@ module Components describe SSD do let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } let(:board) { Board.new(txrx) } - let(:anode) { 11 } - let(:pins) { [12,13,3,4,5,10,9] } - subject { SSD.new(board: board, pins: pins, anode: anode) } + let(:pins) { {anode: 11, a: 12, b: 13, c: 3, d: 4, e: 5, f: 10, g: 9} } + subject { SSD.new(board: board, pins: pins) } describe '#initialize' do - it 'should raise if it does not receive an anode' do - expect { SSD.new(board: board, pins: pins) }.to raise_exception - end - - it 'should create a Core::BaseOutput for the anode and each pin' do + it 'should create a digital output for pins a-g' do subject - subject.anode.class.should == Core::BaseOutput - subject.pins.each { |pin| pin.class.should == Core::BaseOutput} + segments = [:a, :b, :c, :d, :e, :f, :g] + segments.each { |segment| subject.proxies[segment].class.should == Basic::DigitalOutput} end it "should clear the display" do SSD.any_instance.should_receive(:clear).once - SSD.new(board: board, pins: pins, anode: anode) + SSD.new(board: board, pins: pins) end it "should turn on the display" do SSD.any_instance.should_receive(:on).once - SSD.new(board: board, pins: pins, anode: anode) + SSD.new(board: board, pins: pins) end end describe '#clear' do it 'should toggle all the seven leds to off' do - subject.should_receive(:toggle).exactly(7).with(anything, 0) + segments = [:a, :b, :c, :d, :e, :f, :g] + segments.each { |segment| subject.proxies[segment].should_receive(:high) } + subject.clear end end @@ -55,7 +52,7 @@ module Components describe '#display' do it "should scroll on multiple characters" do - subject.should_receive(:scroll).with('FOO') + subject.should_receive(:scroll).with('foo') subject.display('foo') end @@ -68,11 +65,10 @@ module Components subject.should_receive(:clear) subject.display('+') end + end - it "should toggle all the segments in order to display a character" do - subject.should_receive(:toggle).exactly(7) - subject.display('7') - end + describe 'with a cathode' do + it 'should invert the logic' end end end diff --git a/spec/lib/components/stepper_spec.rb b/spec/lib/components/stepper_spec.rb index 082388a7..f5017711 100644 --- a/spec/lib/components/stepper_spec.rb +++ b/spec/lib/components/stepper_spec.rb @@ -11,8 +11,8 @@ module Components describe '#initialize' do it 'should create a BaseOutput instance for each pin' do - subject.step.class.should == Core::BaseOutput - subject.direction.class.should == Core::BaseOutput + subject.step.class.should == Basic::DigitalOutput + subject.direction.class.should == Basic::DigitalOutput end end From d05788e0eb2e6dbff345a2fbc16aa0e4d330601e Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Thu, 26 Dec 2013 15:45:23 -0400 Subject: [PATCH 052/296] Update some examples --- examples/ir_receiver/ir_receiver.rb | 2 +- examples/rgb_led/rgb_led.rb | 2 +- examples/ssd/ssd.rb | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/ir_receiver/ir_receiver.rb b/examples/ir_receiver/ir_receiver.rb index 8f82e520..3111f586 100644 --- a/examples/ir_receiver/ir_receiver.rb +++ b/examples/ir_receiver/ir_receiver.rb @@ -29,6 +29,6 @@ end sleep 4 -ir.flash(flash) +ir.on_flash(flash) sleep diff --git a/examples/rgb_led/rgb_led.rb b/examples/rgb_led/rgb_led.rb index 783ab52d..c72c4d51 100644 --- a/examples/rgb_led/rgb_led.rb +++ b/examples/rgb_led/rgb_led.rb @@ -12,7 +12,7 @@ delay = 500.0 -potentiometer.when_data_received do |data| +potentiometer.on_data do |data| sleep 0.5 puts "DATA: #{delay = data.to_i}" end diff --git a/examples/ssd/ssd.rb b/examples/ssd/ssd.rb index 236c602d..898e78b3 100644 --- a/examples/ssd/ssd.rb +++ b/examples/ssd/ssd.rb @@ -7,8 +7,7 @@ board = Dino::Board.new(Dino::TxRx::Serial.new) ssd = Dino::Components::SSD.new( board: board, - pins: [12,13,3,4,5,10,9], - anode: 11 + pins: { cathode: 10, a: 3, b: 4, c: 5, d: 6, e: 7, f: 8, g: 9 } ) # Turn off the ssd on exit From 313201f4c277a3ca9953670edc8dc0761be45af5 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Fri, 27 Dec 2013 01:17:31 -0400 Subject: [PATCH 053/296] Fix broken component tests --- .../lib/components/basic/analog_input_spec.rb | 3 +- .../components/basic/analog_output_spec.rb | 40 ++++++++++++ .../components/basic/digital_input_spec.rb | 3 +- .../components/basic/digital_output_spec.rb | 59 +++++++++++++++++ spec/lib/components/lcd_spec.rb | 3 +- spec/lib/components/rgb_led_spec.rb | 3 +- spec/lib/components/servo_spec.rb | 3 +- spec/lib/components/setup/base_spec.rb | 10 +-- spec/lib/components/setup/input_spec.rb | 4 +- spec/lib/components/setup/multi_pin_spec.rb | 65 ++++++++++++++++--- spec/lib/components/setup/output_spec.rb | 6 +- spec/lib/components/setup/single_pin_spec.rb | 8 ++- spec/lib/components/shift_register_spec.rb | 3 +- spec/lib/components/softwareserial_spec.rb | 3 +- spec/lib/components/ssd_spec.rb | 3 +- spec/lib/components/stepper_spec.rb | 3 +- spec/spec_helper.rb | 11 +++- 17 files changed, 189 insertions(+), 41 deletions(-) create mode 100644 spec/lib/components/basic/analog_output_spec.rb create mode 100644 spec/lib/components/basic/digital_output_spec.rb diff --git a/spec/lib/components/basic/analog_input_spec.rb b/spec/lib/components/basic/analog_input_spec.rb index 87f8c2b1..5b591fca 100644 --- a/spec/lib/components/basic/analog_input_spec.rb +++ b/spec/lib/components/basic/analog_input_spec.rb @@ -4,8 +4,7 @@ module Dino module Components module Basic describe AnalogInput do - let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } - let(:board) { Board.new(txrx) } + include BoardMock let(:options) { { pin: 'A0', board: board } } subject { AnalogInput.new(options) } diff --git a/spec/lib/components/basic/analog_output_spec.rb b/spec/lib/components/basic/analog_output_spec.rb new file mode 100644 index 00000000..0da24906 --- /dev/null +++ b/spec/lib/components/basic/analog_output_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +module Dino + module Components + module Basic + describe AnalogOutput do + include BoardMock + let(:options) { { pin: '9', board: board } } + subject { AnalogOutput.new(options) } + + describe '#analog_write' do + it 'should update the @state instance variable and call #analog_write on the board' do + board.should_receive(:analog_write).with(subject.pin, 128).once + + subject.analog_write(128) + subject.state.should == 128 + end + end + + describe '#write' do + it 'should call #digital_write if value is HIGH' do + subject.should_receive(:digital_write).with(board.high) + subject.write(board.high) + end + + + it 'should call #digital_write if value is LOW' do + subject.should_receive(:digital_write).with(board.low) + subject.write(board.low) + end + + it 'should call #analog_write if value is anything else' do + subject.should_receive(:analog_write).with(128) + subject.write(128) + end + end + end + end + end +end diff --git a/spec/lib/components/basic/digital_input_spec.rb b/spec/lib/components/basic/digital_input_spec.rb index 132d1c76..ef715cd9 100644 --- a/spec/lib/components/basic/digital_input_spec.rb +++ b/spec/lib/components/basic/digital_input_spec.rb @@ -4,8 +4,7 @@ module Dino module Components module Basic describe DigitalInput do - let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } - let(:board) { Board.new(txrx) } + include BoardMock let(:options) { { pin: 'A0', board: board } } subject { DigitalInput.new(options) } diff --git a/spec/lib/components/basic/digital_output_spec.rb b/spec/lib/components/basic/digital_output_spec.rb new file mode 100644 index 00000000..b378471e --- /dev/null +++ b/spec/lib/components/basic/digital_output_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' + +module Dino + module Components + module Basic + describe DigitalOutput do + include BoardMock + let(:options) { { pin: '13', board: board } } + subject { DigitalOutput.new(options) } + + describe '#after_initialize' do + it 'should set mode to out and go low' do + board.should_receive(:digital_write).with(13, board.low) + DigitalOutput.new(options) + end + end + + describe '#digital_write' do + it 'should update the @state instance variable and call #digital_write on the board' do + subject + board.should_receive(:digital_write).with(subject.pin, board.high).once + subject.digital_write(board.high) + subject.state.should == board.high + end + end + + describe '#high' do + it 'should call #digital_write with HIGH' do + subject.should_receive(:digital_write).with(board.high) + subject.high + end + end + + describe '#low' do + it 'should call #digital_write with LOW' do + subject.should_receive(:digital_write).with(board.low) + subject.low + end + end + + describe '#toggle' do + it 'should call high if currently LOW' do + subject.low + subject.should_receive(:high) + + subject.toggle + end + + it 'should call LOW if anything else' do + subject.high + subject.should_receive(:low) + + subject.toggle + end + end + end + end + end +end diff --git a/spec/lib/components/lcd_spec.rb b/spec/lib/components/lcd_spec.rb index 86b7468d..b6bf5509 100644 --- a/spec/lib/components/lcd_spec.rb +++ b/spec/lib/components/lcd_spec.rb @@ -3,8 +3,7 @@ module Dino module Components describe LCD do - let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } - let(:board) { Board.new(txrx) } + include BoardMock subject { LCD.new board: board, pins: { rs: 12, enable: 11, d4: 5, d5: 4, d6: 3, d7: 2 }, cols: 16, rows: 2 } before do diff --git a/spec/lib/components/rgb_led_spec.rb b/spec/lib/components/rgb_led_spec.rb index ee31f4ab..22dd8088 100644 --- a/spec/lib/components/rgb_led_spec.rb +++ b/spec/lib/components/rgb_led_spec.rb @@ -3,8 +3,7 @@ module Dino module Components describe RgbLed do - let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } - let(:board) { Board.new(txrx) } + include BoardMock let(:options) { { board: board, pins: {red: 1, green: 2, blue: 3} } } subject { RgbLed.new(options) } diff --git a/spec/lib/components/servo_spec.rb b/spec/lib/components/servo_spec.rb index 10639774..a8225e9c 100644 --- a/spec/lib/components/servo_spec.rb +++ b/spec/lib/components/servo_spec.rb @@ -3,8 +3,7 @@ module Dino module Components describe Servo do - let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } - let(:board) { Board.new(txrx) } + include BoardMock let(:options) { { board: board, pin: 9 } } subject { Servo.new(options) } diff --git a/spec/lib/components/setup/base_spec.rb b/spec/lib/components/setup/base_spec.rb index 681a90a3..ddf19f8e 100644 --- a/spec/lib/components/setup/base_spec.rb +++ b/spec/lib/components/setup/base_spec.rb @@ -4,10 +4,12 @@ module Dino module Components module Setup describe Base do - let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } - let(:board) { Board.new(txrx) } + include BoardMock let(:options) { { pin: 'A0', board: board } } - class BaseComponent; include Base; end + + class BaseComponent + include Base + end subject { BaseComponent.new(options) } describe '#initialize' do @@ -17,7 +19,7 @@ class BaseComponent; include Base; end }.to raise_exception end - it 'should add itself to the board' do + it 'should register itself as a component on the board' do board.should_receive(:add_component) BaseComponent.new(options) end diff --git a/spec/lib/components/setup/input_spec.rb b/spec/lib/components/setup/input_spec.rb index 7d7b47c5..ae86375f 100644 --- a/spec/lib/components/setup/input_spec.rb +++ b/spec/lib/components/setup/input_spec.rb @@ -4,9 +4,9 @@ module Dino module Components module Setup describe Input do - let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } - let(:board) { Board.new(txrx) } + include BoardMock let(:options) { { pin: 'A0', board: board, pullup: true} } + class InputComponent include SinglePin include Input diff --git a/spec/lib/components/setup/multi_pin_spec.rb b/spec/lib/components/setup/multi_pin_spec.rb index e1fbe547..db4b4fad 100644 --- a/spec/lib/components/setup/multi_pin_spec.rb +++ b/spec/lib/components/setup/multi_pin_spec.rb @@ -4,24 +4,69 @@ module Dino module Components module Setup describe MultiPin do - let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } - let(:board) { Board.new(txrx) } - let(:options) { { pins: {red: 9, blue: 10}, board: board } } - class MultiPinComponent; include MultiPin; end + include BoardMock + let(:options) { { pins: {one: 9, two: 10, maybe: 11}, board: board } } + + class MultiPinComponent + include MultiPin + require_pin :one + proxy_pin two: Basic::DigitalOutput + proxy_pin maybe: Basic::DigitalInput, optional: true + end subject { MultiPinComponent.new(options) } describe "::require_pins" do - it 'should add the pins to the @@required_pins class variable' + it 'should add required pins to the @@required_pins class variable' do + MultiPinComponent.class_eval('@@required_pins').should == [:one, :two] + end end describe "::proxy_pins" do - it 'should also call ::require_pins unless the pins are optional' - it 'should add the pins to the @@proxied_pins class variable' + it 'should automatically require the pin unless :optional is true' do + MultiPinComponent.class_eval('@@required_pins').should == [:one, :two] + end + + it 'should add the pins to the @@proxied_pins class variable with the right classes' do + MultiPinComponent.class_eval('@@proxied_pins').should == {two: Basic::DigitalOutput, maybe: Basic::DigitalInput} + end + end + + describe '#validate_pins' do + it 'should raise an error for any required pin that is missing' do + expect { + MultiPinComponent.new(board: board, pins: {one: 9, maybe: 11}) + }.to raise_exception(/two/) + end + + it 'should not raise errors for optional pins that are missing' do + expect { + MultiPinComponent.new(board: board, pins: {one: 9, two: 10}) + }.to_not raise_exception + end + end + + describe '#build_proxies' do + it 'should create the correct proxy subcomponents' do + subject.proxies[:two].class.should == Basic::DigitalOutput + subject.proxies[:maybe].class.should == Basic::DigitalInput + end + + it 'should create an attr_accessor for each proxy' do + subject.two.class.should == Basic::DigitalOutput + subject.maybe.class.should == Basic::DigitalInput + end + + it 'should attach the subcomponents to the right pins' do + subject.two.pin.should == 10 + subject.maybe.pin.should == 11 + end end - describe '#initialize' do - it 'should require every pin in the @@require_pin class variable' - it 'should create proxies for each of the pins in @@proxy_pins' + describe '#states' do + it 'should return a hash with the state of each subcomponent' do + subject.two.high + subject.state.should == {two: board.high, maybe: nil} + end end end end diff --git a/spec/lib/components/setup/output_spec.rb b/spec/lib/components/setup/output_spec.rb index 9a85d28e..d2925487 100644 --- a/spec/lib/components/setup/output_spec.rb +++ b/spec/lib/components/setup/output_spec.rb @@ -4,9 +4,9 @@ module Dino module Components module Setup describe Input do - let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } - let(:board) { Board.new(txrx) } + include BoardMock let(:options) { { pin: 'A0', board: board, pullup: true} } + class OutputComponent include SinglePin include Output @@ -14,7 +14,7 @@ class OutputComponent subject { OutputComponent.new(options) } describe '#initialize_pins' do - it 'should set the pin mode to in' do + it 'should set the pin mode to out' do board.should_receive(:set_pin_mode).with(board.convert_pin(options[:pin]), :out) subject end diff --git a/spec/lib/components/setup/single_pin_spec.rb b/spec/lib/components/setup/single_pin_spec.rb index 34a57dc6..9e7a70e2 100644 --- a/spec/lib/components/setup/single_pin_spec.rb +++ b/spec/lib/components/setup/single_pin_spec.rb @@ -4,10 +4,12 @@ module Dino module Components module Setup describe SinglePin do - let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } - let(:board) { Board.new(txrx) } + include BoardMock let(:options) { { pin: 'A0', board: board } } - class SinglePinComponent; include SinglePin; end + + class SinglePinComponent + include SinglePin + end subject { SinglePinComponent.new(options) } describe '#initialize' do diff --git a/spec/lib/components/shift_register_spec.rb b/spec/lib/components/shift_register_spec.rb index cd7f37dd..826f91ce 100644 --- a/spec/lib/components/shift_register_spec.rb +++ b/spec/lib/components/shift_register_spec.rb @@ -3,8 +3,7 @@ module Dino module Components describe ShiftRegister do - let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } - let(:board) { Board.new(txrx) } + include BoardMock let(:options) { { board: board, pins: {clock: 12, data: 11, latch: 8} } } subject { ShiftRegister.new(options) } diff --git a/spec/lib/components/softwareserial_spec.rb b/spec/lib/components/softwareserial_spec.rb index e846e92b..05225b86 100644 --- a/spec/lib/components/softwareserial_spec.rb +++ b/spec/lib/components/softwareserial_spec.rb @@ -3,8 +3,7 @@ module Dino module Components describe SoftwareSerial do - let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } - let(:board) { Board.new(txrx) } + include BoardMock subject { SoftwareSerial.new board: board, pins: { rx: 10, tx: 11 }, baud: 4800 } diff --git a/spec/lib/components/ssd_spec.rb b/spec/lib/components/ssd_spec.rb index f11e89ce..fd3d6367 100644 --- a/spec/lib/components/ssd_spec.rb +++ b/spec/lib/components/ssd_spec.rb @@ -3,8 +3,7 @@ module Dino module Components describe SSD do - let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } - let(:board) { Board.new(txrx) } + include BoardMock let(:pins) { {anode: 11, a: 12, b: 13, c: 3, d: 4, e: 5, f: 10, g: 9} } subject { SSD.new(board: board, pins: pins) } diff --git a/spec/lib/components/stepper_spec.rb b/spec/lib/components/stepper_spec.rb index f5017711..673420aa 100644 --- a/spec/lib/components/stepper_spec.rb +++ b/spec/lib/components/stepper_spec.rb @@ -3,8 +3,7 @@ module Dino module Components describe Stepper do - let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } - let(:board) { Board.new(txrx) } + include BoardMock let(:options) { { pins: {step: 9, direction: 10}, board: board } } subject { Stepper.new(options) } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3ed69bc3..48219c57 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -9,4 +9,13 @@ def self.redefine(const, value, opts={}) opts[:on].send(:remove_const, const) if self.class.const_defined?(const) opts[:on].const_set(const, value) end -end \ No newline at end of file +end + +module BoardMock + def self.included(base) + base.class_eval do + let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } + let(:board) { Dino::Board.new(txrx) } + end + end +end From a6fd739b30260800ee3732af54578413dc6135c1 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Wed, 8 Jan 2014 08:32:23 -0400 Subject: [PATCH 054/296] Make DHT sensors work with recent changes. --- lib/dino/components/dht.rb | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/lib/dino/components/dht.rb b/lib/dino/components/dht.rb index aa53543d..872e54b1 100644 --- a/lib/dino/components/dht.rb +++ b/lib/dino/components/dht.rb @@ -1,11 +1,10 @@ module Dino module Components module DHT - class Temperature < Core::BaseInput - # - # These components work too slowly to poll often enough for a listener. - # - def listen; end + class Temperature + include Setup::SinglePin + include Setup::Input + include Mixins::Poller def _read board.dht_read(self.pin, 0) @@ -18,11 +17,10 @@ def update(data) end end - class Humidity < Core::BaseInput - # - # These components work too slowly to poll often enough for a listener. - # - def listen; end + class Humidity + include Setup::SinglePin + include Setup::Input + include Mixins::Poller def _read board.dht_read(self.pin, 1) From 2dc293ce657448d74a68490cfa6f78de67835690 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Wed, 8 Jan 2014 09:05:33 -0400 Subject: [PATCH 055/296] Model shift register as a drop-in replacement for Board class. Added SSD example. --- examples/shift_register.rb | 17 ++++++++-- lib/dino/components/shift_register.rb | 45 +++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/examples/shift_register.rb b/examples/shift_register.rb index fe5945b6..ab916143 100644 --- a/examples/shift_register.rb +++ b/examples/shift_register.rb @@ -8,10 +8,21 @@ board = Dino::Board.new(Dino::TxRx::Serial.new) shift_register = Dino::Components::ShiftRegister.new(pins: {data: 11, latch: 8, clock: 12}, board: board) +ssd = Dino::Components::SSD.new( + board: shift_register, + pins: { cathode: 0, a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7 } +) + +# Turn off the ssd on exit +trap("SIGINT") { exit !ssd.off } + +# Display each new line on the ssd +loop { ssd.display(gets.chomp) } + # Write a single byte -shift_register.write(255) +# shift_register.write_byte(255) # Write an array of bytes (for multiple registers). -shift_register.write([255, 0]) +# shift_register.write_bytes([255, 0]) -sleep +# sleep diff --git a/lib/dino/components/shift_register.rb b/lib/dino/components/shift_register.rb index 85730234..9eb6ccd1 100644 --- a/lib/dino/components/shift_register.rb +++ b/lib/dino/components/shift_register.rb @@ -8,8 +8,47 @@ class ShiftRegister proxy_pins clock: Basic::DigitalOutput, latch: Basic::DigitalOutput, data: Basic::DigitalOutput - - def write(bytes) + + attr_reader :high, :low, :components + + def after_initialize(options={}) + @high = 1 + @low = 0 + @components = [] + + @state = [0,0,0,0,0,0,0,0] + write_state + end + + def add_component(component) + @components << component + end + + def remove_component(component) + @components.delete(component) + end + + def convert_pin(pin) + pin = pin.to_i + end + + def set_pin_mode(pin, mode) + nil + end + + def digital_write(pin, value) + @state[pin] = value + write_state + end + + alias :write :digital_write + + def write_state + byte = @state.join("").reverse.to_i(2) + write_bytes(byte) + end + + def write_bytes(bytes) bytes = [bytes] unless bytes.class == Array latch.low @@ -18,6 +57,8 @@ def write(bytes) end latch.high end + + alias :write_byte :write_bytes end end end From c9f9d7f05e4e4bebc3cc8237afb48b1328d2ea74 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Mon, 13 Jan 2014 12:26:10 -0400 Subject: [PATCH 056/296] Put back the 3 second sleep for windows --- lib/dino/tx_rx/serial.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/dino/tx_rx/serial.rb b/lib/dino/tx_rx/serial.rb index c52ff7c0..f1e2b4a9 100644 --- a/lib/dino/tx_rx/serial.rb +++ b/lib/dino/tx_rx/serial.rb @@ -15,6 +15,12 @@ def io @io ||= connect end + def handshake + io + sleep 3 if on_windows? + super + end + private def connect From d37e9523f6cad38a57517bdd832340533c0bd52e Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Mon, 24 Feb 2014 23:52:02 -0400 Subject: [PATCH 057/296] Quick and dirty Dallas temp sensor implementation --- examples/one_wire/ds18b20.rb | 15 + lib/dino/board.rb | 3 +- lib/dino/components.rb | 1 + lib/dino/components/ds18b20.rb | 13 + lib/dino_cli/generator.rb | 2 +- src/dino_ethernet.ino | 1 + src/dino_serial.ino | 1 + src/dino_wifi.ino | 1 + src/lib/Dino.cpp | 49 +++ src/lib/Dino.h | 2 + src/lib/OneWire.cpp | 557 +++++++++++++++++++++++++++++++++ src/lib/OneWire.h | 229 ++++++++++++++ 12 files changed, 872 insertions(+), 2 deletions(-) create mode 100644 examples/one_wire/ds18b20.rb create mode 100644 lib/dino/components/ds18b20.rb create mode 100644 src/lib/OneWire.cpp create mode 100644 src/lib/OneWire.h diff --git a/examples/one_wire/ds18b20.rb b/examples/one_wire/ds18b20.rb new file mode 100644 index 00000000..9a7ae8a4 --- /dev/null +++ b/examples/one_wire/ds18b20.rb @@ -0,0 +1,15 @@ +# +# Example of how to use the Dallas DS18B20 temperature sensor. +# +require 'bundler/setup' +require 'dino' + +board = Dino::Board.new(Dino::TxRx::Serial.new) + +# The temperature and humidity functions of the DHT sensors are +# modelled separately, but both isntances can be set up on the same pin. +temp = Dino::Components::DS18B20.new(pin: 7, board: board) + +temp.read do |temperature| + puts "The temperature is #{temperature} degrees C" +end diff --git a/lib/dino/board.rb b/lib/dino/board.rb index b80c9459..e3a1c95e 100644 --- a/lib/dino/board.rb +++ b/lib/dino/board.rb @@ -81,7 +81,8 @@ def set_pullup(pin, pullup) stop_listener: '7', servo_toggle: '8', servo_write: '9', - dht_read: '13' + dht_read: '13', + ds18b20_read: '15' } PIN_COMMANDS.each_key do |command| diff --git a/lib/dino/components.rb b/lib/dino/components.rb index d2f8c5da..bdbc86d7 100644 --- a/lib/dino/components.rb +++ b/lib/dino/components.rb @@ -16,5 +16,6 @@ module Components autoload :Relay, 'dino/components/relay' autoload :SoftwareSerial, 'dino/components/softserial' autoload :DHT, 'dino/components/dht' + autoload :DS18B20, 'dino/components/ds18b20' end end diff --git a/lib/dino/components/ds18b20.rb b/lib/dino/components/ds18b20.rb new file mode 100644 index 00000000..0ee1d385 --- /dev/null +++ b/lib/dino/components/ds18b20.rb @@ -0,0 +1,13 @@ +module Dino + module Components + class DS18B20 + include Setup::SinglePin + include Setup::Input + include Mixins::Poller + + def _read + board.ds18b20_read(self.pin, 0) + end + end + end +end diff --git a/lib/dino_cli/generator.rb b/lib/dino_cli/generator.rb index b5a234fc..89d71a63 100644 --- a/lib/dino_cli/generator.rb +++ b/lib/dino_cli/generator.rb @@ -1,6 +1,6 @@ class DinoCLI::Generator require "fileutils" - LIB_FILENAMES = ["Dino.h", "Dino.cpp", "DinoLCD.h", "DinoLCD.cpp", "DinoSerial.cpp","DinoSerial.h", "DHT.cpp", "DHT.h"] + LIB_FILENAMES = ["Dino.h", "Dino.cpp", "DinoLCD.h", "DinoLCD.cpp", "DinoSerial.cpp","DinoSerial.h", "DHT.cpp", "DHT.h", "OneWire.h", "OneWire.cpp"] attr_accessor :options def initialize(options={}) diff --git a/src/dino_ethernet.ino b/src/dino_ethernet.ino index 1091c5db..737c2040 100644 --- a/src/dino_ethernet.ino +++ b/src/dino_ethernet.ino @@ -4,6 +4,7 @@ #include #include #include "DHT.h" +#include "OneWire.h" // SoftwareSerial doesn't work on the Due yet. #if !defined(__SAM3X8E__) diff --git a/src/dino_serial.ino b/src/dino_serial.ino index f3edb943..e276e5d3 100644 --- a/src/dino_serial.ino +++ b/src/dino_serial.ino @@ -2,6 +2,7 @@ #include #include #include "DHT.h" +#include "OneWire.h" // SoftwareSerial doesn't work on the Due yet. #if !defined(__SAM3X8E__) diff --git a/src/dino_wifi.ino b/src/dino_wifi.ino index 50a16d3a..3a99e701 100644 --- a/src/dino_wifi.ino +++ b/src/dino_wifi.ino @@ -4,6 +4,7 @@ #include #include #include "DHT.h" +#include "OneWire.h" // SoftwareSerial doesn't work on the Due yet. #if !defined(__SAM3X8E__) diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index eace6ba6..404c05ea 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -87,6 +87,7 @@ void Dino::process() { case 11: shiftWrite (); break; case 12: handleSerial (); break; case 13: handleDHT (); break; + case 15: ds18Read (); break; case 90: reset (); break; case 96: setAnalogResolution (); break; case 97: setAnalogDivider (); break; @@ -321,6 +322,54 @@ void Dino::handleDHT() { #endif } +void Dino::ds18Read() { + OneWire ds(pin); + + byte data[12]; + byte addr[8]; + + if ( !ds.search(addr)) { + ds.reset_search(); + return; + } + + if ( OneWire::crc8( addr, 7) != addr[7]) { + // Serial.println("CRC is not valid!"); + return; + } + + if ( addr[0] != 0x10 && addr[0] != 0x28) { + // Serial.print("Device is not recognized"); + return; + } + + ds.reset(); + ds.select(addr); + ds.write(0x44,1); // start conversion, with parasite power on at the end + + byte present = ds.reset(); + ds.select(addr); + ds.write(0xBE); // Read Scratchpad + + for (int i = 0; i < 9; i++) { // we need 9 bytes + data[i] = ds.read(); + } + + ds.reset_search(); + + byte MSB = data[1]; + byte LSB = data[0]; + + float tempRead = ((MSB << 8) | LSB); //using two's compliment + float reading = tempRead / 16; + char readingBuff[10]; + + if (! isnan(reading)) { + dtostrf(reading, 6, 4, readingBuff); + sprintf(response, "%d:%s", pin, readingBuff); + } +} + // CMD = 90 void Dino::reset() { diff --git a/src/lib/Dino.h b/src/lib/Dino.h index 7164eb21..eb0364bf 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -9,6 +9,7 @@ #include #include "DinoLCD.h" #include "DHT.h" +#include "OneWire.h" // SoftwareSerial doesn't work on the Due yet. #if !defined(__SAM3X8E__) @@ -54,6 +55,7 @@ class Dino { void shiftWrite (); //cmd = 11 void handleSerial (); //cmd = 12 void handleDHT (); //cmd = 13 + void ds18Read (); //cmd = 15 void reset (); //cmd = 90 void setAnalogResolution (); //cmd = 96 void setAnalogDivider (); //cmd = 97 diff --git a/src/lib/OneWire.cpp b/src/lib/OneWire.cpp new file mode 100644 index 00000000..631813f8 --- /dev/null +++ b/src/lib/OneWire.cpp @@ -0,0 +1,557 @@ +/* +Copyright (c) 2007, Jim Studt (original old version - many contributors since) + +The latest version of this library may be found at: + http://www.pjrc.com/teensy/td_libs_OneWire.html + +OneWire has been maintained by Paul Stoffregen (paul@pjrc.com) since +January 2010. At the time, it was in need of many bug fixes, but had +been abandoned the original author (Jim Studt). None of the known +contributors were interested in maintaining OneWire. Paul typically +works on OneWire every 6 to 12 months. Patches usually wait that +long. If anyone is interested in more actively maintaining OneWire, +please contact Paul. + +Version 2.2: + Teensy 3.0 compatibility, Paul Stoffregen, paul@pjrc.com + Arduino Due compatibility, http://arduino.cc/forum/index.php?topic=141030 + Fix DS18B20 example negative temperature + Fix DS18B20 example's low res modes, Ken Butcher + Improve reset timing, Mark Tillotson + Add const qualifiers, Bertrik Sikken + Add initial value input to crc16, Bertrik Sikken + Add target_search() function, Scott Roberts + +Version 2.1: + Arduino 1.0 compatibility, Paul Stoffregen + Improve temperature example, Paul Stoffregen + DS250x_PROM example, Guillermo Lovato + PIC32 (chipKit) compatibility, Jason Dangel, dangel.jason AT gmail.com + Improvements from Glenn Trewitt: + - crc16() now works + - check_crc16() does all of calculation/checking work. + - Added read_bytes() and write_bytes(), to reduce tedious loops. + - Added ds2408 example. + Delete very old, out-of-date readme file (info is here) + +Version 2.0: Modifications by Paul Stoffregen, January 2010: +http://www.pjrc.com/teensy/td_libs_OneWire.html + Search fix from Robin James + http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27 + Use direct optimized I/O in all cases + Disable interrupts during timing critical sections + (this solves many random communication errors) + Disable interrupts during read-modify-write I/O + Reduce RAM consumption by eliminating unnecessary + variables and trimming many to 8 bits + Optimize both crc8 - table version moved to flash + +Modified to work with larger numbers of devices - avoids loop. +Tested in Arduino 11 alpha with 12 sensors. +26 Sept 2008 -- Robin James +http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27 + +Updated to work with arduino-0008 and to include skip() as of +2007/07/06. --RJL20 + +Modified to calculate the 8-bit CRC directly, avoiding the need for +the 256-byte lookup table to be loaded in RAM. Tested in arduino-0010 +-- Tom Pollard, Jan 23, 2008 + +Jim Studt's original library was modified by Josh Larios. + +Tom Pollard, pollard@alum.mit.edu, contributed around May 20, 2008 + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Much of the code was inspired by Derek Yerger's code, though I don't +think much of that remains. In any event that was.. + (copyleft) 2006 by Derek Yerger - Free to distribute freely. + +The CRC code was excerpted and inspired by the Dallas Semiconductor +sample code bearing this copyright. +//--------------------------------------------------------------------------- +// Copyright (C) 2000 Dallas Semiconductor Corporation, All Rights Reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES +// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// Except as contained in this notice, the name of Dallas Semiconductor +// shall not be used except as stated in the Dallas Semiconductor +// Branding Policy. +//-------------------------------------------------------------------------- +*/ + +#include "OneWire.h" + + +OneWire::OneWire(uint8_t pin) +{ + pinMode(pin, INPUT); + bitmask = PIN_TO_BITMASK(pin); + baseReg = PIN_TO_BASEREG(pin); +#if ONEWIRE_SEARCH + reset_search(); +#endif +} + + +// Perform the onewire reset function. We will wait up to 250uS for +// the bus to come high, if it doesn't then it is broken or shorted +// and we return a 0; +// +// Returns 1 if a device asserted a presence pulse, 0 otherwise. +// +uint8_t OneWire::reset(void) +{ + IO_REG_TYPE mask = bitmask; + volatile IO_REG_TYPE *reg IO_REG_ASM = baseReg; + uint8_t r; + uint8_t retries = 125; + + noInterrupts(); + DIRECT_MODE_INPUT(reg, mask); + interrupts(); + // wait until the wire is high... just in case + do { + if (--retries == 0) return 0; + delayMicroseconds(2); + } while ( !DIRECT_READ(reg, mask)); + + noInterrupts(); + DIRECT_WRITE_LOW(reg, mask); + DIRECT_MODE_OUTPUT(reg, mask); // drive output low + interrupts(); + delayMicroseconds(480); + noInterrupts(); + DIRECT_MODE_INPUT(reg, mask); // allow it to float + delayMicroseconds(70); + r = !DIRECT_READ(reg, mask); + interrupts(); + delayMicroseconds(410); + return r; +} + +// +// Write a bit. Port and bit is used to cut lookup time and provide +// more certain timing. +// +void OneWire::write_bit(uint8_t v) +{ + IO_REG_TYPE mask=bitmask; + volatile IO_REG_TYPE *reg IO_REG_ASM = baseReg; + + if (v & 1) { + noInterrupts(); + DIRECT_WRITE_LOW(reg, mask); + DIRECT_MODE_OUTPUT(reg, mask); // drive output low + delayMicroseconds(10); + DIRECT_WRITE_HIGH(reg, mask); // drive output high + interrupts(); + delayMicroseconds(55); + } else { + noInterrupts(); + DIRECT_WRITE_LOW(reg, mask); + DIRECT_MODE_OUTPUT(reg, mask); // drive output low + delayMicroseconds(65); + DIRECT_WRITE_HIGH(reg, mask); // drive output high + interrupts(); + delayMicroseconds(5); + } +} + +// +// Read a bit. Port and bit is used to cut lookup time and provide +// more certain timing. +// +uint8_t OneWire::read_bit(void) +{ + IO_REG_TYPE mask=bitmask; + volatile IO_REG_TYPE *reg IO_REG_ASM = baseReg; + uint8_t r; + + noInterrupts(); + DIRECT_MODE_OUTPUT(reg, mask); + DIRECT_WRITE_LOW(reg, mask); + delayMicroseconds(3); + DIRECT_MODE_INPUT(reg, mask); // let pin float, pull up will raise + delayMicroseconds(10); + r = DIRECT_READ(reg, mask); + interrupts(); + delayMicroseconds(53); + return r; +} + +// +// Write a byte. The writing code uses the active drivers to raise the +// pin high, if you need power after the write (e.g. DS18S20 in +// parasite power mode) then set 'power' to 1, otherwise the pin will +// go tri-state at the end of the write to avoid heating in a short or +// other mishap. +// +void OneWire::write(uint8_t v, uint8_t power /* = 0 */) { + uint8_t bitMask; + + for (bitMask = 0x01; bitMask; bitMask <<= 1) { + OneWire::write_bit( (bitMask & v)?1:0); + } + if ( !power) { + noInterrupts(); + DIRECT_MODE_INPUT(baseReg, bitmask); + DIRECT_WRITE_LOW(baseReg, bitmask); + interrupts(); + } +} + +void OneWire::write_bytes(const uint8_t *buf, uint16_t count, bool power /* = 0 */) { + for (uint16_t i = 0 ; i < count ; i++) + write(buf[i]); + if (!power) { + noInterrupts(); + DIRECT_MODE_INPUT(baseReg, bitmask); + DIRECT_WRITE_LOW(baseReg, bitmask); + interrupts(); + } +} + +// +// Read a byte +// +uint8_t OneWire::read() { + uint8_t bitMask; + uint8_t r = 0; + + for (bitMask = 0x01; bitMask; bitMask <<= 1) { + if ( OneWire::read_bit()) r |= bitMask; + } + return r; +} + +void OneWire::read_bytes(uint8_t *buf, uint16_t count) { + for (uint16_t i = 0 ; i < count ; i++) + buf[i] = read(); +} + +// +// Do a ROM select +// +void OneWire::select(const uint8_t rom[8]) +{ + uint8_t i; + + write(0x55); // Choose ROM + + for (i = 0; i < 8; i++) write(rom[i]); +} + +// +// Do a ROM skip +// +void OneWire::skip() +{ + write(0xCC); // Skip ROM +} + +void OneWire::depower() +{ + noInterrupts(); + DIRECT_MODE_INPUT(baseReg, bitmask); + interrupts(); +} + +#if ONEWIRE_SEARCH + +// +// You need to use this function to start a search again from the beginning. +// You do not need to do it for the first search, though you could. +// +void OneWire::reset_search() +{ + // reset the search state + LastDiscrepancy = 0; + LastDeviceFlag = FALSE; + LastFamilyDiscrepancy = 0; + for(int i = 7; ; i--) { + ROM_NO[i] = 0; + if ( i == 0) break; + } +} + +// Setup the search to find the device type 'family_code' on the next call +// to search(*newAddr) if it is present. +// +void OneWire::target_search(uint8_t family_code) +{ + // set the search state to find SearchFamily type devices + ROM_NO[0] = family_code; + for (uint8_t i = 1; i < 8; i++) + ROM_NO[i] = 0; + LastDiscrepancy = 64; + LastFamilyDiscrepancy = 0; + LastDeviceFlag = FALSE; +} + +// +// Perform a search. If this function returns a '1' then it has +// enumerated the next device and you may retrieve the ROM from the +// OneWire::address variable. If there are no devices, no further +// devices, or something horrible happens in the middle of the +// enumeration then a 0 is returned. If a new device is found then +// its address is copied to newAddr. Use OneWire::reset_search() to +// start over. +// +// --- Replaced by the one from the Dallas Semiconductor web site --- +//-------------------------------------------------------------------------- +// Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing +// search state. +// Return TRUE : device found, ROM number in ROM_NO buffer +// FALSE : device not found, end of search +// +uint8_t OneWire::search(uint8_t *newAddr) +{ + uint8_t id_bit_number; + uint8_t last_zero, rom_byte_number, search_result; + uint8_t id_bit, cmp_id_bit; + + unsigned char rom_byte_mask, search_direction; + + // initialize for search + id_bit_number = 1; + last_zero = 0; + rom_byte_number = 0; + rom_byte_mask = 1; + search_result = 0; + + // if the last call was not the last one + if (!LastDeviceFlag) + { + // 1-Wire reset + if (!reset()) + { + // reset the search + LastDiscrepancy = 0; + LastDeviceFlag = FALSE; + LastFamilyDiscrepancy = 0; + return FALSE; + } + + // issue the search command + write(0xF0); + + // loop to do the search + do + { + // read a bit and its complement + id_bit = read_bit(); + cmp_id_bit = read_bit(); + + // check for no devices on 1-wire + if ((id_bit == 1) && (cmp_id_bit == 1)) + break; + else + { + // all devices coupled have 0 or 1 + if (id_bit != cmp_id_bit) + search_direction = id_bit; // bit write value for search + else + { + // if this discrepancy if before the Last Discrepancy + // on a previous next then pick the same as last time + if (id_bit_number < LastDiscrepancy) + search_direction = ((ROM_NO[rom_byte_number] & rom_byte_mask) > 0); + else + // if equal to last pick 1, if not then pick 0 + search_direction = (id_bit_number == LastDiscrepancy); + + // if 0 was picked then record its position in LastZero + if (search_direction == 0) + { + last_zero = id_bit_number; + + // check for Last discrepancy in family + if (last_zero < 9) + LastFamilyDiscrepancy = last_zero; + } + } + + // set or clear the bit in the ROM byte rom_byte_number + // with mask rom_byte_mask + if (search_direction == 1) + ROM_NO[rom_byte_number] |= rom_byte_mask; + else + ROM_NO[rom_byte_number] &= ~rom_byte_mask; + + // serial number search direction write bit + write_bit(search_direction); + + // increment the byte counter id_bit_number + // and shift the mask rom_byte_mask + id_bit_number++; + rom_byte_mask <<= 1; + + // if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask + if (rom_byte_mask == 0) + { + rom_byte_number++; + rom_byte_mask = 1; + } + } + } + while(rom_byte_number < 8); // loop until through all ROM bytes 0-7 + + // if the search was successful then + if (!(id_bit_number < 65)) + { + // search successful so set LastDiscrepancy,LastDeviceFlag,search_result + LastDiscrepancy = last_zero; + + // check for last device + if (LastDiscrepancy == 0) + LastDeviceFlag = TRUE; + + search_result = TRUE; + } + } + + // if no device found then reset counters so next 'search' will be like a first + if (!search_result || !ROM_NO[0]) + { + LastDiscrepancy = 0; + LastDeviceFlag = FALSE; + LastFamilyDiscrepancy = 0; + search_result = FALSE; + } + for (int i = 0; i < 8; i++) newAddr[i] = ROM_NO[i]; + return search_result; + } + +#endif + +#if ONEWIRE_CRC +// The 1-Wire CRC scheme is described in Maxim Application Note 27: +// "Understanding and Using Cyclic Redundancy Checks with Maxim iButton Products" +// + +#if ONEWIRE_CRC8_TABLE +// This table comes from Dallas sample code where it is freely reusable, +// though Copyright (C) 2000 Dallas Semiconductor Corporation +static const uint8_t PROGMEM dscrc_table[] = { + 0, 94,188,226, 97, 63,221,131,194,156,126, 32,163,253, 31, 65, + 157,195, 33,127,252,162, 64, 30, 95, 1,227,189, 62, 96,130,220, + 35,125,159,193, 66, 28,254,160,225,191, 93, 3,128,222, 60, 98, + 190,224, 2, 92,223,129, 99, 61,124, 34,192,158, 29, 67,161,255, + 70, 24,250,164, 39,121,155,197,132,218, 56,102,229,187, 89, 7, + 219,133,103, 57,186,228, 6, 88, 25, 71,165,251,120, 38,196,154, + 101, 59,217,135, 4, 90,184,230,167,249, 27, 69,198,152,122, 36, + 248,166, 68, 26,153,199, 37,123, 58,100,134,216, 91, 5,231,185, + 140,210, 48,110,237,179, 81, 15, 78, 16,242,172, 47,113,147,205, + 17, 79,173,243,112, 46,204,146,211,141,111, 49,178,236, 14, 80, + 175,241, 19, 77,206,144,114, 44,109, 51,209,143, 12, 82,176,238, + 50,108,142,208, 83, 13,239,177,240,174, 76, 18,145,207, 45,115, + 202,148,118, 40,171,245, 23, 73, 8, 86,180,234,105, 55,213,139, + 87, 9,235,181, 54,104,138,212,149,203, 41,119,244,170, 72, 22, + 233,183, 85, 11,136,214, 52,106, 43,117,151,201, 74, 20,246,168, + 116, 42,200,150, 21, 75,169,247,182,232, 10, 84,215,137,107, 53}; + +// +// Compute a Dallas Semiconductor 8 bit CRC. These show up in the ROM +// and the registers. (note: this might better be done without to +// table, it would probably be smaller and certainly fast enough +// compared to all those delayMicrosecond() calls. But I got +// confused, so I use this table from the examples.) +// +uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len) +{ + uint8_t crc = 0; + + while (len--) { + crc = pgm_read_byte(dscrc_table + (crc ^ *addr++)); + } + return crc; +} +#else +// +// Compute a Dallas Semiconductor 8 bit CRC directly. +// this is much slower, but much smaller, than the lookup table. +// +uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len) +{ + uint8_t crc = 0; + + while (len--) { + uint8_t inbyte = *addr++; + for (uint8_t i = 8; i; i--) { + uint8_t mix = (crc ^ inbyte) & 0x01; + crc >>= 1; + if (mix) crc ^= 0x8C; + inbyte >>= 1; + } + } + return crc; +} +#endif + +#if ONEWIRE_CRC16 +bool OneWire::check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc) +{ + crc = ~crc16(input, len, crc); + return (crc & 0xFF) == inverted_crc[0] && (crc >> 8) == inverted_crc[1]; +} + +uint16_t OneWire::crc16(const uint8_t* input, uint16_t len, uint16_t crc) +{ + static const uint8_t oddparity[16] = + { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 }; + + for (uint16_t i = 0 ; i < len ; i++) { + // Even though we're just copying a byte from the input, + // we'll be doing 16-bit computation with it. + uint16_t cdata = input[i]; + cdata = (cdata ^ crc) & 0xff; + crc >>= 8; + + if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4]) + crc ^= 0xC001; + + cdata <<= 6; + crc ^= cdata; + cdata <<= 1; + crc ^= cdata; + } + return crc; +} +#endif + +#endif diff --git a/src/lib/OneWire.h b/src/lib/OneWire.h new file mode 100644 index 00000000..916c5290 --- /dev/null +++ b/src/lib/OneWire.h @@ -0,0 +1,229 @@ +#ifndef OneWire_h +#define OneWire_h + +#include + +#if ARDUINO >= 100 +#include "Arduino.h" // for delayMicroseconds, digitalPinToBitMask, etc +#else +#include "WProgram.h" // for delayMicroseconds +#include "pins_arduino.h" // for digitalPinToBitMask, etc +#endif + +// You can exclude certain features from OneWire. In theory, this +// might save some space. In practice, the compiler automatically +// removes unused code (technically, the linker, using -fdata-sections +// and -ffunction-sections when compiling, and Wl,--gc-sections +// when linking), so most of these will not result in any code size +// reduction. Well, unless you try to use the missing features +// and redesign your program to not need them! ONEWIRE_CRC8_TABLE +// is the exception, because it selects a fast but large algorithm +// or a small but slow algorithm. + +// you can exclude onewire_search by defining that to 0 +#ifndef ONEWIRE_SEARCH +#define ONEWIRE_SEARCH 1 +#endif + +// You can exclude CRC checks altogether by defining this to 0 +#ifndef ONEWIRE_CRC +#define ONEWIRE_CRC 1 +#endif + +// Select the table-lookup method of computing the 8-bit CRC +// by setting this to 1. The lookup table enlarges code size by +// about 250 bytes. It does NOT consume RAM (but did in very +// old versions of OneWire). If you disable this, a slower +// but very compact algorithm is used. +#ifndef ONEWIRE_CRC8_TABLE +#define ONEWIRE_CRC8_TABLE 1 +#endif + +// You can allow 16-bit CRC checks by defining this to 1 +// (Note that ONEWIRE_CRC must also be 1.) +#ifndef ONEWIRE_CRC16 +#define ONEWIRE_CRC16 1 +#endif + +#define FALSE 0 +#define TRUE 1 + +// Platform specific I/O definitions + +#if defined(__AVR__) +#define PIN_TO_BASEREG(pin) (portInputRegister(digitalPinToPort(pin))) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint8_t +#define IO_REG_ASM asm("r30") +#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) &= ~(mask)) +#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+1)) |= (mask)) +#define DIRECT_WRITE_LOW(base, mask) ((*((base)+2)) &= ~(mask)) +#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+2)) |= (mask)) + +#elif defined(__MK20DX128__) +#define PIN_TO_BASEREG(pin) (portOutputRegister(pin)) +#define PIN_TO_BITMASK(pin) (1) +#define IO_REG_TYPE uint8_t +#define IO_REG_ASM +#define DIRECT_READ(base, mask) (*((base)+512)) +#define DIRECT_MODE_INPUT(base, mask) (*((base)+640) = 0) +#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+640) = 1) +#define DIRECT_WRITE_LOW(base, mask) (*((base)+256) = 1) +#define DIRECT_WRITE_HIGH(base, mask) (*((base)+128) = 1) + +#elif defined(__SAM3X8E__) +// Arduino 1.5.1 may have a bug in delayMicroseconds() on Arduino Due. +// http://arduino.cc/forum/index.php/topic,141030.msg1076268.html#msg1076268 +// If you have trouble with OneWire on Arduino Due, please check the +// status of delayMicroseconds() before reporting a bug in OneWire! +#define PIN_TO_BASEREG(pin) (&(digitalPinToPort(pin)->PIO_PER)) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint32_t +#define IO_REG_ASM +#define DIRECT_READ(base, mask) (((*((base)+15)) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) ((*((base)+5)) = (mask)) +#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+4)) = (mask)) +#define DIRECT_WRITE_LOW(base, mask) ((*((base)+13)) = (mask)) +#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+12)) = (mask)) +#ifndef PROGMEM +#define PROGMEM +#endif +#ifndef pgm_read_byte +#define pgm_read_byte(addr) (*(const uint8_t *)(addr)) +#endif + +#elif defined(__PIC32MX__) +#define PIN_TO_BASEREG(pin) (portModeRegister(digitalPinToPort(pin))) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint32_t +#define IO_REG_ASM +#define DIRECT_READ(base, mask) (((*(base+4)) & (mask)) ? 1 : 0) //PORTX + 0x10 +#define DIRECT_MODE_INPUT(base, mask) ((*(base+2)) = (mask)) //TRISXSET + 0x08 +#define DIRECT_MODE_OUTPUT(base, mask) ((*(base+1)) = (mask)) //TRISXCLR + 0x04 +#define DIRECT_WRITE_LOW(base, mask) ((*(base+8+1)) = (mask)) //LATXCLR + 0x24 +#define DIRECT_WRITE_HIGH(base, mask) ((*(base+8+2)) = (mask)) //LATXSET + 0x28 + +#else +#error "Please define I/O register types here" +#endif + + +class OneWire +{ + private: + IO_REG_TYPE bitmask; + volatile IO_REG_TYPE *baseReg; + +#if ONEWIRE_SEARCH + // global search state + unsigned char ROM_NO[8]; + uint8_t LastDiscrepancy; + uint8_t LastFamilyDiscrepancy; + uint8_t LastDeviceFlag; +#endif + + public: + OneWire( uint8_t pin); + + // Perform a 1-Wire reset cycle. Returns 1 if a device responds + // with a presence pulse. Returns 0 if there is no device or the + // bus is shorted or otherwise held low for more than 250uS + uint8_t reset(void); + + // Issue a 1-Wire rom select command, you do the reset first. + void select(const uint8_t rom[8]); + + // Issue a 1-Wire rom skip command, to address all on bus. + void skip(void); + + // Write a byte. If 'power' is one then the wire is held high at + // the end for parasitically powered devices. You are responsible + // for eventually depowering it by calling depower() or doing + // another read or write. + void write(uint8_t v, uint8_t power = 0); + + void write_bytes(const uint8_t *buf, uint16_t count, bool power = 0); + + // Read a byte. + uint8_t read(void); + + void read_bytes(uint8_t *buf, uint16_t count); + + // Write a bit. The bus is always left powered at the end, see + // note in write() about that. + void write_bit(uint8_t v); + + // Read a bit. + uint8_t read_bit(void); + + // Stop forcing power onto the bus. You only need to do this if + // you used the 'power' flag to write() or used a write_bit() call + // and aren't about to do another read or write. You would rather + // not leave this powered if you don't have to, just in case + // someone shorts your bus. + void depower(void); + +#if ONEWIRE_SEARCH + // Clear the search state so that if will start from the beginning again. + void reset_search(); + + // Setup the search to find the device type 'family_code' on the next call + // to search(*newAddr) if it is present. + void target_search(uint8_t family_code); + + // Look for the next device. Returns 1 if a new address has been + // returned. A zero might mean that the bus is shorted, there are + // no devices, or you have already retrieved all of them. It + // might be a good idea to check the CRC to make sure you didn't + // get garbage. The order is deterministic. You will always get + // the same devices in the same order. + uint8_t search(uint8_t *newAddr); +#endif + +#if ONEWIRE_CRC + // Compute a Dallas Semiconductor 8 bit CRC, these are used in the + // ROM and scratchpad registers. + static uint8_t crc8(const uint8_t *addr, uint8_t len); + +#if ONEWIRE_CRC16 + // Compute the 1-Wire CRC16 and compare it against the received CRC. + // Example usage (reading a DS2408): + // // Put everything in a buffer so we can compute the CRC easily. + // uint8_t buf[13]; + // buf[0] = 0xF0; // Read PIO Registers + // buf[1] = 0x88; // LSB address + // buf[2] = 0x00; // MSB address + // WriteBytes(net, buf, 3); // Write 3 cmd bytes + // ReadBytes(net, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16 + // if (!CheckCRC16(buf, 11, &buf[11])) { + // // Handle error. + // } + // + // @param input - Array of bytes to checksum. + // @param len - How many bytes to use. + // @param inverted_crc - The two CRC16 bytes in the received data. + // This should just point into the received data, + // *not* at a 16-bit integer. + // @param crc - The crc starting value (optional) + // @return True, iff the CRC matches. + static bool check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc = 0); + + // Compute a Dallas Semiconductor 16 bit CRC. This is required to check + // the integrity of data received from many 1-Wire devices. Note that the + // CRC computed here is *not* what you'll get from the 1-Wire network, + // for two reasons: + // 1) The CRC is transmitted bitwise inverted. + // 2) Depending on the endian-ness of your processor, the binary + // representation of the two-byte return value may have a different + // byte order than the two bytes you get from 1-Wire. + // @param input - Array of bytes to checksum. + // @param len - How many bytes to use. + // @param crc - The crc starting value (optional) + // @return The CRC16, as defined by Dallas Semiconductor. + static uint16_t crc16(const uint8_t* input, uint16_t len, uint16_t crc = 0); +#endif +#endif +}; + +#endif From a23595474926f6f6e961a5167da94dfeec055942 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Mon, 8 Sep 2014 23:46:32 -0400 Subject: [PATCH 058/296] Support MS1, MS2, SLEEP and ENABLE features on the EasyDriver stepper board. --- lib/dino/components/stepper.rb | 52 +++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/lib/dino/components/stepper.rb b/lib/dino/components/stepper.rb index 3f739f76..075104f6 100644 --- a/lib/dino/components/stepper.rb +++ b/lib/dino/components/stepper.rb @@ -2,18 +2,62 @@ module Dino module Components class Stepper include Setup::MultiPin - + proxy_pins step: Basic::DigitalOutput, direction: Basic::DigitalOutput - + + proxy_pins ms1: Basic::DigitalOutput, + ms2: Basic::DigitalOutput, + enable: Basic::DigitalOutput, + sleep: Basic::DigitalOutput, + optional: true + + def after_initialize(options={}) + wake; on; divider = 8 + end + + def sleep_mode + sleep.low if pins[:sleep] + end + + def wake + sleep.high if pins[:sleep] + end + + def off + enable.high if pins[:enable] + end + + def on + enable.low if pins[:enable] + end + + def divider=(steps) + return unless (ms1 && ms2) + case steps.to_i + when 1 + ms1.low; ms2.low + when 2 + ms1.high; ms2.low + when 4 + ms1.low; ms2.high + when 8 + ms1.high; ms2.high + else + return + end + @divider = steps + end + attr_reader :divider + def step_cc - direction.high + direction.high unless direction.high? step.high step.low end def step_cw - direction.low + direction.low unless direction.low? step.high step.low end From f8802b6492bf7d2820916cc835787f783e08ba3a Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Tue, 9 Sep 2014 16:53:31 -0400 Subject: [PATCH 059/296] Add DigitalOutput#low?, high?, on?, off? --- lib/dino/components/basic/digital_output.rb | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/dino/components/basic/digital_output.rb b/lib/dino/components/basic/digital_output.rb index 13c88136..3a44e3f0 100644 --- a/lib/dino/components/basic/digital_output.rb +++ b/lib/dino/components/basic/digital_output.rb @@ -14,23 +14,31 @@ def after_initialize(options={}) def digital_write(value) board.digital_write(pin, @state = value) end - alias :write :digital_write def low digital_write(board.low) end + alias :off :low + + def low? + state == board.low + end + alias :off? :low? def high digital_write(board.high) end + alias :on :high + + def high? + state == board.high + end + alias :on? :high? def toggle - state == board.low ? high : low + board.low? ? high : low end - - alias :off :low - alias :on :high end end end From 634846a1c8f78224657617f6ee8152e68b8ae7e3 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Mon, 29 Sep 2014 14:23:49 -0400 Subject: [PATCH 060/296] Call low? not board.low? --- lib/dino/components/basic/digital_output.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dino/components/basic/digital_output.rb b/lib/dino/components/basic/digital_output.rb index 3a44e3f0..d34a702a 100644 --- a/lib/dino/components/basic/digital_output.rb +++ b/lib/dino/components/basic/digital_output.rb @@ -37,7 +37,7 @@ def high? alias :on? :high? def toggle - board.low? ? high : low + low? ? high : low end end end From dc38438bde68f2c61907ae050155cac76762e05a Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 3 Jan 2015 19:53:15 -0400 Subject: [PATCH 061/296] Switched from serialport to rubyserial --- dino.gemspec | 2 +- lib/dino/tx_rx/base.rb | 46 +++++++++++++++++++++++----------------- lib/dino/tx_rx/serial.rb | 25 ++++++++++++---------- lib/dino/tx_rx/tcp.rb | 15 ++++++++++--- 4 files changed, 53 insertions(+), 35 deletions(-) diff --git a/dino.gemspec b/dino.gemspec index 1be2b618..96d54cb8 100644 --- a/dino.gemspec +++ b/dino.gemspec @@ -16,7 +16,7 @@ Gem::Specification.new do |gem| gem.version = Dino::VERSION gem.executables = ["dino"] - gem.add_dependency 'serialport' + gem.add_dependency 'rubyserial' gem.add_dependency 'trollop' gem.add_development_dependency 'rake' diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index 9401b47a..abf2b4cb 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -6,6 +6,10 @@ module TxRx class Base include Observable + def io + @io ||= connect + end + def read @thread ||= Thread.new do loop do @@ -24,35 +28,37 @@ def close_read @thread = nil end - def write(message) - loop do - if IO.select(nil, [io], nil) - io.syswrite(message) - break - end - end - end - def handshake flush_read 10.times do - write Dino::Message.encode(command: 90) - line = gets(1) - if line && line.match(/ACK:/) - flush_read - return line.chop.split(/:/)[1] + begin + Timeout.timeout(1) do + write Dino::Message.encode(command: 90) + loop do + line = gets + if line && line.match(/ACK:/) + flush_read + return line.chop.split(/:/)[1] + end + end + end + rescue Timeout::Error + puts "Could not find board. Retrying..." end end - raise BoardNotFound + raise BoardNotFound end + def write(message); raise "#write should be defined in TxRX subclasses"; end + + private + + def connect(message); raise "#connect should be defined in TxRX subclasses"; end + def gets(message); raise "#gets should be defined in TxRX subclasses"; end + def flush_read gets until gets == nil end - - def gets(timeout=0.005) - IO.select([io], nil, nil, timeout) && io.gets - end end end -end \ No newline at end of file +end diff --git a/lib/dino/tx_rx/serial.rb b/lib/dino/tx_rx/serial.rb index f1e2b4a9..3630fd1e 100644 --- a/lib/dino/tx_rx/serial.rb +++ b/lib/dino/tx_rx/serial.rb @@ -1,4 +1,4 @@ -require 'serialport' +require 'rubyserial' module Dino module TxRx @@ -11,20 +11,14 @@ def initialize(options={}) @first_write = true end - def io - @io ||= connect + def write(message) + io.write(message) end - def handshake - io - sleep 3 if on_windows? - super - end - - private + private def connect - tty_devices.each { |device| return SerialPort.new(device, @baud) rescue nil } + tty_devices.each { |device| return ::Serial.new(device, @baud) rescue nil } raise BoardNotFound end @@ -37,6 +31,15 @@ def tty_devices def on_windows? RUBY_PLATFORM.match /mswin|mingw/i end + + def gets(timeout=0) + buff = io.read(1) + return nil if buff.empty? + loop do + buff << io.read(1) + return buff if buff[-1] == "\n" + end + end end end end diff --git a/lib/dino/tx_rx/tcp.rb b/lib/dino/tx_rx/tcp.rb index f8e0b3d7..c8783135 100644 --- a/lib/dino/tx_rx/tcp.rb +++ b/lib/dino/tx_rx/tcp.rb @@ -7,10 +7,15 @@ def initialize(host, port=3466) @host, @port = host, port end - def io - @io ||= connect + def write(message) + loop do + if IO.select(nil, [io], nil) + io.syswrite(message) + break + end + end end - + private def connect @@ -18,6 +23,10 @@ def connect rescue raise BoardNotFound end + + def gets(timeout=0.005) + IO.select([io], nil, nil, timeout) && io.gets + end end end end From f6caf610332a0a581ee1a912868d797862545e3e Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Mon, 5 Jan 2015 09:33:14 -0400 Subject: [PATCH 062/296] Fix tests for rubyserial --- spec/lib/tx_rx/serial_spec.rb | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/spec/lib/tx_rx/serial_spec.rb b/spec/lib/tx_rx/serial_spec.rb index d9a8a13c..e52f36d6 100644 --- a/spec/lib/tx_rx/serial_spec.rb +++ b/spec/lib/tx_rx/serial_spec.rb @@ -24,9 +24,9 @@ module Dino subject.should_receive(:tty_devices).and_return(["COM1", "COM2", "COM3"]) # COM2 is chosen as available for this test. - SerialPort.should_receive(:new).with("COM1", TxRx::Serial::BAUD).and_raise - SerialPort.should_receive(:new).with("COM2", TxRx::Serial::BAUD).and_return(mock_serial = mock) - SerialPort.should_not_receive(:new).with("COM3", TxRx::Serial::BAUD) + ::Serial.should_receive(:new).with("COM1", TxRx::Serial::BAUD).and_raise + ::Serial.should_receive(:new).with("COM2", TxRx::Serial::BAUD).and_return(mock_serial = mock) + ::Serial.should_not_receive(:new).with("COM3", TxRx::Serial::BAUD) subject.io.should == mock_serial Constants.redefine(:RUBY_PLATFORM, original_platform, :on => Object) @@ -38,8 +38,8 @@ module Dino subject.should_receive(:tty_devices).and_return(['/dev/ttyACM0', '/dev/tty.usbmodem1']) # /dev/ttyACM0 is chosen as available for this test. - SerialPort.should_receive(:new).with('/dev/ttyACM0', TxRx::Serial::BAUD).and_return(mock_serial = mock) - SerialPort.should_not_receive(:new).with('/dev/tty.usbmodem1', TxRx::Serial::BAUD) + ::Serial.should_receive(:new).with('/dev/ttyACM0', TxRx::Serial::BAUD).and_return(mock_serial = mock) + ::Serial.should_not_receive(:new).with('/dev/tty.usbmodem1', TxRx::Serial::BAUD) subject.io.should == mock_serial end @@ -47,7 +47,7 @@ module Dino it 'should connect to the specified device at the specified baud rate' do subject.should_receive(:tty_devices).and_return(["/dev/ttyACM0"]) - SerialPort.should_receive(:new).with('/dev/ttyACM0', 9600).and_return(mock_serial = mock) + ::Serial.should_receive(:new).with('/dev/ttyACM0', 9600).and_return(mock_serial = mock) subject.instance_variable_set(:@device, "/dev/ttyACM0") subject.instance_variable_set(:@baud, 9600) @@ -57,14 +57,14 @@ module Dino it 'should use the existing io instance if set' do subject.should_receive(:tty_devices).once.and_return(['/dev/tty.ACM0', '/dev/tty.usbmodem1']) - SerialPort.stub(:new).and_return(mock_serial = mock) + ::Serial.stub(:new).and_return(mock_serial = mock) 3.times { subject.io } subject.io.should == mock_serial end it 'should raise a BoardNotFound exception if there is no board connected' do - SerialPort.stub(:new).and_raise + ::Serial.stub(:new).and_raise expect { subject.io }.to raise_exception BoardNotFound end end @@ -75,13 +75,10 @@ module Dino subject.read end - it 'should get messages from the device' do - subject.stub(:io).and_return(mock_serial = mock) - - IO.should_receive(:select).and_return(true) + it 'should start a loop and notify observers on changes' do Thread.should_receive(:new).and_yield subject.should_receive(:loop).and_yield - mock_serial.should_receive(:gets).and_return("02:00\n") + subject.should_receive(:gets).and_return("02:00\n") subject.should_receive(:changed).and_return(true) subject.should_receive(:notify_observers).with('02','00') @@ -100,10 +97,8 @@ module Dino describe '#write' do it 'should write to the device' do - IO.should_receive(:select).and_return(true) - subject.stub(:io).and_return(mock_serial = mock) - mock_serial.should_receive(:syswrite).with('a message') + mock_serial.should_receive(:write).with('a message') subject.write('a message') end end From c810478af2004ff484634c26d0a3dd28a09a89bf Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Mon, 5 Jan 2015 09:43:03 -0400 Subject: [PATCH 063/296] Change mock to double for rspec --- spec/lib/board_spec.rb | 14 +++++++------- spec/lib/components/basic/digital_input_spec.rb | 8 ++++---- spec/lib/tx_rx/serial_spec.rb | 12 ++++++------ spec/spec_helper.rb | 4 ++-- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/spec/lib/board_spec.rb b/spec/lib/board_spec.rb index 3b99785f..5d9262f7 100644 --- a/spec/lib/board_spec.rb +++ b/spec/lib/board_spec.rb @@ -3,7 +3,7 @@ module Dino describe Dino::Board do def io_mock(methods = {}) - @io ||= mock(:io, {add_observer: true, handshake: 14, write: true, read: true, write: nil}.merge(methods)) + @io ||= double(:io, {add_observer: true, handshake: 14, write: true, read: true, write: nil}.merge(methods)) end subject { Board.new(io_mock) } @@ -37,9 +37,9 @@ def io_mock(methods = {}) describe '#update' do context 'when a component is connected to the pin' do it 'should call update with the message on the component' do - part = mock(:part, pin: 7, pullup: nil) + part = double(:part, pin: 7, pullup: nil) subject.add_component(part) - other_part = mock(:part, pin: 9, pullup: nil) + other_part = double(:part, pin: 9, pullup: nil) subject.add_component(other_part) part.should_receive(:update).with('wake up!') @@ -58,20 +58,20 @@ def io_mock(methods = {}) describe '#add_component' do it 'should add the component to the board' do - subject.add_component(mock1 = mock(:part1, pin: 12, pullup: nil)) - subject.add_component(mock2 = mock(:part2, pin: 14, pullup: nil)) + subject.add_component(mock1 = double(:part1, pin: 12, pullup: nil)) + subject.add_component(mock2 = double(:part2, pin: 14, pullup: nil)) subject.components.should =~ [mock1, mock2] end end describe '#remove_component' do it 'should remove the given part from the hardware of the board and stop listening' do - mock = mock(:part1, pin: 12, pullup: nil) + mock = double(:part1, pin: 12, pullup: nil) subject.add_component(mock) subject.should_receive(:stop_listener).with(12) subject.remove_component(mock) - + subject.components.should == [] end end diff --git a/spec/lib/components/basic/digital_input_spec.rb b/spec/lib/components/basic/digital_input_spec.rb index ef715cd9..a96d5422 100644 --- a/spec/lib/components/basic/digital_input_spec.rb +++ b/spec/lib/components/basic/digital_input_spec.rb @@ -34,8 +34,8 @@ module Basic context 'callbacks' do before :each do - @low_callback = mock - @high_callback = mock + @low_callback = double + @high_callback = double subject.on_low { @low_callback.called } subject.on_high { @high_callback.called } end @@ -46,7 +46,7 @@ module Basic @high_callback.should_not_receive(:called) subject.update(DigitalInput::LOW) - end + end end describe '#on_high' do @@ -55,7 +55,7 @@ module Basic @high_callback.should_receive(:called) subject.update(DigitalInput::HIGH) - end + end end end end diff --git a/spec/lib/tx_rx/serial_spec.rb b/spec/lib/tx_rx/serial_spec.rb index e52f36d6..f03c46b1 100644 --- a/spec/lib/tx_rx/serial_spec.rb +++ b/spec/lib/tx_rx/serial_spec.rb @@ -25,7 +25,7 @@ module Dino # COM2 is chosen as available for this test. ::Serial.should_receive(:new).with("COM1", TxRx::Serial::BAUD).and_raise - ::Serial.should_receive(:new).with("COM2", TxRx::Serial::BAUD).and_return(mock_serial = mock) + ::Serial.should_receive(:new).with("COM2", TxRx::Serial::BAUD).and_return(mock_serial = double) ::Serial.should_not_receive(:new).with("COM3", TxRx::Serial::BAUD) subject.io.should == mock_serial @@ -38,7 +38,7 @@ module Dino subject.should_receive(:tty_devices).and_return(['/dev/ttyACM0', '/dev/tty.usbmodem1']) # /dev/ttyACM0 is chosen as available for this test. - ::Serial.should_receive(:new).with('/dev/ttyACM0', TxRx::Serial::BAUD).and_return(mock_serial = mock) + ::Serial.should_receive(:new).with('/dev/ttyACM0', TxRx::Serial::BAUD).and_return(mock_serial = double) ::Serial.should_not_receive(:new).with('/dev/tty.usbmodem1', TxRx::Serial::BAUD) subject.io.should == mock_serial @@ -47,7 +47,7 @@ module Dino it 'should connect to the specified device at the specified baud rate' do subject.should_receive(:tty_devices).and_return(["/dev/ttyACM0"]) - ::Serial.should_receive(:new).with('/dev/ttyACM0', 9600).and_return(mock_serial = mock) + ::Serial.should_receive(:new).with('/dev/ttyACM0', 9600).and_return(mock_serial = double) subject.instance_variable_set(:@device, "/dev/ttyACM0") subject.instance_variable_set(:@baud, 9600) @@ -57,7 +57,7 @@ module Dino it 'should use the existing io instance if set' do subject.should_receive(:tty_devices).once.and_return(['/dev/tty.ACM0', '/dev/tty.usbmodem1']) - ::Serial.stub(:new).and_return(mock_serial = mock) + ::Serial.stub(:new).and_return(mock_serial = double) 3.times { subject.io } subject.io.should == mock_serial @@ -88,7 +88,7 @@ module Dino describe '#close_read' do it 'should kill the reading thread' do - subject.instance_variable_set(:@thread, mock_thread = mock) + subject.instance_variable_set(:@thread, mock_thread = double) Thread.should_receive(:kill).with(mock_thread) subject.read subject.close_read @@ -97,7 +97,7 @@ module Dino describe '#write' do it 'should write to the device' do - subject.stub(:io).and_return(mock_serial = mock) + subject.stub(:io).and_return(mock_serial = double) mock_serial.should_receive(:write).with('a message') subject.write('a message') end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 48219c57..281f2af1 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -13,8 +13,8 @@ def self.redefine(const, value, opts={}) module BoardMock def self.included(base) - base.class_eval do - let(:txrx) { mock(:txrx, add_observer: true, handshake: 14, write: true, read: true) } + base.class_eval do + let(:txrx) { double(:txrx, add_observer: true, handshake: 14, write: true, read: true) } let(:board) { Dino::Board.new(txrx) } end end From 4f8a52270167e8631ed06cd3ce353964835af937 Mon Sep 17 00:00:00 2001 From: Vickash Date: Sat, 7 Feb 2015 00:42:44 -0400 Subject: [PATCH 064/296] Remove upload/compile. Too much scope. Use shell scripts instead. --- lib/dino_cli.rb | 19 +++++--------- lib/dino_cli/generator.rb | 2 +- lib/dino_cli/helper.rb | 54 +++++++++++++++++++-------------------- lib/dino_cli/parser.rb | 32 +++++++++++------------ lib/dino_cli/uploader.rb | 37 --------------------------- 5 files changed, 49 insertions(+), 95 deletions(-) delete mode 100644 lib/dino_cli/uploader.rb diff --git a/lib/dino_cli.rb b/lib/dino_cli.rb index bffe309e..30d7a37c 100644 --- a/lib/dino_cli.rb +++ b/lib/dino_cli.rb @@ -1,25 +1,18 @@ module DinoCLI require "dino_cli/parser" require "dino_cli/generator" - require "dino_cli/uploader" - COMMANDS = ["generate-sketch"] + TASKS = ["sketch"] SKETCHES = ["serial", "ethernet", "wifi"] - - def self.start(options={}) - parsed_options = DinoCLI::Parser.run(options) - method = parsed_options[:command].gsub('-', '_').to_s + def self.start(options={}) + options = DinoCLI::Parser.run(options) + method = options[:task] self.send method, options end - def self.generate_sketch(options) + def self.sketch(options) options = DinoCLI::Generator.run!(options) - - if options[:upload] || options[:compile] - DinoCLI::Uploader.run!(options) - else - $stdout.puts options[:sketch_file] - end + $stdout.puts options[:sketch_file] end end diff --git a/lib/dino_cli/generator.rb b/lib/dino_cli/generator.rb index 89d71a63..b5a234fc 100644 --- a/lib/dino_cli/generator.rb +++ b/lib/dino_cli/generator.rb @@ -1,6 +1,6 @@ class DinoCLI::Generator require "fileutils" - LIB_FILENAMES = ["Dino.h", "Dino.cpp", "DinoLCD.h", "DinoLCD.cpp", "DinoSerial.cpp","DinoSerial.h", "DHT.cpp", "DHT.h", "OneWire.h", "OneWire.cpp"] + LIB_FILENAMES = ["Dino.h", "Dino.cpp", "DinoLCD.h", "DinoLCD.cpp", "DinoSerial.cpp","DinoSerial.h", "DHT.cpp", "DHT.h"] attr_accessor :options def initialize(options={}) diff --git a/lib/dino_cli/helper.rb b/lib/dino_cli/helper.rb index fece1920..2d30a1b2 100644 --- a/lib/dino_cli/helper.rb +++ b/lib/dino_cli/helper.rb @@ -8,34 +8,32 @@ def error(message) def usage $stderr.puts "Usage:" - $stderr.puts " dino COMMAND [command-specific-options]" - $stderr.puts - $stderr.puts "Commands:" - $stderr.puts " generate-sketch SKETCH [options]" - $stderr.puts - $stderr.puts " Available sketches and options:" - $stderr.puts - $stderr.puts " serial" - $stderr.puts " --baud BAUD" - $stderr.puts " --debug" - $stderr.puts " --upload" - $stderr.puts - $stderr.puts " ethernet" - $stderr.puts " --mac XX:XX:XX:XX:XX:XX" - $stderr.puts " --ip XXX.XXX.XXX.XXX" - $stderr.puts " --port PORT" - $stderr.puts " --debug" - $stderr.puts " --upload" - $stderr.puts - $stderr.puts " wifi" - $stderr.puts " --ssid SSID" - $stderr.puts " --password PASSWORD" - $stderr.puts " --port PORT" - $stderr.puts " --debug" - $stderr.puts " --upload" - $stderr.puts - $stderr.puts "Note: Automatic upload requires Arduino IDE 1.5 or greater." - $stderr.puts " Set your board type and serialport in the IDE first." + $stderr.puts " dino task [options]" + $stderr.puts + $stderr.puts "Tasks:" + $stderr.puts " sketch SKETCH [options]" + $stderr.puts + $stderr.puts "Available sketches and options for each sketch:" + $stderr.puts + $stderr.puts " serial" + $stderr.puts " -baud BAUD" + $stderr.puts " -debug" + $stderr.puts + $stderr.puts " ethernet" + $stderr.puts " -mac XX:XX:XX:XX:XX:XX" + $stderr.puts " -ip XXX.XXX.XXX.XXX" + $stderr.puts " -port PORT" + $stderr.puts " -debug" + $stderr.puts + $stderr.puts " wifi" + $stderr.puts " -ssid SSID" + $stderr.puts " -password PASSWORD" + $stderr.puts " -port PORT" + $stderr.puts " -debug" + $stderr.puts + $stderr.puts "Example:" + $stderr.puts + $stderr.puts " dino sketch serial -baud 9600" $stderr.puts exit(2) end diff --git a/lib/dino_cli/parser.rb b/lib/dino_cli/parser.rb index 87c76236..7e9217c9 100644 --- a/lib/dino_cli/parser.rb +++ b/lib/dino_cli/parser.rb @@ -11,12 +11,16 @@ def self.run(options={}) end def parse + # Ensure we have arguments. args = @options[:args].dup + error("No arguments given") if args.empty? - # Command must be the first arg. - @options[:command] = args.shift - usage if @options[:command].match /help|usage/ - error "Invalid command '#{@options[:command]}'" unless DinoCLI::COMMANDS.include? @options[:command] + # Ensure task is the first argument + @options[:task] = args.shift + usage if @options[:task].match /help|usage/ + + # Ensure there's only one task + error "Invalid task '#{@options[:task]}'" unless DinoCLI::TASKS.include? @options[:task] # Parse the rest loosely. loop do @@ -27,26 +31,22 @@ def parse args.shift; set_sketch("ethernet") when 'wifi' args.shift; set_sketch("wifi") - when '--baud' + when '-baud' args.shift; @options[:baud] = args.shift - when '--mac' + when '-mac' args.shift; @options[:mac] = args.shift - when '--ip' + when '-ip' args.shift; @options[:ip] = args.shift - when '--ssid' + when '-ssid' args.shift; @options[:ssid] = args.shift - when '--password' + when '-password' args.shift; @options[:password] = args.shift - when '--port' + when '-port' args.shift; @options[:port] = args.shift - when '--debug' + when '-debug' args.shift; @options[:debug] = true - when '--compile' - args.shift; @options[:compile] = true - when '--upload' - args.shift; @options[:upload] = true when /^-/ - error "Invalid argument '#{ARGV[0]}'" + error "Invalid argument '#{args[0]}'" else break end end diff --git a/lib/dino_cli/uploader.rb b/lib/dino_cli/uploader.rb deleted file mode 100644 index 44ce1a16..00000000 --- a/lib/dino_cli/uploader.rb +++ /dev/null @@ -1,37 +0,0 @@ -class DinoCLI::Uploader - require "pathname" - require "fileutils" - attr_reader :options - - def initialize(options={}) - @options = options - end - - def self.run!(options={}) - instance = self.new(options) - - if options[:upload] - instance.upload - elsif options[:compile] - instance.compile - end - end - - def compile - output = `#{executable} --verify --verbose '#{options[:sketch_file]}'` - - tmp_hex_file = output.match(/elf.*.hex/)[0].gsub("elf", "").lstrip - FileUtils::copy(tmp_hex_file, options[:output_dir]) - - hex_file = File.join(options[:output_dir], Pathname.new(tmp_hex_file).basename) - $stdout.puts hex_file - end - - def upload - `#{executable} --upload '#{options[:sketch_file]}'` - end - - def executable - return "/Applications/Arduino.app/Contents/MacOS/JavaApplicationStub" if RUBY_PLATFORM.match(/darwin/i) - end -end From 0dce1e110e111c0fcd241dece8a3ceda81b22f4b Mon Sep 17 00:00:00 2001 From: Vickash Date: Sat, 7 Feb 2015 11:08:49 -0400 Subject: [PATCH 065/296] Update the readme for new command line options --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 588dcfe3..60b988a0 100644 --- a/README.md +++ b/README.md @@ -25,10 +25,10 @@ Use the included command line tool to create a folder with the Arduino sketch yo ```shell # If connecting via serial, USB or ser2net, this is all you should need: -dino generate-sketch serial +dino sketch serial # If usng the ethernet shield, you'll want to specify unique MAC and IP addresses: -dino generate-sketch ethernet --mac XX:XX:XX:XX:XX:XX --ip XXX.XXX.XXX.XXX +dino sketch ethernet -mac XX:XX:XX:XX:XX:XX -ip XXX.XXX.XXX.XXX # For more options: dino help @@ -38,7 +38,7 @@ __Note:__ Current Ethernet shields come with a sticker indicating the MAC addres #### Upload The Bootstrapper -* Connect the Arduino to a USB port on your machine, regardless of which sketch you're using. +* Connect the Arduino to a USB port on your machine, (even if using the ethernet sketch). * Open [the normal Arduino IDE](http://arduino.cc/en/Main/Software) * Open the `.ino` file in the sketch folder you just generated. * Click the upload button (an arrow). From cc70a4d922baaeebd25a7a232204f542eea8a4c0 Mon Sep 17 00:00:00 2001 From: Vickash Date: Sat, 7 Feb 2015 11:09:41 -0400 Subject: [PATCH 066/296] Notify when the board is connected --- lib/dino/tx_rx/base.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index abf2b4cb..5419fd70 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -37,6 +37,7 @@ def handshake loop do line = gets if line && line.match(/ACK:/) + puts "Connected to board..." flush_read return line.chop.split(/:/)[1] end From e257d569c733a73eaa258f08dfc3d419d35bafad Mon Sep 17 00:00:00 2001 From: Vickash Date: Sat, 7 Feb 2015 11:11:01 -0400 Subject: [PATCH 067/296] Use a mutex when modifying callbacks to avoid modifying hash while it's being enumerated --- lib/dino/components/mixins/callbacks.rb | 31 ++++++++++++++----------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/lib/dino/components/mixins/callbacks.rb b/lib/dino/components/mixins/callbacks.rb index dd1006d1..424a7bb9 100644 --- a/lib/dino/components/mixins/callbacks.rb +++ b/lib/dino/components/mixins/callbacks.rb @@ -2,30 +2,35 @@ module Dino module Components module Mixins module Callbacks - def callbacks - @callbacks ||= {} + def initialize + @callbacks = {} + @callback_mutex = Mutex.new end def add_callback(key=:persistent, &block) - callbacks - @callbacks[key] ||= [] - @callbacks[key] << block + @callback_mutex.synchronize { + @callbacks[key] ||= [] + @callbacks[key] << block + } end - + def remove_callback(key=nil) - callbacks - key ? @callbacks[key] = [] : @callbacks = {} + @callback_mutex.synchronize { + key ? @new_callbacks[key] = [] : @new_callbacks = {} + } end alias :on_data :add_callback alias :remove_callbacks :remove_callback def update(data) - @state = data - callbacks.each_value do |array| - array.each { |callback| callback.call(@state) } - end - remove_callback :read + @callback_mutex.synchronize { + @state = data + callbacks.each_value do |array| + array.each { |callback| callback.call(@state) } + end + remove_callback :read + } end end end From 4bb18092d9735086313c92b186f7bc9e24fe4131 Mon Sep 17 00:00:00 2001 From: Vickash Date: Sat, 7 Feb 2015 11:12:58 -0400 Subject: [PATCH 068/296] Forgot to call super in initialize --- lib/dino/components/mixins/callbacks.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/dino/components/mixins/callbacks.rb b/lib/dino/components/mixins/callbacks.rb index 424a7bb9..46527bef 100644 --- a/lib/dino/components/mixins/callbacks.rb +++ b/lib/dino/components/mixins/callbacks.rb @@ -3,6 +3,7 @@ module Components module Mixins module Callbacks def initialize + super @callbacks = {} @callback_mutex = Mutex.new end From 8c5724c031606653f8fa68b8173709c03c3c349c Mon Sep 17 00:00:00 2001 From: Vickash Date: Sat, 7 Feb 2015 11:23:01 -0400 Subject: [PATCH 069/296] Use after_initalize instead --- lib/dino/components/mixins/callbacks.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dino/components/mixins/callbacks.rb b/lib/dino/components/mixins/callbacks.rb index 46527bef..5129f132 100644 --- a/lib/dino/components/mixins/callbacks.rb +++ b/lib/dino/components/mixins/callbacks.rb @@ -2,7 +2,7 @@ module Dino module Components module Mixins module Callbacks - def initialize + def after_initialize(options={}) super @callbacks = {} @callback_mutex = Mutex.new From 7fcf387132dbda80f7ae34d203e71cb113da7e9e Mon Sep 17 00:00:00 2001 From: Vickash Date: Sat, 7 Feb 2015 11:37:13 -0400 Subject: [PATCH 070/296] Fix error leftover from cloning callbacks --- lib/dino/components/mixins/callbacks.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/dino/components/mixins/callbacks.rb b/lib/dino/components/mixins/callbacks.rb index 5129f132..16da6723 100644 --- a/lib/dino/components/mixins/callbacks.rb +++ b/lib/dino/components/mixins/callbacks.rb @@ -17,7 +17,7 @@ def add_callback(key=:persistent, &block) def remove_callback(key=nil) @callback_mutex.synchronize { - key ? @new_callbacks[key] = [] : @new_callbacks = {} + key ? @callbacks[key] = [] : @callbacks = {} } end @@ -25,13 +25,13 @@ def remove_callback(key=nil) alias :remove_callbacks :remove_callback def update(data) + @state = data @callback_mutex.synchronize { - @state = data - callbacks.each_value do |array| + @callbacks.each_value do |array| array.each { |callback| callback.call(@state) } end - remove_callback :read } + remove_callback :read end end end From 55b4385ed10829b7e42b9418006782dc33c85983 Mon Sep 17 00:00:00 2001 From: Vickash Date: Sat, 7 Feb 2015 18:29:58 -0400 Subject: [PATCH 071/296] Include a C library for the Due to compile --- src/lib/Dino.cpp | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 404c05ea..24d27699 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -4,13 +4,17 @@ #include "Arduino.h" #include "Dino.h" +#if defined(__SAM3X8E__) + #include +#endif + DinoLCD dinoLCD; DHT dht; + // SoftwareSerial doesn't work on the Due yet. #if !defined(__SAM3X8E__) DinoSerial dinoSerial; -#endif - +#endif Dino::Dino(){ messageFragments[0] = cmdStr; @@ -94,10 +98,10 @@ void Dino::process() { case 98: setHeartRate (); break; default: break; } - + // Write the response. if (response[0] != '\0') writeResponse(); - + #ifdef debug Serial.print("Responded with - "); Serial.println(response); Serial.println(); @@ -132,7 +136,7 @@ void Dino::updateDigitalListeners() { if (rval != digitalListenerValues[i]) { digitalListenerValues[i] = rval; writeResponse(); - } + } } } } @@ -187,7 +191,7 @@ void Dino::dWrite() { } // CMD = 02 // Digital Read -void Dino::dRead() { +void Dino::dRead() { rval = digitalRead(pin); sprintf(response, "%02d:%02d", pin, rval); } @@ -327,7 +331,7 @@ void Dino::ds18Read() { byte data[12]; byte addr[8]; - + if ( !ds.search(addr)) { ds.reset_search(); return; @@ -348,15 +352,15 @@ void Dino::ds18Read() { ds.write(0x44,1); // start conversion, with parasite power on at the end byte present = ds.reset(); - ds.select(addr); + ds.select(addr); ds.write(0xBE); // Read Scratchpad for (int i = 0; i < 9; i++) { // we need 9 bytes data[i] = ds.read(); } - + ds.reset_search(); - + byte MSB = data[1]; byte LSB = data[0]; @@ -419,4 +423,3 @@ void Dino::setHeartRate() { Serial.print("Heart rate set to "); Serial.print(heartRate); Serial.println(" microseconds"); #endif } - From a0d923ce8d6b121d6de5caada2b25848aced4c2c Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Sat, 7 Feb 2015 21:54:50 -0400 Subject: [PATCH 072/296] Better behavior when using a shift register as a board proxy --- examples/load.rb | 93 ------------------- examples/shift_register/shift_register.rb | 17 ++++ .../shift_register_ssd.rb} | 13 +-- lib/dino/components/mixins.rb | 3 +- lib/dino/components/mixins/board_proxy.rb | 31 +++++++ lib/dino/components/shift_register.rb | 71 +++++++------- 6 files changed, 91 insertions(+), 137 deletions(-) delete mode 100644 examples/load.rb create mode 100644 examples/shift_register/shift_register.rb rename examples/{shift_register.rb => shift_register/shift_register_ssd.rb} (62%) create mode 100644 lib/dino/components/mixins/board_proxy.rb diff --git a/examples/load.rb b/examples/load.rb deleted file mode 100644 index e70cb96e..00000000 --- a/examples/load.rb +++ /dev/null @@ -1,93 +0,0 @@ -#c -# This is a simple example to blink an led -# every half a second -# - -require File.expand_path('../../lib/dino', __FILE__) -# txrx = Dino::TxRx.new -# txrx = Dino::TxRx::TCP.new("192.168.0.77") -# txrx = Dino::TxRx::TCP.new("192.168.0.143", 3001) # ; txrx.io; sleep 5 -# board = Dino::Board.new(txrx) - -# txrx.write("!9800010.") - - - -board = Dino::Board.new(Dino::TxRx::Serial.new) #(device: "/dev/tty.usbserial-A9015AK7")) -servo = Dino::Components::Servo.new(pin: 9, board: board) -led = Dino::Components::Led.new(pin: '13', board: board) - -# board.heart_rate = 10 -# board.analog_divider = 8 - -counter_analog = 0 -counter_digital = 0 -test_time = 2 - -analog_count = 5 -digital_count = 6 - -# led.pulse(0, 128) - -# Setup hardware -(0..5).each do |pin| - eval "$sensor#{pin} = Dino::Components::Sensor.new(pin: 'A#{pin}', board: board)" - eval "$sensor#{pin}.when_data_received { counter_analog = counter_analog + 1 }" -end -(5..9).each do |pin| - eval "$digital#{pin} = Dino::Components::Button.new(pin: '#{pin}', board: board, pullup: true)" - eval "$digital#{pin}.up { counter_digital = counter_digital + 1 }" - eval "$digital#{pin}.down { counter_digital = counter_digital + 1 }" -end - - - - -puts "Starting test..." -start_time = Time.now - -loop do - - 40.times do - led.off - led.on - end - - # [0, 90].each do |pos| - # servo.position = pos - # end - - # Pre-test measurement - state1_digital = counter_digital - state1_analog = counter_analog - - # Wait - sleep test_time - - # Post-test measurement - state2_digital = counter_digital - state2_analog = counter_analog - - # Calculation - diff_digital = state2_digital - state1_digital - diff_analog = state2_analog - state1_analog - - # Print - print "#{digital_count -1}D/#{analog_count + 1}A Components: " - print "#{diff_digital/(test_time * (digital_count - 1))}Hz" - print "/" - print "#{diff_analog/(test_time * (analog_count + 1))}Hz per component | " - puts "#{(diff_digital + diff_analog)/(test_time)} responses/s overall" - - print "Elapsed time: " - puts Time.now - start_time - -end - -# Remove hardware -(0..5).each do |pin| - board.remove_analog_hardware(eval "$sensor#{pin}") -end -(5..10).each do |pin| - board.remove_digital_hardware(eval "$digital#{pin}") -end diff --git a/examples/shift_register/shift_register.rb b/examples/shift_register/shift_register.rb new file mode 100644 index 00000000..fe5945b6 --- /dev/null +++ b/examples/shift_register/shift_register.rb @@ -0,0 +1,17 @@ +# +# This is a simple example to write to a shift register. +# Writing a byte of 255 sets all the output pins to high. +# +require 'bundler/setup' +require 'dino' + +board = Dino::Board.new(Dino::TxRx::Serial.new) +shift_register = Dino::Components::ShiftRegister.new(pins: {data: 11, latch: 8, clock: 12}, board: board) + +# Write a single byte +shift_register.write(255) + +# Write an array of bytes (for multiple registers). +shift_register.write([255, 0]) + +sleep diff --git a/examples/shift_register.rb b/examples/shift_register/shift_register_ssd.rb similarity index 62% rename from examples/shift_register.rb rename to examples/shift_register/shift_register_ssd.rb index ab916143..7af26409 100644 --- a/examples/shift_register.rb +++ b/examples/shift_register/shift_register_ssd.rb @@ -1,6 +1,7 @@ # -# This is a simple example to write to a shift register. -# Writing a byte of 255 sets all the output pins to high. +# This examples sets up a shift register, and an SSD that is connected to its 8 output pins. +# The shift register is passed in as the 'board' when setting up the SSD. +# The individual pins on the register are accessible by the SSD instance, so it works as usual. # require 'bundler/setup' require 'dino' @@ -18,11 +19,3 @@ # Display each new line on the ssd loop { ssd.display(gets.chomp) } - -# Write a single byte -# shift_register.write_byte(255) - -# Write an array of bytes (for multiple registers). -# shift_register.write_bytes([255, 0]) - -# sleep diff --git a/lib/dino/components/mixins.rb b/lib/dino/components/mixins.rb index f889b4e0..6e8afe56 100644 --- a/lib/dino/components/mixins.rb +++ b/lib/dino/components/mixins.rb @@ -6,6 +6,7 @@ module Mixins require 'dino/components/mixins/reader' require 'dino/components/mixins/poller' require 'dino/components/mixins/listener' + require 'dino/components/mixins/board_proxy' end end -end \ No newline at end of file +end diff --git a/lib/dino/components/mixins/board_proxy.rb b/lib/dino/components/mixins/board_proxy.rb new file mode 100644 index 00000000..30f7f6b8 --- /dev/null +++ b/lib/dino/components/mixins/board_proxy.rb @@ -0,0 +1,31 @@ +module Dino + module Components + module Mixins + module BoardProxy + def after_initialize(options={}) + @high = 1 + @low = 0 + @components = [] + end + + attr_reader :high, :low, :components + + def add_component(component) + @components << component + end + + def remove_component(component) + @components.delete(component) + end + + def convert_pin(pin) + pin = pin.to_i + end + + def set_pin_mode(pin, mode) + nil + end + end + end + end +end diff --git a/lib/dino/components/shift_register.rb b/lib/dino/components/shift_register.rb index 9eb6ccd1..a0b18d13 100644 --- a/lib/dino/components/shift_register.rb +++ b/lib/dino/components/shift_register.rb @@ -4,61 +4,66 @@ class ShiftRegister # # options = {board: my_board, pins: {clock: clock_pin, latch: latch_pin, data: data_pin} # - include Setup::MultiPin + include Setup::MultiPin proxy_pins clock: Basic::DigitalOutput, latch: Basic::DigitalOutput, data: Basic::DigitalOutput - - attr_reader :high, :low, :components def after_initialize(options={}) - @high = 1 - @low = 0 - @components = [] - - @state = [0,0,0,0,0,0,0,0] + @bytes = options[:bytes] || 1 + @state = Array.new(@bytes*8) {|i| 0} write_state + super end - def add_component(component) - @components << component + def write_state + bytes = [] + @state.each_slice(8) do |slice| + bytes << slice.join("").reverse.to_i(2) + end + write_bytes(bytes) end - def remove_component(component) - @components.delete(component) + def write_bytes(bytes) + latch.low + bytes.each do |byte| + board.write Dino::Message.encode(command: 11, pin: data.pin, value: byte, aux_message: clock.pin) + end + latch.high end - def convert_pin(pin) - pin = pin.to_i - end + alias :write_byte :write_bytes - def set_pin_mode(pin, mode) - nil - end + # + # Make the shift register behave like a board. + # We can use each output pin on it individually for digital out components. + # To set up component, use the register object as the 'board', and the corresponding pin numbers. + # + include Mixins::BoardProxy + include Mixins::Threaded def digital_write(pin, value) + @last_input = Time.now @state[pin] = value - write_state + start_write end alias :write :digital_write - def write_state - byte = @state.join("").reverse.to_i(2) - write_bytes(byte) - end - - def write_bytes(bytes) - bytes = [bytes] unless bytes.class == Array - - latch.low - bytes.each do |byte| - board.write Dino::Message.encode(command: 11, pin: data.pin, value: byte, aux_message: clock.pin) + # + # Wait until we have not had a digital_write for 1ms before writing to the board. + # Reduces the amount of wrting required for things like SSDs that change many bits in sequence. + # + def start_write + return if @thread + @last_output = Time.now + threaded_loop do + if ((Time.now - @last_input) > 0.001) && (@last_input > @last_output) + write_state + @last_output = Time.now + end end - latch.high end - - alias :write_byte :write_bytes end end end From 7fa7fcabf9964e92dc8c04b50d9b4f8961bfea99 Mon Sep 17 00:00:00 2001 From: Vickash Date: Fri, 20 Feb 2015 21:00:58 -0400 Subject: [PATCH 073/296] Test 2.0.0, 2.2.0 and jruby. Bare minimum work to get tests passing. --- .travis.yml | 7 ++-- lib/dino/components/shift_register.rb | 9 +++-- spec/lib/board_spec.rb | 13 ++++---- .../components/basic/digital_input_spec.rb | 33 ++++++++++--------- spec/lib/components/setup/input_spec.rb | 4 +-- 5 files changed, 34 insertions(+), 32 deletions(-) diff --git a/.travis.yml b/.travis.yml index 43351e51..96ba4de9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,8 @@ language: ruby rvm: + - 2.2.0 + - 2.0.0 - 1.9.3 - - 1.9.2 -script: bundle exec rspec spec \ No newline at end of file + - jruby-19mode + +script: bundle exec rspec spec diff --git a/lib/dino/components/shift_register.rb b/lib/dino/components/shift_register.rb index a0b18d13..7e09f105 100644 --- a/lib/dino/components/shift_register.rb +++ b/lib/dino/components/shift_register.rb @@ -24,20 +24,21 @@ def write_state write_bytes(bytes) end - def write_bytes(bytes) + def write_bytes(*bytes) latch.low - bytes.each do |byte| + bytes.flatten.each do |byte| board.write Dino::Message.encode(command: 11, pin: data.pin, value: byte, aux_message: clock.pin) end latch.high end alias :write_byte :write_bytes + alias :write :write_bytes # # Make the shift register behave like a board. # We can use each output pin on it individually for digital out components. - # To set up component, use the register object as the 'board', and the corresponding pin numbers. + # To instantiate a component, pass the register as a 'board' and the corresponding pin numbers on the register. # include Mixins::BoardProxy include Mixins::Threaded @@ -48,8 +49,6 @@ def digital_write(pin, value) start_write end - alias :write :digital_write - # # Wait until we have not had a digital_write for 1ms before writing to the board. # Reduces the amount of wrting required for things like SSDs that change many bits in sequence. diff --git a/spec/lib/board_spec.rb b/spec/lib/board_spec.rb index 5d9262f7..2c9d161f 100644 --- a/spec/lib/board_spec.rb +++ b/spec/lib/board_spec.rb @@ -16,7 +16,7 @@ def io_mock(methods = {}) end it 'should observe the io' do - io_mock.should_receive(:add_observer).with(subject) + expect(io_mock).to receive(:add_observer).with(subject) subject.send(:initialize, io_mock) end @@ -26,11 +26,10 @@ def io_mock(methods = {}) end it 'should define the logic properly' do - subject.should_receive(:analog_resolution=).with(8) - + expect(subject).to receive(:analog_resolution=).with(8) subject.send(:initialize, io_mock) - subject.high.should == 255 - subject.low.should == 0 + expect(subject.high).to equal(255) + expect(subject.low).to equal(0) end end @@ -164,12 +163,12 @@ def io_mock(methods = {}) describe '#set_pullup' do it 'should write high if pullup is enabled' do - io_mock.should_receive(:write).with(Dino::Message.encode(command: 1, pin: 13, value: subject.high)) + expect(io_mock).to receive(:write).with(Dino::Message.encode(command: 1, pin: 13, value: subject.high)) subject.set_pullup(13, true) end it 'should write low if pullup is disabled' do - io_mock.should_receive(:write).with(Dino::Message.encode(command: 1, pin: 13, value: subject.low)) + expect(io_mock).to receive(:write).with(Dino::Message.encode(command: 1, pin: 13, value: subject.low)) subject.set_pullup(13, false) end end diff --git a/spec/lib/components/basic/digital_input_spec.rb b/spec/lib/components/basic/digital_input_spec.rb index a96d5422..8a2087c3 100644 --- a/spec/lib/components/basic/digital_input_spec.rb +++ b/spec/lib/components/basic/digital_input_spec.rb @@ -8,32 +8,33 @@ module Basic let(:options) { { pin: 'A0', board: board } } subject { DigitalInput.new(options) } - describe '#initialize' do - it 'should start listening immediately' do - board.should_receive(:digital_listen).with(14) - - component = DigitalInput.new(options) - end + it 'should start listening immediately' do + expect(board).to receive(:digital_listen).with(14) + component = DigitalInput.new(options) end describe '#_read' do - it 'should send #digital_read to the board with its pin' do - board.should_receive(:digital_read).with(subject.pin) + it 'should call board#digital_read with its pin once' do + expect(board).to receive(:digital_read).with(subject.pin).once subject._read end end describe '#_listen' do - it 'should send #digital_listen to the board with its pin' do - subject.stop - board.should_receive(:digital_listen).with(subject.pin) - + it 'should call board#digital_listen with its pin once' do + expect(board).to receive(:digital_listen).with(subject.pin).once subject._listen end end context 'callbacks' do before :each do + # Simulate the mutex + mutex = double + allow(mutex).to receive(:synchronize).and_yield + subject.instance_variable_set(:@callback_mutex, mutex) + subject.instance_variable_set(:@callbacks, {}) + @low_callback = double @high_callback = double subject.on_low { @low_callback.called } @@ -42,8 +43,8 @@ module Basic describe '#on_low' do it 'should add a callback that only gets fired when LOW' do - @low_callback.should_receive(:called) - @high_callback.should_not_receive(:called) + expect(@low_callback).to receive(:called) + expect(@high_callback).not_to receive(:called) subject.update(DigitalInput::LOW) end @@ -51,8 +52,8 @@ module Basic describe '#on_high' do it 'should add a callback that only gets fired when HIGH' do - @low_callback.should_not_receive(:called) - @high_callback.should_receive(:called) + expect(@high_callback).to receive(:called) + expect(@low_callback).not_to receive(:called) subject.update(DigitalInput::HIGH) end diff --git a/spec/lib/components/setup/input_spec.rb b/spec/lib/components/setup/input_spec.rb index ae86375f..ab1f71b7 100644 --- a/spec/lib/components/setup/input_spec.rb +++ b/spec/lib/components/setup/input_spec.rb @@ -29,8 +29,8 @@ class InputComponent end it 'should set the pulllup if included in options' do - board.should_receive(:set_pullup).with(subject.pin, true) - subject + expect(board).to receive(:set_pullup).with(subject.pin, true) + InputComponent.new(options) end it 'should tell the board to start reading' do From ee937d39ba6d91ab31cebac35e8b37f78027427e Mon Sep 17 00:00:00 2001 From: Vickash Date: Sat, 21 Feb 2015 19:01:25 -0400 Subject: [PATCH 074/296] Better tests for Board. Move BoardNotFound into TxRx module. --- lib/dino.rb | 1 - lib/dino/board.rb | 21 +- lib/dino/board_not_found.rb | 3 - lib/dino/tx_rx/base.rb | 2 + spec/lib/board_not_found_spec.rb | 8 - spec/lib/board_spec.rb | 207 ++++++++++-------- .../components/basic/digital_output_spec.rb | 4 +- spec/lib/tx_rx/serial_spec.rb | 2 +- spec/lib/tx_rx/tcp_spec.rb | 2 +- spec/spec_helper.rb | 2 +- 10 files changed, 131 insertions(+), 121 deletions(-) delete mode 100644 lib/dino/board_not_found.rb delete mode 100644 spec/lib/board_not_found_spec.rb diff --git a/lib/dino.rb b/lib/dino.rb index c7c3dfb1..b63aa07a 100644 --- a/lib/dino.rb +++ b/lib/dino.rb @@ -1,4 +1,3 @@ -require 'dino/board_not_found' require 'dino/version' require 'dino/message' require 'dino/tx_rx' diff --git a/lib/dino/board.rb b/lib/dino/board.rb index e3a1c95e..57f63c36 100644 --- a/lib/dino/board.rb +++ b/lib/dino/board.rb @@ -4,22 +4,18 @@ class Board DIVIDERS = [1, 2, 4, 8, 16, 32, 64, 128] def initialize(io, options={}) - @bits = options[:bits] || 8 @io, @components = io, [] io.add_observer(self) @analog_zero, @dac_zero = @io.handshake.to_s.split(",").map { |pin| pin.to_i } - define_logic - end - - def define_logic - @low = 0 - @high = (2 ** @bits) - 1 - self.analog_resolution = @bits + self.analog_resolution = options[:bits] end def analog_resolution=(value) - write Dino::Message.encode(command: 96, value: value) + @bits = value || 8 + write Dino::Message.encode(command: 96, value: @bits) + @low = 0 + @high = (2 ** @bits) - 1 end def analog_divider=(value) @@ -97,12 +93,10 @@ def set_pullup(pin, pullup) def convert_pin(pin) pin = pin.to_s - return pin.to_i if pin.match(DIGITAL_REGEX) return analog_pin_to_i(pin) if pin.match(ANALOG_REGEX) return dac_pin_to_i(pin) if pin.match(DAC_REGEX) - - nil + raise "Incorrect pin format" end def analog_pin_to_i(pin) @@ -110,7 +104,8 @@ def analog_pin_to_i(pin) end def dac_pin_to_i(pin) - @dac_zero + pin.gsub(/\Aa/i, '').to_i + raise "The board did not specify any DAC pins" unless @dac_zero + @dac_zero + pin.gsub(/\Adac/i, '').to_i end end end diff --git a/lib/dino/board_not_found.rb b/lib/dino/board_not_found.rb deleted file mode 100644 index 808d86e2..00000000 --- a/lib/dino/board_not_found.rb +++ /dev/null @@ -1,3 +0,0 @@ -module Dino - class BoardNotFound < Exception; end -end diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index 5419fd70..edd85bba 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -3,6 +3,8 @@ module Dino module TxRx + class BoardNotFound < StandardError; end + class Base include Observable diff --git a/spec/lib/board_not_found_spec.rb b/spec/lib/board_not_found_spec.rb deleted file mode 100644 index 8da605cb..00000000 --- a/spec/lib/board_not_found_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require 'spec_helper' - -module Dino - describe BoardNotFound do - it { should be } - it { should be_a Exception } - end -end \ No newline at end of file diff --git a/spec/lib/board_spec.rb b/spec/lib/board_spec.rb index 2c9d161f..3fccbf9f 100644 --- a/spec/lib/board_spec.rb +++ b/spec/lib/board_spec.rb @@ -3,16 +3,14 @@ module Dino describe Dino::Board do def io_mock(methods = {}) - @io ||= double(:io, {add_observer: true, handshake: 14, write: true, read: true, write: nil}.merge(methods)) + @io ||= double(:io, {add_observer: true, handshake: "14,20", write: true, read: true}.merge(methods)) end subject { Board.new(io_mock) } describe '#initialize' do - it 'should take an io class' do - expect { - Board.new(io_mock) - }.to_not raise_exception + it 'should require an io object' do + expect { Board.new() }.to raise_exception end it 'should observe the io' do @@ -21,167 +19,196 @@ def io_mock(methods = {}) end it 'should initiate the handshake' do - io_mock.should_receive(:handshake) + expect(io_mock).to receive(:handshake) subject end - it 'should define the logic properly' do - expect(subject).to receive(:analog_resolution=).with(8) - subject.send(:initialize, io_mock) - expect(subject.high).to equal(255) - expect(subject.low).to equal(0) + it 'should set @analog_zero' do + expect(subject.analog_zero).to equal(14) + end + + it 'should set @dac_zero' do + expect(subject.dac_zero).to equal(20) + end + + it 'should set the analog resolution' do + board = Board.new(io_mock) + expect(board.low).to equal(0) + expect(board.high).to equal(255) end end describe '#update' do - context 'when a component is connected to the pin' do - it 'should call update with the message on the component' do - part = double(:part, pin: 7, pullup: nil) - subject.add_component(part) - other_part = double(:part, pin: 9, pullup: nil) - subject.add_component(other_part) + it 'should pass messages from a pin to the right part' do + subject.add_component(part1 = double(pin: 7)) + subject.add_component(part2 = double(pin: 9)) - part.should_receive(:update).with('wake up!') - subject.update(7, 'wake up!') - end + expect(part1).to receive(:update).with('wake up!') + expect(part2).to_not receive(:update).with('wake up!') + subject.update(7, 'wake up!') end - context 'when a component is not connected to the pin' do - it 'should not do anything' do - expect { - subject.update(5, 'wake up!') - }.to_not raise_exception - end + it 'should silently ignore messages from a pin if there is no part on it' do + expect { subject.update(5, 'wake up!') }.to_not raise_exception end end describe '#add_component' do - it 'should add the component to the board' do - subject.add_component(mock1 = double(:part1, pin: 12, pullup: nil)) - subject.add_component(mock2 = double(:part2, pin: 14, pullup: nil)) - subject.components.should =~ [mock1, mock2] + it 'should put the part in the components array' do + subject.add_component(part = double) + expect(subject.components).to include(part) end end describe '#remove_component' do - it 'should remove the given part from the hardware of the board and stop listening' do - mock = double(:part1, pin: 12, pullup: nil) - subject.add_component(mock) - - subject.should_receive(:stop_listener).with(12) - subject.remove_component(mock) - - subject.components.should == [] + it 'should remove the part from the components array and stop listening' do + subject.add_component(part = double(pin: 12)) + expect(subject).to receive(:stop_listener).with(12) + subject.remove_component(part) + expect(subject.components).to be_empty end end describe '#start_read' do it 'should tell the io to read' do - io_mock.should_receive(:read) - Board.new(io_mock).start_read + expect(io_mock).to receive(:read) + subject.start_read end end describe '#stop_read' do it 'should tell the io to read' do - io_mock.should_receive(:close_read) - Board.new(io_mock).stop_read + expect(io_mock).to receive(:close_read) + subject.stop_read end end describe '#write' do - it 'should return true if the write succeeds' do - @io = nil - board = Board.new(io_mock(write: true)) - board.write('message').should == true + it 'should call #write on the io with the message' do + expect(io_mock).to receive(:write).with('message') + subject.write('message') + end + end + + describe '#convert_pin' do + before(:each) { subject.instance_variable_set(:@analog_zero, 14) } + + it 'should leave numeric pins as is' do + expect(subject.convert_pin '13').to equal(13) + end + + it 'should convert analog pins to numeric form' do + expect(subject.convert_pin 'A1').to equal(15) + end + + it 'should convert dac pins to numeric form' do + expect(subject.convert_pin 'DAC1').to equal(21) + end + + it 'should raise if trying to convert a dac pin and the board has none' do + subject.instance_variable_set(:@dac_zero, nil) + expect { subject.convert_pin 'DAC1' }.to raise_exception(/dac/i) + end + + it 'should raise if trying to convert a wrongly formatted pin' do + expect { subject.convert_pin 'ADC1' }.to raise_exception(/incorrect/i) end end # - # Board commands + # Board API Tests # + describe '#set_pin_mode' do + it 'should send a value of 0 if the pin mode is set to out' do + expect(io_mock).to receive(:write).with(Dino::Message.encode(command: 0, pin: 13, value: 0)) + subject.set_pin_mode(13, :out) + end + + it 'should send a value of 1 if the pin mode is set to in' do + expect(io_mock).to receive(:write).with(Dino::Message.encode(command: 0, pin: 13, value: 1)) + subject.set_pin_mode(13, :in) + end + end + + describe '#set_pullup' do + it 'should write high if pullup is enabled' do + expect(io_mock).to receive(:write).with(Dino::Message.encode(command: 1, pin: 13, value: subject.high)) + subject.set_pullup(13, true) + end + + it 'should write low if pullup is disabled' do + expect(io_mock).to receive(:write).with(Dino::Message.encode(command: 1, pin: 13, value: subject.low)) + subject.set_pullup(13, false) + end + end + describe '#digital_write' do - it 'should write the value to the right pin' do - io_mock.should_receive(:write).with(Dino::Message.encode(command: 1, pin: 1, value: 3)) - subject.digital_write(01, 003) + it 'should digitalWrite the value to the pin' do + expect(io_mock).to receive(:write).with(Dino::Message.encode command: 1, pin: 1, value: 255) + subject.digital_write(01, 255) end end describe '#digital_read' do - it 'should tell the board to read once from the given pin' do - io_mock.should_receive(:write).with(Dino::Message.encode(command: 2, pin: 13)) + it 'should digitalRead once from the given pin' do + expect(io_mock).to receive(:write).with(Dino::Message.encode command: 2, pin: 13) subject.digital_read(13) end end describe '#analog_write' do - it 'should append a append a write to the pin and value' do - io_mock.should_receive(:write).with(Dino::Message.encode(command: 3, pin: 1, value: 3)) + it 'should analogWrite the value to the pin' do + expect(io_mock).to receive(:write).with(Dino::Message.encode command: 3, pin: 1, value: 3) subject.analog_write(01, 3) end end describe '#analog_read' do - it 'should tell the board to read once from the given pin' do - io_mock.should_receive(:write).with(Dino::Message.encode(command: 4, pin: 13)) + it 'should analogRead once from the given pin' do + expect(io_mock).to receive(:write).with(Dino::Message.encode command: 4, pin: 13) subject.analog_read(13) end end describe '#digital_listen' do - it 'should tell the board to continuously read from the given pin' do - io_mock.should_receive(:write).with(Dino::Message.encode(command: 5, pin: 13)) + it 'should start listening for a digital signal on the given pin' do + expect(io_mock).to receive(:write).with(Dino::Message.encode command: 5, pin: 13) subject.digital_listen(13) end end describe '#analog_listen' do - it 'should tell the board to continuously read from the given pin' do - io_mock.should_receive(:write).with(Dino::Message.encode(command: 6, pin: 13)) + it 'should start listening for an analog signal on the given pin' do + expect(io_mock).to receive(:write).with(Dino::Message.encode command: 6, pin: 13) subject.analog_listen(13) end end describe '#stop_listener' do - it 'should tell the board to stop sending values for the given pin' do - io_mock.should_receive(:write).with(Dino::Message.encode(command: 7, pin: 13)) + it 'should stop listening for any signal on the given pin' do + expect(io_mock).to receive(:write).with(Dino::Message.encode command: 7, pin: 13) subject.stop_listener(13) end end - describe '#set_pin_mode' do - it 'should send a value of 0 if the pin mode is set to out' do - io_mock.should_receive(:write).with(Dino::Message.encode(command: 0, pin: 13, value: 0)) - subject.set_pin_mode(13, :out) - end - - it 'should send a value of 1 if the pin mode is set to in' do - io_mock.should_receive(:write).with(Dino::Message.encode(command: 0, pin: 13, value: 1)) - subject.set_pin_mode(13, :in) - end - end - - describe '#set_pullup' do - it 'should write high if pullup is enabled' do - expect(io_mock).to receive(:write).with(Dino::Message.encode(command: 1, pin: 13, value: subject.high)) - subject.set_pullup(13, true) + describe '#analog_resolution=' do + it 'should tell the board to change the resolution' do + expect(io_mock).to receive(:write).with(Dino::Message.encode command: 96, value: 10) + subject.analog_resolution = 10 end - it 'should write low if pullup is disabled' do - expect(io_mock).to receive(:write).with(Dino::Message.encode(command: 1, pin: 13, value: subject.low)) - subject.set_pullup(13, false) + it 'should set @bits' do + subject.analog_resolution = 12 + expect(subject.instance_variable_get(:@bits)).to equal(12) end - end - - describe '#convert_pin' do - before(:each) { subject.instance_variable_set(:@analog_zero, 14) } - it 'should convert alphanumeric pins to numbers' do - subject.convert_pin('A1').should == 15 - end + it 'should set @high and @low correctly' do + subject.analog_resolution = 8 + expect(subject.low).to equal(0) + expect(subject.high).to equal(255) - it 'should leave numeric pins alone' do - subject.convert_pin('13').should == 13 + subject.analog_resolution = 10 + expect(subject.high).to equal(1023) end end end diff --git a/spec/lib/components/basic/digital_output_spec.rb b/spec/lib/components/basic/digital_output_spec.rb index b378471e..2b578453 100644 --- a/spec/lib/components/basic/digital_output_spec.rb +++ b/spec/lib/components/basic/digital_output_spec.rb @@ -11,7 +11,7 @@ module Basic describe '#after_initialize' do it 'should set mode to out and go low' do board.should_receive(:digital_write).with(13, board.low) - DigitalOutput.new(options) + subject end end @@ -42,14 +42,12 @@ module Basic it 'should call high if currently LOW' do subject.low subject.should_receive(:high) - subject.toggle end it 'should call LOW if anything else' do subject.high subject.should_receive(:low) - subject.toggle end end diff --git a/spec/lib/tx_rx/serial_spec.rb b/spec/lib/tx_rx/serial_spec.rb index f03c46b1..fa930801 100644 --- a/spec/lib/tx_rx/serial_spec.rb +++ b/spec/lib/tx_rx/serial_spec.rb @@ -65,7 +65,7 @@ module Dino it 'should raise a BoardNotFound exception if there is no board connected' do ::Serial.stub(:new).and_raise - expect { subject.io }.to raise_exception BoardNotFound + expect { subject.io }.to raise_exception Dino::TxRx::BoardNotFound end end diff --git a/spec/lib/tx_rx/tcp_spec.rb b/spec/lib/tx_rx/tcp_spec.rb index 725b09f5..2b5f0d1f 100644 --- a/spec/lib/tx_rx/tcp_spec.rb +++ b/spec/lib/tx_rx/tcp_spec.rb @@ -10,7 +10,7 @@ module Dino describe "#connect" do it 'should raise a BoardNotFound exception if it cannot connect to the server' do - expect { @instance.io }.to raise_exception BoardNotFound + expect { @instance.io }.to raise_exception Dino::TxRx::BoardNotFound end it 'should return the TCPSocket if connected' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 281f2af1..3ed0a269 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -14,7 +14,7 @@ def self.redefine(const, value, opts={}) module BoardMock def self.included(base) base.class_eval do - let(:txrx) { double(:txrx, add_observer: true, handshake: 14, write: true, read: true) } + let(:txrx) { double(:txrx, add_observer: true, handshake: "14,20", write: true, read: true) } let(:board) { Dino::Board.new(txrx) } end end From 1926aeb14b515de6dd758ab7f7ff6114da167877 Mon Sep 17 00:00:00 2001 From: Vickash Date: Sun, 1 Mar 2015 09:57:44 -0400 Subject: [PATCH 075/296] Fix inheritance of initalize for callbacks module --- lib/dino/components/basic/digital_input.rb | 1 + lib/dino/components/mixins/callbacks.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/dino/components/basic/digital_input.rb b/lib/dino/components/basic/digital_input.rb index 23808aa1..d0788194 100644 --- a/lib/dino/components/basic/digital_input.rb +++ b/lib/dino/components/basic/digital_input.rb @@ -9,6 +9,7 @@ class DigitalInput include Mixins::Listener def after_initialize(options={}) + super(options) _listen end diff --git a/lib/dino/components/mixins/callbacks.rb b/lib/dino/components/mixins/callbacks.rb index 16da6723..eb3a1c3c 100644 --- a/lib/dino/components/mixins/callbacks.rb +++ b/lib/dino/components/mixins/callbacks.rb @@ -3,7 +3,7 @@ module Components module Mixins module Callbacks def after_initialize(options={}) - super + super(options) @callbacks = {} @callback_mutex = Mutex.new end From 57258fb2d64f890dafccc5e250966f8800e9bd8d Mon Sep 17 00:00:00 2001 From: Vickash Date: Sun, 1 Mar 2015 17:05:11 -0400 Subject: [PATCH 076/296] Forgot to put back 5ms sleep in read thread to avoid high cpu usage --- lib/dino/tx_rx/base.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index edd85bba..2b3d21a6 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -4,7 +4,7 @@ module Dino module TxRx class BoardNotFound < StandardError; end - + class Base include Observable @@ -19,6 +19,8 @@ def read if line && line.match(/\A\d+:/) pin, message = line.chop.split(/:/) pin && message && changed && notify_observers(pin, message) + else + sleep 0.005 end end end From 52e7d1d05958fc3681bf92bb6bb5f360cf957138 Mon Sep 17 00:00:00 2001 From: Vickash Date: Mon, 2 Mar 2015 11:07:52 -0400 Subject: [PATCH 077/296] Make the read thread abort on exception. No more silently failing callbacks. --- lib/dino/tx_rx/base.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index 2b3d21a6..b87a31ba 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -24,6 +24,7 @@ def read end end end + @thread.abort_on_exception = true end def close_read From 27ed24ab7c15cc33086335e6f84ac4483f6e22f5 Mon Sep 17 00:00:00 2001 From: Vickash Date: Mon, 2 Mar 2015 11:55:21 -0400 Subject: [PATCH 078/296] Separate Board#read for simpler testing --- lib/dino/tx_rx/base.rb | 23 +++++++++++------------ spec/lib/tx_rx/serial_spec.rb | 18 +++++++++--------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index b87a31ba..1eeee7be 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -12,19 +12,18 @@ def io @io ||= connect end - def read - @thread ||= Thread.new do - loop do - line = gets - if line && line.match(/\A\d+:/) - pin, message = line.chop.split(/:/) - pin && message && changed && notify_observers(pin, message) - else - sleep 0.005 - end - end + def _read + line = gets + if line && line.match(/\A\d+:/) + pin, message = line.chop.split(/:/) + pin && message && changed && notify_observers(pin, message) + else + sleep 0.005 end - @thread.abort_on_exception = true + end + + def read + @thread ||= Thread.new { loop { _read } }.abort_on_exception = true end def close_read diff --git a/spec/lib/tx_rx/serial_spec.rb b/spec/lib/tx_rx/serial_spec.rb index fa930801..4948f4c3 100644 --- a/spec/lib/tx_rx/serial_spec.rb +++ b/spec/lib/tx_rx/serial_spec.rb @@ -69,19 +69,19 @@ module Dino end end - describe '#read' do - it 'should create a new thread' do - Thread.should_receive :new - subject.read - end - - it 'should start a loop and notify observers on changes' do - Thread.should_receive(:new).and_yield - subject.should_receive(:loop).and_yield + describe '#_read' do + it 'should notify observers on change' do subject.should_receive(:gets).and_return("02:00\n") subject.should_receive(:changed).and_return(true) subject.should_receive(:notify_observers).with('02','00') + subject._read + end + end + describe '#read' do + it 'should create a new thread and start looping' do + Thread.should_receive(:new).and_yield.and_return(double("abort_on_exception=" => true)) + subject.should_receive(:loop) subject.read end end From c165218ddf1c6aa5f2711bf14141109f5936a611 Mon Sep 17 00:00:00 2001 From: Vickash Date: Mon, 2 Mar 2015 13:38:36 -0400 Subject: [PATCH 079/296] Expect all pins to be integer after component init. Add #pin to fix MultiPin components. --- lib/dino/board.rb | 2 +- lib/dino/components/setup/multi_pin.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/dino/board.rb b/lib/dino/board.rb index 57f63c36..01fab882 100644 --- a/lib/dino/board.rb +++ b/lib/dino/board.rb @@ -44,7 +44,7 @@ def write(msg) def update(pin, msg) @components.each do |part| - part.update(msg) if convert_pin(pin) == convert_pin(part.pin) + part.update(msg) if pin.to_i == part.pin end end diff --git a/lib/dino/components/setup/multi_pin.rb b/lib/dino/components/setup/multi_pin.rb index 0d1b89df..75c26e14 100644 --- a/lib/dino/components/setup/multi_pin.rb +++ b/lib/dino/components/setup/multi_pin.rb @@ -6,7 +6,7 @@ module MultiPin # Build complex components by modeling them as separate single pin subcomponents. # include Base - attr_reader :pins, :pullups, :proxies + attr_reader :pin, :pins, :pullups, :proxies # # Return a hash with the state of each proxy component. @@ -58,7 +58,7 @@ def proxy_pins(options={}) require_pins(*options.keys) end - proxied_pins = self.class_eval('@@proxied_pins') rescue {} + proxied_pins = self.class_eval('@@proxied_pins') rescue {} proxied_pins.merge!(options) self.class_variable_set(:@@proxied_pins, proxied_pins) end From 6090ebbd87606c4a3198e45948e176c4c1aea471 Mon Sep 17 00:00:00 2001 From: Vickash Date: Mon, 2 Mar 2015 21:49:17 -0400 Subject: [PATCH 080/296] New rspec syntax for TxRx specs. Escape backslashes everywhere. --- lib/dino/message.rb | 2 +- lib/dino/tx_rx/base.rb | 6 +- lib/dino/tx_rx/serial.rb | 20 +++++-- spec/lib/message_spec.rb | 6 +- spec/lib/tx_rx/serial_spec.rb | 108 ++++++++++++++++++---------------- spec/lib/tx_rx/tcp_spec.rb | 6 +- src/lib/Dino.cpp | 31 +++++----- 7 files changed, 101 insertions(+), 78 deletions(-) diff --git a/lib/dino/message.rb b/lib/dino/message.rb index 6dfa1930..af2b8531 100644 --- a/lib/dino/message.rb +++ b/lib/dino/message.rb @@ -5,7 +5,7 @@ def self.encode(options={}) pin = options[:pin] val = options[:value] aux = options[:aux_message] - aux.to_s.gsub!("\n", "\\\n") if aux + aux = aux.to_s.gsub("\\","\\\\\\\\").gsub("\n", "\\\n") if aux raise Exception.new('command must be specified') unless cmd raise Exception.new('commands can only be four digits') if cmd.to_s.length > 4 diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index 1eeee7be..cb58b0c6 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -54,12 +54,12 @@ def handshake raise BoardNotFound end - def write(message); raise "#write should be defined in TxRX subclasses"; end + def write(message); raise "#write should be defined in TxRx subclasses"; end private - def connect(message); raise "#connect should be defined in TxRX subclasses"; end - def gets(message); raise "#gets should be defined in TxRX subclasses"; end + def connect(message); raise "#connect should be defined in TxRx subclasses"; end + def gets(message); raise "#gets should be defined in TxRx subclasses"; end def flush_read gets until gets == nil diff --git a/lib/dino/tx_rx/serial.rb b/lib/dino/tx_rx/serial.rb index 3630fd1e..de7a939e 100644 --- a/lib/dino/tx_rx/serial.rb +++ b/lib/dino/tx_rx/serial.rb @@ -8,7 +8,6 @@ class Serial < Base def initialize(options={}) @device = options[:device] @baud = options[:baud] || BAUD - @first_write = true end def write(message) @@ -33,11 +32,22 @@ def on_windows? end def gets(timeout=0) - buff = io.read(1) - return nil if buff.empty? + buff, escaped = "", false loop do - buff << io.read(1) - return buff if buff[-1] == "\n" + char = io.read(1) + if ["\n", "\\"].include? char + if escaped + buff << char + escaped = false + elsif (char == "\n") + return buff + elsif (char == "\\") + escaped = true + end + else + buff << char + end + return nil if (buff.empty? && !escaped) end end end diff --git a/spec/lib/message_spec.rb b/spec/lib/message_spec.rb index 7a8db344..99dd8aaa 100644 --- a/spec/lib/message_spec.rb +++ b/spec/lib/message_spec.rb @@ -35,9 +35,13 @@ module Dino Dino::Message.encode(command: 1, value: 1, aux_message: "Some Text").should == "1..1.Some Text\n" end - it 'should insert a backslash before any newline inside the aux message' do + it 'should escape newlines inside aux message' do Dino::Message.encode(command: 1, aux_message: "line1\nline2").should == "1...line1\\\nline2\n" end + + it 'should escape backslashes inside aux message' do + Dino::Message.encode(command: 1, aux_message: "line1\\line2").should == "1...line1\\\\line2\n" + end end end end diff --git a/spec/lib/tx_rx/serial_spec.rb b/spec/lib/tx_rx/serial_spec.rb index 4948f4c3..666cf608 100644 --- a/spec/lib/tx_rx/serial_spec.rb +++ b/spec/lib/tx_rx/serial_spec.rb @@ -2,86 +2,68 @@ module Dino describe TxRx::Serial do - it { should be } - - describe '#initialize' do - it 'should set first_write to true' do - TxRx::Serial.new.instance_variable_get(:@first_write).should == true + describe '#connect' do + it 'should use the specified device and baud rate from options hash' do + expect(::Serial).to receive(:new).with("/dev/ttyACM0", 9600).and_return(mock_serial = double) + txrx = Dino::TxRx::Serial.new(device: "/dev/ttyACM0", baud: 9600) + expect(txrx.io).to equal mock_serial end - it 'should set the device and buad if specified' do - txrx = TxRx::Serial.new({device: "/dev/ttyACM0", baud: 9600}) - txrx.instance_variable_get(:@baud).should == 9600 - txrx.instance_variable_get(:@device).should == "/dev/ttyACM0" - end - end - - describe '#io' do context "on windows" do - it 'should instantiate a new SerialPort for the first available tty device' do + it 'should create a new ::Serial object for the connected COM port if non specified' do + # Simulate being on Windows original_platform = RUBY_PLATFORM Constants.redefine(:RUBY_PLATFORM, "mswin", :on => Object) - subject.should_receive(:tty_devices).and_return(["COM1", "COM2", "COM3"]) - # COM2 is chosen as available for this test. - ::Serial.should_receive(:new).with("COM1", TxRx::Serial::BAUD).and_raise - ::Serial.should_receive(:new).with("COM2", TxRx::Serial::BAUD).and_return(mock_serial = double) - ::Serial.should_not_receive(:new).with("COM3", TxRx::Serial::BAUD) + # Simulate 3 COM ports with COM2 connected. + expect(subject).to receive(:tty_devices).and_return(["COM1", "COM2", "COM3"]) + expect(::Serial).to receive(:new).with("COM1", TxRx::Serial::BAUD).and_raise + expect(::Serial).to receive(:new).with("COM2", TxRx::Serial::BAUD).and_return(mock_serial = double) + expect(::Serial).to_not receive(:new).with("COM3", TxRx::Serial::BAUD) + expect(subject.io).to equal(mock_serial) - subject.io.should == mock_serial + # Set platform back to original value Constants.redefine(:RUBY_PLATFORM, original_platform, :on => Object) end end - context "on unix" do - it 'should instantiate a new SerialPort for the first available tty device' do - subject.should_receive(:tty_devices).and_return(['/dev/ttyACM0', '/dev/tty.usbmodem1']) - - # /dev/ttyACM0 is chosen as available for this test. - ::Serial.should_receive(:new).with('/dev/ttyACM0', TxRx::Serial::BAUD).and_return(mock_serial = double) - ::Serial.should_not_receive(:new).with('/dev/tty.usbmodem1', TxRx::Serial::BAUD) + context "on *nix" do + it 'should create a new ::Serial for the first connected tty device if none specified' do + expect(subject).to receive(:tty_devices).and_return(['/dev/ttyACM0', '/dev/tty.usbmodem1']) - subject.io.should == mock_serial + # Simulate /dev/ttyACM0 connected. + expect(::Serial).to receive(:new).with('/dev/ttyACM0', TxRx::Serial::BAUD).and_return(mock_serial = double) + expect(::Serial).to_not receive(:new).with('/dev/tty.usbmodem1', TxRx::Serial::BAUD) + expect(subject.io).to equal(mock_serial) end end - it 'should connect to the specified device at the specified baud rate' do - subject.should_receive(:tty_devices).and_return(["/dev/ttyACM0"]) - ::Serial.should_receive(:new).with('/dev/ttyACM0', 9600).and_return(mock_serial = double) - - subject.instance_variable_set(:@device, "/dev/ttyACM0") - subject.instance_variable_set(:@baud, 9600) - - subject.io.should == mock_serial - end - it 'should use the existing io instance if set' do - subject.should_receive(:tty_devices).once.and_return(['/dev/tty.ACM0', '/dev/tty.usbmodem1']) - ::Serial.stub(:new).and_return(mock_serial = double) - + expect(subject).to receive(:tty_devices).once.and_return(['/dev/tty.ACM0']) + expect(::Serial).to receive(:new).and_return(mock_serial = double) 3.times { subject.io } subject.io.should == mock_serial end it 'should raise a BoardNotFound exception if there is no board connected' do - ::Serial.stub(:new).and_raise + expect(::Serial).to receive(:new).at_least(:once).and_raise expect { subject.io }.to raise_exception Dino::TxRx::BoardNotFound end end describe '#_read' do it 'should notify observers on change' do - subject.should_receive(:gets).and_return("02:00\n") - subject.should_receive(:changed).and_return(true) - subject.should_receive(:notify_observers).with('02','00') + expect(subject).to receive(:gets).and_return("02:00\n") + expect(subject).to receive(:changed).and_return(true) + expect(subject).to receive(:notify_observers).with('02','00') subject._read end end describe '#read' do it 'should create a new thread and start looping' do - Thread.should_receive(:new).and_yield.and_return(double("abort_on_exception=" => true)) - subject.should_receive(:loop) + expect(Thread).to receive(:new).and_yield.and_return(double("abort_on_exception=" => true)) + expect(subject).to receive(:loop) subject.read end end @@ -89,7 +71,7 @@ module Dino describe '#close_read' do it 'should kill the reading thread' do subject.instance_variable_set(:@thread, mock_thread = double) - Thread.should_receive(:kill).with(mock_thread) + expect(Thread).to receive(:kill).with(mock_thread) subject.read subject.close_read end @@ -97,10 +79,36 @@ module Dino describe '#write' do it 'should write to the device' do - subject.stub(:io).and_return(mock_serial = double) - mock_serial.should_receive(:write).with('a message') + expect(subject).to receive(:io).and_return(mock_serial = double) + expect(mock_serial).to receive(:write).with('a message') subject.write('a message') end end + + describe '#gets' do + it 'should read single characters until it hits a newline and strip it' do + expect(subject).to receive(:io).at_least(:once).and_return(mock_serial = double) + expect(mock_serial).to receive(:read).exactly(5).times.with(1).and_return("l", "i", "n", "e", "\n") + expect(subject.send(:gets)).to eq("line") + end + + it 'should catch escaped newlines' do + expect(subject).to receive(:io).at_least(:once).and_return(mock_serial = double) + expect(mock_serial).to receive(:read).exactly(7).times.with(1).and_return("l", "1", "\\", "\n", "l", "2", "\n") + expect(subject.send(:gets)).to eq("l1\nl2") + end + + it 'should return a blank string if there is just a newline' do + expect(subject).to receive(:io).at_least(:once).and_return(mock_serial = double) + expect(mock_serial).to receive(:read).exactly(1).times.with(1).and_return("\n") + expect(subject.send(:gets)).to eq("") + end + + it 'should allow escaped backslashes' do + expect(subject).to receive(:io).at_least(:once).and_return(mock_serial = double) + expect(mock_serial).to receive(:read).exactly(3).times.with(1).and_return("\\", "\\", "\n") + expect(subject.send(:gets)).to eq("\\") + end + end end end diff --git a/spec/lib/tx_rx/tcp_spec.rb b/spec/lib/tx_rx/tcp_spec.rb index 2b5f0d1f..79be3998 100644 --- a/spec/lib/tx_rx/tcp_spec.rb +++ b/spec/lib/tx_rx/tcp_spec.rb @@ -15,21 +15,21 @@ module Dino it 'should return the TCPSocket if connected' do @server = TCPServer.new @port - @instance.io.should be_a TCPSocket + expect(@instance.io).to be_a(TCPSocket) @server.close end end describe '#io' do it 'should set io to a new TCPSocket with the specified host and port' do - TCPSocket.should_receive(:open).with(@host, @port) + expect(TCPSocket).to receive(:open).with(@host, @port) @instance.io end it 'should use the existing io instance if set' do @server = TCPServer.new @port socket = @instance.io - @instance.io.should be socket + expect(@instance.io).to equal(socket) @server.close end end diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 24d27699..7cca89a1 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -25,19 +25,23 @@ Dino::Dino(){ } void Dino::parse(char c) { - // Handle escaped newlines. - if (backslash) { - if (c != '\n') append('\\'); - append(c); - backslash = false; - } + if ((c == '\n') || (c == '\\')) { + // If last char was a \, this \ or \n is escaped. + if(backslash){ + append(c); + backslash = false; + } + + // If EOL, process and reset. + else if (c == '\n'){ + append('\0'); + process(); + fragmentIndex = 0; + charIndex = 0; + } - // If EOL process and reset. - else if (c == '\n') { - append('\0'); - process(); - fragmentIndex = 0; - charIndex = 0; + // Backslash is the escape character. + else if (c == '\\') backslash = true; } // If fragment delimiter, terminate current fragment and move to next. @@ -52,9 +56,6 @@ void Dino::parse(char c) { } } - // Catch backslash so we can escape the next character. - else if (c == '\\') backslash = true; - // Else just append the character. else append(c); } From cf895f6d0df47d6b8af9990d1ec2ffc07c215f0f Mon Sep 17 00:00:00 2001 From: Vickash Date: Mon, 2 Mar 2015 22:20:28 -0400 Subject: [PATCH 081/296] allow instead of expect. Trying to get test passing on travis. --- spec/lib/tx_rx/serial_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/tx_rx/serial_spec.rb b/spec/lib/tx_rx/serial_spec.rb index 666cf608..00826a2f 100644 --- a/spec/lib/tx_rx/serial_spec.rb +++ b/spec/lib/tx_rx/serial_spec.rb @@ -46,7 +46,7 @@ module Dino end it 'should raise a BoardNotFound exception if there is no board connected' do - expect(::Serial).to receive(:new).at_least(:once).and_raise + allow(::Serial).to receive(:new).and_raise expect { subject.io }.to raise_exception Dino::TxRx::BoardNotFound end end From 3a8d73ca4d9da0bdc95537e8b23ba09df04b86fb Mon Sep 17 00:00:00 2001 From: Vickash Date: Mon, 2 Mar 2015 23:30:56 -0400 Subject: [PATCH 082/296] Rspec 3.2 syntax for all tests --- .../lib/components/basic/analog_input_spec.rb | 4 +- .../components/basic/analog_output_spec.rb | 12 +++--- .../components/basic/digital_output_spec.rb | 14 +++---- spec/lib/components/lcd_spec.rb | 38 +++++++++---------- spec/lib/components/rgb_led_spec.rb | 26 ++++++------- spec/lib/components/servo_spec.rb | 11 +++--- spec/lib/components/setup/base_spec.rb | 4 +- spec/lib/components/setup/input_spec.rb | 8 ++-- spec/lib/components/setup/multi_pin_spec.rb | 21 +++++----- spec/lib/components/setup/output_spec.rb | 2 +- spec/lib/components/setup/single_pin_spec.rb | 8 ++-- spec/lib/components/shift_register_spec.rb | 20 +++++----- spec/lib/components/softwareserial_spec.rb | 8 ++-- spec/lib/components/ssd_spec.rb | 26 +++++++------ spec/lib/components/stepper_spec.rb | 18 ++++----- spec/lib/message_spec.rb | 18 ++++----- spec/lib/tx_rx/serial_spec.rb | 2 +- 17 files changed, 121 insertions(+), 119 deletions(-) diff --git a/spec/lib/components/basic/analog_input_spec.rb b/spec/lib/components/basic/analog_input_spec.rb index 5b591fca..f5d95908 100644 --- a/spec/lib/components/basic/analog_input_spec.rb +++ b/spec/lib/components/basic/analog_input_spec.rb @@ -10,14 +10,14 @@ module Basic describe '#_read' do it 'should send #analog_read to the board with its pin' do - board.should_receive(:analog_read).with(subject.pin) + expect(board).to receive(:analog_read).with(subject.pin) subject._read end end describe '#_listen' do it 'should send #analog_listen to the board with its pin' do - board.should_receive(:analog_listen).with(subject.pin) + expect(board).to receive(:analog_listen).with(subject.pin) subject._listen end end diff --git a/spec/lib/components/basic/analog_output_spec.rb b/spec/lib/components/basic/analog_output_spec.rb index 0da24906..1d610320 100644 --- a/spec/lib/components/basic/analog_output_spec.rb +++ b/spec/lib/components/basic/analog_output_spec.rb @@ -10,27 +10,27 @@ module Basic describe '#analog_write' do it 'should update the @state instance variable and call #analog_write on the board' do - board.should_receive(:analog_write).with(subject.pin, 128).once - + expect(board).to receive(:analog_write).with(subject.pin, 128).once + subject.analog_write(128) - subject.state.should == 128 + expect(subject.state).to eq(128) end end describe '#write' do it 'should call #digital_write if value is HIGH' do - subject.should_receive(:digital_write).with(board.high) + expect(subject).to receive(:digital_write).with(board.high) subject.write(board.high) end it 'should call #digital_write if value is LOW' do - subject.should_receive(:digital_write).with(board.low) + expect(subject).to receive(:digital_write).with(board.low) subject.write(board.low) end it 'should call #analog_write if value is anything else' do - subject.should_receive(:analog_write).with(128) + expect(subject).to receive(:analog_write).with(128) subject.write(128) end end diff --git a/spec/lib/components/basic/digital_output_spec.rb b/spec/lib/components/basic/digital_output_spec.rb index 2b578453..ad032cfc 100644 --- a/spec/lib/components/basic/digital_output_spec.rb +++ b/spec/lib/components/basic/digital_output_spec.rb @@ -10,7 +10,7 @@ module Basic describe '#after_initialize' do it 'should set mode to out and go low' do - board.should_receive(:digital_write).with(13, board.low) + expect(board).to receive(:digital_write).with(13, board.low) subject end end @@ -18,22 +18,22 @@ module Basic describe '#digital_write' do it 'should update the @state instance variable and call #digital_write on the board' do subject - board.should_receive(:digital_write).with(subject.pin, board.high).once + expect(board).to receive(:digital_write).with(subject.pin, board.high).once subject.digital_write(board.high) - subject.state.should == board.high + expect(subject.state).to eq(board.high) end end describe '#high' do it 'should call #digital_write with HIGH' do - subject.should_receive(:digital_write).with(board.high) + expect(subject).to receive(:digital_write).with(board.high) subject.high end end describe '#low' do it 'should call #digital_write with LOW' do - subject.should_receive(:digital_write).with(board.low) + expect(subject).to receive(:digital_write).with(board.low) subject.low end end @@ -41,13 +41,13 @@ module Basic describe '#toggle' do it 'should call high if currently LOW' do subject.low - subject.should_receive(:high) + expect(subject).to receive(:high) subject.toggle end it 'should call LOW if anything else' do subject.high - subject.should_receive(:low) + expect(subject).to receive(:low) subject.toggle end end diff --git a/spec/lib/components/lcd_spec.rb b/spec/lib/components/lcd_spec.rb index b6bf5509..9b909707 100644 --- a/spec/lib/components/lcd_spec.rb +++ b/spec/lib/components/lcd_spec.rb @@ -7,121 +7,121 @@ module Components subject { LCD.new board: board, pins: { rs: 12, enable: 11, d4: 5, d5: 4, d6: 3, d7: 2 }, cols: 16, rows: 2 } before do - board.should_receive(:write).with("10..0.12,11,5,4,3,2\n") - board.should_receive(:write).with("10..1.16,2\n") + expect(board).to receive(:write).with("10..0.12,11,5,4,3,2\n") + expect(board).to receive(:write).with("10..1.16,2\n") end describe '#clear' do it 'clears the display' do - board.should_receive(:write).with Dino::Message.encode(command: 10, value: 2) + expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 2) subject.clear end end describe '#home' do it 'Moves the cursor to the first position' do - board.should_receive(:write).with Dino::Message.encode(command: 10, value: 3) + expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 3) subject.home end end describe '#set_cursor' do it 'moves the cursor to the given position' do - board.should_receive(:write).with Dino::Message.encode(command: 10, value: 4, aux_message: "0,1") + expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 4, aux_message: "0,1") subject.set_cursor(0,1) end end describe '#puts' do it 'prints a string in the display' do - board.should_receive(:write).with Dino::Message.encode(command: 10, value: 5, aux_message: "AB") + expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 5, aux_message: "AB") subject.puts("AB") end end describe '#show_cursor' do it 'shows the cursor' do - board.should_receive(:write).with Dino::Message.encode(command: 10, value: 6) + expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 6) subject.show_cursor end end describe '#hide_cursor' do it 'hides the cursor' do - board.should_receive(:write).with Dino::Message.encode(command: 10, value: 7) + expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 7) subject.hide_cursor end end describe '#blink' do it 'shows a blinking cursor' do - board.should_receive(:write).with Dino::Message.encode(command: 10, value: 8) + expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 8) subject.blink end end describe '#no_blink' do it 'stops a blinking cursor' do - board.should_receive(:write).with Dino::Message.encode(command: 10, value: 9) + expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 9) subject.no_blink end end describe '#on' do it 'turn on the display' do - board.should_receive(:write).with Dino::Message.encode(command: 10, value: 10) + expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 10) subject.on end end describe '#off' do it 'turn off the display' do - board.should_receive(:write).with Dino::Message.encode(command: 10, value: 11) + expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 11) subject.off end end describe '#scroll_left' do it 'move the text in the display one position to the left' do - board.should_receive(:write).with Dino::Message.encode(command: 10, value: 12) + expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 12) subject.scroll_left end end describe '#scroll_right' do it 'move the text in the display one position to the right' do - board.should_receive(:write).with Dino::Message.encode(command: 10, value: 13) + expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 13) subject.scroll_right end end describe '#enable_autoscroll' do it 'enable autoscroll' do - board.should_receive(:write).with Dino::Message.encode(command: 10, value: 14) + expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 14) subject.enable_autoscroll end end describe '#disable_autoscroll' do it 'disable autoscroll' do - board.should_receive(:write).with Dino::Message.encode(command: 10, value: 15) + expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 15) subject.disable_autoscroll end end describe '#left_to_right' do it 'set the display writing to start from the left' do - board.should_receive(:write).with Dino::Message.encode(command: 10, value: 16) + expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 16) subject.left_to_right end end describe '#right_to_left' do it 'set the display writing to start from the right' do - board.should_receive(:write).with Dino::Message.encode(command: 10, value: 17) + expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 17) subject.right_to_left end end end end -end \ No newline at end of file +end diff --git a/spec/lib/components/rgb_led_spec.rb b/spec/lib/components/rgb_led_spec.rb index 22dd8088..12859ef6 100644 --- a/spec/lib/components/rgb_led_spec.rb +++ b/spec/lib/components/rgb_led_spec.rb @@ -10,18 +10,18 @@ module Components describe '#initialize' do it 'should create a BaseOutput instance for each pin' do led = RgbLed.new(options) - - led.red.class.should == Basic::AnalogOutput - led.green.class.should == Basic::AnalogOutput - led.blue.class.should == Basic::AnalogOutput + + expect(led.red.class).to eq(Basic::AnalogOutput) + expect(led.green.class).to eq(Basic::AnalogOutput) + expect(led.blue.class).to eq(Basic::AnalogOutput) end end describe '#write' do it 'should write the elements of the array to red, green and blue' do - subject.red.should_receive(:write).with(0) - subject.green.should_receive(:write).with(128) - subject.blue.should_receive(:write).with(0) + expect(subject.red).to receive(:write).with(0) + expect(subject.green).to receive(:write).with(128) + expect(subject.blue).to receive(:write).with(0) subject.write [0, 128, 0] end @@ -29,7 +29,7 @@ module Components describe '#color=' do it 'should write an array of values' do - subject.should_receive(:write).with([128, 0, 0]) + expect(subject).to receive(:write).with([128, 0, 0]) subject.color = [128, 0, 0] end @@ -44,7 +44,7 @@ module Components white: [255, 255, 255], off: [000, 000, 000] } - colors.each_value { |color| subject.should_receive(:write).with(color).twice } + colors.each_value { |color| expect(subject).to receive(:write).with(color).twice } colors.each_key { |key| subject.color = key } colors.each_key { |key| subject.color = key.to_s } @@ -53,10 +53,10 @@ module Components describe '#cycle' do it 'should cycle through the 3 base colors' do - Array.any_instance.should_receive(:cycle).and_yield(:red).and_yield(:green).and_yield(:blue) - subject.should_receive(:color=).with(:red) - subject.should_receive(:color=).with(:green) - subject.should_receive(:color=).with(:blue) + expect_any_instance_of(Array).to receive(:cycle).and_yield(:red).and_yield(:green).and_yield(:blue) + expect(subject).to receive(:color=).with(:red) + expect(subject).to receive(:color=).with(:green) + expect(subject).to receive(:color=).with(:blue) subject.cycle end end diff --git a/spec/lib/components/servo_spec.rb b/spec/lib/components/servo_spec.rb index a8225e9c..839b8ac2 100644 --- a/spec/lib/components/servo_spec.rb +++ b/spec/lib/components/servo_spec.rb @@ -9,7 +9,7 @@ module Components describe '#initialize' do it 'should toggle the servo library on for the pin' do - board.should_receive(:servo_toggle).with(options[:pin], 1) + expect(board).to receive(:servo_toggle).with(options[:pin], 1) subject end end @@ -19,25 +19,24 @@ module Components it 'should set the position of the Servo' do servo.position = 90 - servo.position.should == 90 + expect(servo.position).to eq(90) end it 'should let you write up to 180' do servo.position = 180 - servo.position.should == 180 + expect(servo.position).to eq(180) end it 'should modulate when position > 180' do servo.position = 190 - servo.position.should == 10 + expect(servo.position).to eq(10) end it 'should write the new position to the board' do - board.should_receive(:servo_write).with(13, 10) + expect(board).to receive(:servo_write).with(13, 10) servo.position = 190 end end end end end - diff --git a/spec/lib/components/setup/base_spec.rb b/spec/lib/components/setup/base_spec.rb index ddf19f8e..b9164f1d 100644 --- a/spec/lib/components/setup/base_spec.rb +++ b/spec/lib/components/setup/base_spec.rb @@ -11,7 +11,7 @@ class BaseComponent include Base end subject { BaseComponent.new(options) } - + describe '#initialize' do it 'should require a board' do expect { @@ -20,7 +20,7 @@ class BaseComponent end it 'should register itself as a component on the board' do - board.should_receive(:add_component) + expect(board).to receive(:add_component) BaseComponent.new(options) end end diff --git a/spec/lib/components/setup/input_spec.rb b/spec/lib/components/setup/input_spec.rb index ab1f71b7..4e7c1df8 100644 --- a/spec/lib/components/setup/input_spec.rb +++ b/spec/lib/components/setup/input_spec.rb @@ -16,15 +16,15 @@ class InputComponent describe '#pullup=' do it 'should tell the board to set the pullup mode correctly' do subject - board.should_receive(:set_pullup).with(subject.pin, false) + expect(board).to receive(:set_pullup).with(subject.pin, false) subject.pullup = false - subject.pullup.should == false + expect(subject.pullup).to be(false) end end describe '#initialize_pins' do it 'should set the pin mode to in' do - board.should_receive(:set_pin_mode).with(board.convert_pin(options[:pin]), :in) + expect(board).to receive(:set_pin_mode).with(board.convert_pin(options[:pin]), :in) subject end @@ -34,7 +34,7 @@ class InputComponent end it 'should tell the board to start reading' do - board.should_receive(:start_read) + expect(board).to receive(:start_read) subject end end diff --git a/spec/lib/components/setup/multi_pin_spec.rb b/spec/lib/components/setup/multi_pin_spec.rb index db4b4fad..c9645803 100644 --- a/spec/lib/components/setup/multi_pin_spec.rb +++ b/spec/lib/components/setup/multi_pin_spec.rb @@ -17,17 +17,17 @@ class MultiPinComponent describe "::require_pins" do it 'should add required pins to the @@required_pins class variable' do - MultiPinComponent.class_eval('@@required_pins').should == [:one, :two] + expect(MultiPinComponent.class_eval('@@required_pins')).to eq([:one, :two]) end end describe "::proxy_pins" do it 'should automatically require the pin unless :optional is true' do - MultiPinComponent.class_eval('@@required_pins').should == [:one, :two] + expect(MultiPinComponent.class_eval('@@required_pins')).to eq([:one, :two]) end it 'should add the pins to the @@proxied_pins class variable with the right classes' do - MultiPinComponent.class_eval('@@proxied_pins').should == {two: Basic::DigitalOutput, maybe: Basic::DigitalInput} + expect(MultiPinComponent.class_eval('@@proxied_pins')).to eq({two: Basic::DigitalOutput, maybe: Basic::DigitalInput}) end end @@ -47,25 +47,24 @@ class MultiPinComponent describe '#build_proxies' do it 'should create the correct proxy subcomponents' do - subject.proxies[:two].class.should == Basic::DigitalOutput - subject.proxies[:maybe].class.should == Basic::DigitalInput + expect(subject.proxies[:two].class).to equal(Basic::DigitalOutput) + expect(subject.proxies[:maybe].class).to equal(Basic::DigitalInput) end it 'should create an attr_accessor for each proxy' do - subject.two.class.should == Basic::DigitalOutput - subject.maybe.class.should == Basic::DigitalInput + expect(subject.two).to equal(subject.proxies[:two]) + expect(subject.maybe).to equal(subject.proxies[:maybe]) end it 'should attach the subcomponents to the right pins' do - subject.two.pin.should == 10 - subject.maybe.pin.should == 11 - end + expect(subject.two.pin).to eq(10) + expect(subject.maybe.pin).to eq(11) end end describe '#states' do it 'should return a hash with the state of each subcomponent' do subject.two.high - subject.state.should == {two: board.high, maybe: nil} + expect(subject.state).to eq({two: board.high, maybe: nil}) end end end diff --git a/spec/lib/components/setup/output_spec.rb b/spec/lib/components/setup/output_spec.rb index d2925487..8de06ff6 100644 --- a/spec/lib/components/setup/output_spec.rb +++ b/spec/lib/components/setup/output_spec.rb @@ -15,7 +15,7 @@ class OutputComponent describe '#initialize_pins' do it 'should set the pin mode to out' do - board.should_receive(:set_pin_mode).with(board.convert_pin(options[:pin]), :out) + expect(board).to receive(:set_pin_mode).with(board.convert_pin(options[:pin]), :out) subject end end diff --git a/spec/lib/components/setup/single_pin_spec.rb b/spec/lib/components/setup/single_pin_spec.rb index 9e7a70e2..d34c5584 100644 --- a/spec/lib/components/setup/single_pin_spec.rb +++ b/spec/lib/components/setup/single_pin_spec.rb @@ -11,7 +11,7 @@ class SinglePinComponent include SinglePin end subject { SinglePinComponent.new(options) } - + describe '#initialize' do it 'should require a pin' do expect { @@ -20,17 +20,17 @@ class SinglePinComponent end it 'should convert the pin to an integer' do - board.should_receive(:convert_pin).with(options[:pin]) + expect(board).to receive(:convert_pin).with(options[:pin]) component = SinglePinComponent.new(options) end end describe '#mode=' do it 'should tell the board to set the pin mode' do - board.should_receive(:set_pin_mode).with(subject.pin, :out) + expect(board).to receive(:set_pin_mode).with(subject.pin, :out) subject.send(:mode=, :out) - subject.mode.should == :out + expect(subject.mode).to eq(:out) end end end diff --git a/spec/lib/components/shift_register_spec.rb b/spec/lib/components/shift_register_spec.rb index 826f91ce..246ae490 100644 --- a/spec/lib/components/shift_register_spec.rb +++ b/spec/lib/components/shift_register_spec.rb @@ -9,9 +9,9 @@ module Components describe '#initialize' do it 'should create a BaseOutput instance for each pin' do - subject.clock.class.should == Basic::DigitalOutput - subject.latch.class.should == Basic::DigitalOutput - subject.data.class.should == Basic::DigitalOutput + expect(subject.clock.class).to eq(Basic::DigitalOutput) + expect(subject.latch.class).to eq(Basic::DigitalOutput) + expect(subject.data.class).to eq(Basic::DigitalOutput) end end @@ -19,18 +19,18 @@ module Components before(:each) { subject } it 'should write a single byte as value and clock pin as aux to the data pin' do - subject.latch.should_receive(:digital_write).with(board.low) - board.should_receive(:write).with "11.11.255.12\n" - subject.latch.should_receive(:digital_write).with(board.high) + expect(subject.latch).to receive(:digital_write).with(board.low) + expect(board).to receive(:write).with "11.11.255.12\n" + expect(subject.latch).to receive(:digital_write).with(board.high) subject.write(255) end it 'should write an array of bytes as value and clock pin as aux to the data pin' do - subject.latch.should_receive(:digital_write).with(board.low) - board.should_receive(:write).with "11.11.255.12\n" - board.should_receive(:write).with "11.11.0.12\n" - subject.latch.should_receive(:digital_write).with(board.high) + expect(subject.latch).to receive(:digital_write).with(board.low) + expect(board).to receive(:write).with "11.11.255.12\n" + expect(board).to receive(:write).with "11.11.0.12\n" + expect(subject.latch).to receive(:digital_write).with(board.high) subject.write([255,0]) end diff --git a/spec/lib/components/softwareserial_spec.rb b/spec/lib/components/softwareserial_spec.rb index 05225b86..440a2627 100644 --- a/spec/lib/components/softwareserial_spec.rb +++ b/spec/lib/components/softwareserial_spec.rb @@ -8,16 +8,16 @@ module Components subject { SoftwareSerial.new board: board, pins: { rx: 10, tx: 11 }, baud: 4800 } before do - board.should_receive(:write).with("12..0.10,11\n") - board.should_receive(:write).with("12..1.4800\n") + expect(board).to receive(:write).with("12..0.10,11\n") + expect(board).to receive(:write).with("12..1.4800\n") end describe '#puts' do it 'prints a string to the serial interface' do - board.should_receive(:write).with "12..3.Testing\n" + expect(board).to receive(:write).with "12..3.Testing\n" subject.puts("Testing") end end end end -end \ No newline at end of file +end diff --git a/spec/lib/components/ssd_spec.rb b/spec/lib/components/ssd_spec.rb index fd3d6367..b5ead4f5 100644 --- a/spec/lib/components/ssd_spec.rb +++ b/spec/lib/components/ssd_spec.rb @@ -12,24 +12,28 @@ module Components subject segments = [:a, :b, :c, :d, :e, :f, :g] - segments.each { |segment| subject.proxies[segment].class.should == Basic::DigitalOutput} + segments.each do |segment| + expect(subject.proxies[segment].class).to equal(Basic::DigitalOutput) + end end it "should clear the display" do - SSD.any_instance.should_receive(:clear).once - SSD.new(board: board, pins: pins) + expect(subject).to receive(:clear).once + subject.send(:initialize, {board: board, pins: pins}) end it "should turn on the display" do - SSD.any_instance.should_receive(:on).once - SSD.new(board: board, pins: pins) + expect(subject).to receive(:on).once + subject.send(:initialize, {board: board, pins: pins}) end end describe '#clear' do it 'should toggle all the seven leds to off' do segments = [:a, :b, :c, :d, :e, :f, :g] - segments.each { |segment| subject.proxies[segment].should_receive(:high) } + segments.each do |segment| + expect(subject.proxies[segment]).to receive(:high) + end subject.clear end @@ -37,31 +41,31 @@ module Components describe '#on' do it 'should turn the ssd on' do - subject.anode.should_receive(:digital_write).with(board.high) + expect(subject.anode).to receive(:digital_write).with(board.high) subject.on end end describe '#off' do it 'should turn the ssd off' do - subject.anode.should_receive(:digital_write).with(board.low) + expect(subject.anode).to receive(:digital_write).with(board.low) subject.off end end describe '#display' do it "should scroll on multiple characters" do - subject.should_receive(:scroll).with('foo') + expect(subject).to receive(:scroll).with('foo') subject.display('foo') end it "should make sure the ssd is turned on" do - subject.should_receive(:on) + expect(subject).to receive(:on) subject.display(1) end it "should clear the display on unknown character" do - subject.should_receive(:clear) + expect(subject).to receive(:clear) subject.display('+') end end diff --git a/spec/lib/components/stepper_spec.rb b/spec/lib/components/stepper_spec.rb index 673420aa..b4407123 100644 --- a/spec/lib/components/stepper_spec.rb +++ b/spec/lib/components/stepper_spec.rb @@ -9,17 +9,17 @@ module Components subject { Stepper.new(options) } describe '#initialize' do - it 'should create a BaseOutput instance for each pin' do - subject.step.class.should == Basic::DigitalOutput - subject.direction.class.should == Basic::DigitalOutput + it 'should create a BaseOutput instance for each pin' do + expect(subject.step.class).to equal(Basic::DigitalOutput) + expect(subject.direction.class).to equal(Basic::DigitalOutput) end end describe '#step_cc' do it 'should send high to the step pin with the direction pin high' do - subject.direction.should_receive(:digital_write).with(board.high) - subject.step.should_receive(:digital_write).with(board.high) - subject.step.should_receive(:digital_write).with(board.low) + expect(subject.direction).to receive(:digital_write).with(board.high) + expect(subject.step).to receive(:digital_write).with(board.high) + expect(subject.step).to receive(:digital_write).with(board.low) subject.step_cc end @@ -27,9 +27,9 @@ module Components describe '#step_cw' do it 'should send high to the board with the direction pin low' do - subject.direction.should_receive(:digital_write).with(board.low) - subject.step.should_receive(:digital_write).with(board.high) - subject.step.should_receive(:digital_write).with(board.low) + expect(subject.direction).to receive(:digital_write).with(board.low) + expect(subject.step).to receive(:digital_write).with(board.high) + expect(subject.step).to receive(:digital_write).with(board.low) subject.step_cw end diff --git a/spec/lib/message_spec.rb b/spec/lib/message_spec.rb index 99dd8aaa..4750f559 100644 --- a/spec/lib/message_spec.rb +++ b/spec/lib/message_spec.rb @@ -26,21 +26,21 @@ module Dino end it 'should build messages correctly' do - Dino::Message.encode(command: 1, pin: 1, value: 1).should == "1.1.1\n" - Dino::Message.encode(command: 1, pin: 1).should == "1.1\n" - Dino::Message.encode(command: 1, value: 1).should == "1..1\n" - Dino::Message.encode(command: 1).should == "1\n" - Dino::Message.encode(command: 1, pin: 1, value: 1, aux_message: "Some Text").should == "1.1.1.Some Text\n" - Dino::Message.encode(command: 1, aux_message: "Some Text").should == "1...Some Text\n" - Dino::Message.encode(command: 1, value: 1, aux_message: "Some Text").should == "1..1.Some Text\n" + expect(Dino::Message.encode command: 1, pin: 1, value: 1 ).to eq("1.1.1\n") + expect(Dino::Message.encode command: 1, pin: 1 ).to eq("1.1\n") + expect(Dino::Message.encode command: 1, value: 1 ).to eq("1..1\n") + expect(Dino::Message.encode command: 1 ).to eq("1\n") + expect(Dino::Message.encode command: 1, pin: 1, value: 1, aux_message: "Some Text" ).to eq("1.1.1.Some Text\n") + expect(Dino::Message.encode command: 1, aux_message: "Some Text" ).to eq("1...Some Text\n") + expect(Dino::Message.encode command: 1, value: 1, aux_message: "Some Text" ).to eq("1..1.Some Text\n") end it 'should escape newlines inside aux message' do - Dino::Message.encode(command: 1, aux_message: "line1\nline2").should == "1...line1\\\nline2\n" + expect(Dino::Message.encode command: 1, aux_message: "line1\nline2").to eq("1...line1\\\nline2\n") end it 'should escape backslashes inside aux message' do - Dino::Message.encode(command: 1, aux_message: "line1\\line2").should == "1...line1\\\\line2\n" + expect(Dino::Message.encode command: 1, aux_message: "line1\\line2").to eq("1...line1\\\\line2\n") end end end diff --git a/spec/lib/tx_rx/serial_spec.rb b/spec/lib/tx_rx/serial_spec.rb index 00826a2f..5cc1535b 100644 --- a/spec/lib/tx_rx/serial_spec.rb +++ b/spec/lib/tx_rx/serial_spec.rb @@ -42,7 +42,7 @@ module Dino expect(subject).to receive(:tty_devices).once.and_return(['/dev/tty.ACM0']) expect(::Serial).to receive(:new).and_return(mock_serial = double) 3.times { subject.io } - subject.io.should == mock_serial + expect(subject.io).to equal(mock_serial) end it 'should raise a BoardNotFound exception if there is no board connected' do From 8976767ccc271b8482fd77c319a2060be3b28f15 Mon Sep 17 00:00:00 2001 From: Vickash Date: Tue, 3 Mar 2015 00:21:17 -0400 Subject: [PATCH 083/296] Test the Callbacks mixin properly --- lib/dino/components/mixins/callbacks.rb | 2 +- .../components/basic/digital_input_spec.rb | 6 -- spec/lib/components/mixins/callbacks_spec.rb | 81 +++++++++++++++++++ 3 files changed, 82 insertions(+), 7 deletions(-) create mode 100644 spec/lib/components/mixins/callbacks_spec.rb diff --git a/lib/dino/components/mixins/callbacks.rb b/lib/dino/components/mixins/callbacks.rb index eb3a1c3c..8084ea24 100644 --- a/lib/dino/components/mixins/callbacks.rb +++ b/lib/dino/components/mixins/callbacks.rb @@ -3,7 +3,7 @@ module Components module Mixins module Callbacks def after_initialize(options={}) - super(options) + super(options) if defined?(super) @callbacks = {} @callback_mutex = Mutex.new end diff --git a/spec/lib/components/basic/digital_input_spec.rb b/spec/lib/components/basic/digital_input_spec.rb index 8a2087c3..e4daad23 100644 --- a/spec/lib/components/basic/digital_input_spec.rb +++ b/spec/lib/components/basic/digital_input_spec.rb @@ -29,12 +29,6 @@ module Basic context 'callbacks' do before :each do - # Simulate the mutex - mutex = double - allow(mutex).to receive(:synchronize).and_yield - subject.instance_variable_set(:@callback_mutex, mutex) - subject.instance_variable_set(:@callbacks, {}) - @low_callback = double @high_callback = double subject.on_low { @low_callback.called } diff --git a/spec/lib/components/mixins/callbacks_spec.rb b/spec/lib/components/mixins/callbacks_spec.rb new file mode 100644 index 00000000..5f0186a6 --- /dev/null +++ b/spec/lib/components/mixins/callbacks_spec.rb @@ -0,0 +1,81 @@ +require 'spec_helper' + +module Dino + module Components + module Mixins + describe Callbacks do + + class CallbackComponent + include Callbacks + def initialize; after_initialize; end + end + subject { CallbackComponent.new } + + describe '#initialize' do + it 'should have an empty hash to hold callbacks' do + expect(subject.instance_variable_get :@callbacks).to eq({}) + end + + it 'should have a mutex to protect access to the hash' do + expect(subject.instance_variable_get(:@callback_mutex).class).to equal(Mutex) + end + end + + describe '#add_callback' do + it 'should atomically add a callback to the hash' do + expect(subject.instance_variable_get :@callback_mutex).to receive(:synchronize).once.and_yield + callback = Proc.new{} + subject.add_callback(&callback) + expect(subject.instance_variable_get :@callbacks).to eq({persistent: [callback]}) + end + + it 'should add callbacks under arbitrary keys' do + callback = Proc.new{} + subject.add_callback(:key, &callback) + expect(subject.instance_variable_get :@callbacks).to eq({key: [callback]}) + end + end + + context 'with callbacks added' do + before :each do + @callback1 = Proc.new{} + @callback2 = Proc.new{} + subject.add_callback(&@callback1) + subject.add_callback(:read, &@callback2) + end + + describe '#remove_callback' do + it 'should remove all callbacks if no key given' do + subject.remove_callbacks + expect(subject.instance_variable_get :@callbacks).to eq({}) + end + + it 'should remove only callbacks for a specific key given' do + subject.remove_callbacks(:read) + expect(subject.instance_variable_get(:@callbacks)[:read]).to eq([]) + expect(subject.instance_variable_get(:@callbacks)[:persistent]).to_not eq([]) + end + end + + describe '#update' do + it 'should set the @state variable' do + subject.update("thing") + expect(subject.instance_variable_get :@state).to eq("thing") + end + + it 'should call all the callbacks' do + expect(@callback1).to receive(:call).once + expect(@callback2).to receive(:call).once + subject.update("data") + end + + it 'should remove any callbacks saved with the key :read' do + subject.update("data") + expect(subject.instance_variable_get(:@callbacks)[:read]).to eq([]) + end + end + end + end + end + end +end From a00e3b4e0a49351a579e676014a8cba64e0561fe Mon Sep 17 00:00:00 2001 From: Vickash Date: Tue, 3 Mar 2015 14:04:15 -0400 Subject: [PATCH 084/296] Don't need to #chop trailing \n from messages --- lib/dino/tx_rx/base.rb | 4 ++-- spec/lib/tx_rx/serial_spec.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index cb58b0c6..fee04990 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -15,7 +15,7 @@ def io def _read line = gets if line && line.match(/\A\d+:/) - pin, message = line.chop.split(/:/) + pin, message = line.split(/:/) pin && message && changed && notify_observers(pin, message) else sleep 0.005 @@ -43,7 +43,7 @@ def handshake if line && line.match(/ACK:/) puts "Connected to board..." flush_read - return line.chop.split(/:/)[1] + return line.split(/:/)[1] end end end diff --git a/spec/lib/tx_rx/serial_spec.rb b/spec/lib/tx_rx/serial_spec.rb index 5cc1535b..7a2eb07d 100644 --- a/spec/lib/tx_rx/serial_spec.rb +++ b/spec/lib/tx_rx/serial_spec.rb @@ -53,7 +53,7 @@ module Dino describe '#_read' do it 'should notify observers on change' do - expect(subject).to receive(:gets).and_return("02:00\n") + expect(subject).to receive(:gets).and_return("02:00") expect(subject).to receive(:changed).and_return(true) expect(subject).to receive(:notify_observers).with('02','00') subject._read From 512e3186ac223ae7e998259e423894170fc9bd2c Mon Sep 17 00:00:00 2001 From: Vickash Date: Tue, 3 Mar 2015 14:26:08 -0400 Subject: [PATCH 085/296] TCP lines need \n removed though --- lib/dino/tx_rx/tcp.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/dino/tx_rx/tcp.rb b/lib/dino/tx_rx/tcp.rb index c8783135..edeb1125 100644 --- a/lib/dino/tx_rx/tcp.rb +++ b/lib/dino/tx_rx/tcp.rb @@ -15,7 +15,7 @@ def write(message) end end end - + private def connect @@ -25,7 +25,7 @@ def connect end def gets(timeout=0.005) - IO.select([io], nil, nil, timeout) && io.gets + IO.select([io], nil, nil, timeout) && io.gets.gsub(/\n\z/, "") end end end From 8858b6396945b8971ed6f1f75a20430dd5a71664 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Wed, 23 Mar 2016 17:20:03 -0400 Subject: [PATCH 086/296] Make sure one wire library gets copied when generating a sketch --- lib/dino_cli/generator.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/dino_cli/generator.rb b/lib/dino_cli/generator.rb index b5a234fc..6a2103d1 100644 --- a/lib/dino_cli/generator.rb +++ b/lib/dino_cli/generator.rb @@ -1,6 +1,6 @@ class DinoCLI::Generator require "fileutils" - LIB_FILENAMES = ["Dino.h", "Dino.cpp", "DinoLCD.h", "DinoLCD.cpp", "DinoSerial.cpp","DinoSerial.h", "DHT.cpp", "DHT.h"] + LIB_FILENAMES = ["Dino.h", "Dino.cpp", "DinoLCD.h", "DinoLCD.cpp", "DinoSerial.cpp","DinoSerial.h", "DHT.cpp", "DHT.h", "OneWire.cpp", "OneWire.h"] attr_accessor :options def initialize(options={}) @@ -16,7 +16,7 @@ def self.run!(options={}) end def read - @libs = LIB_FILENAMES.map do |f| + @libs = LIB_FILENAMES.map do |f| File.read(File.join(options[:src_dir], "lib", f)) end @sketch = File.read File.join(options[:src_dir], sketch_filename) From 23921aa8b1097255e537b27990ef244a4f98115b Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Wed, 23 Mar 2016 17:20:18 -0400 Subject: [PATCH 087/296] Use latest version of one wire library --- src/lib/OneWire.cpp | 28 +++++++-- src/lib/OneWire.h | 148 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 169 insertions(+), 7 deletions(-) mode change 100644 => 100755 src/lib/OneWire.cpp mode change 100644 => 100755 src/lib/OneWire.h diff --git a/src/lib/OneWire.cpp b/src/lib/OneWire.cpp old mode 100644 new mode 100755 index 631813f8..efd69491 --- a/src/lib/OneWire.cpp +++ b/src/lib/OneWire.cpp @@ -12,6 +12,11 @@ works on OneWire every 6 to 12 months. Patches usually wait that long. If anyone is interested in more actively maintaining OneWire, please contact Paul. +Version 2.3: + Unknonw chip fallback mode, Roger Clark + Teensy-LC compatibility, Paul Stoffregen + Search bug fix, Love Nystrom + Version 2.2: Teensy 3.0 compatibility, Paul Stoffregen, paul@pjrc.com Arduino Due compatibility, http://arduino.cc/forum/index.php?topic=141030 @@ -339,7 +344,7 @@ void OneWire::target_search(uint8_t family_code) // Return TRUE : device found, ROM number in ROM_NO buffer // FALSE : device not found, end of search // -uint8_t OneWire::search(uint8_t *newAddr) +uint8_t OneWire::search(uint8_t *newAddr, bool search_mode /* = true */) { uint8_t id_bit_number; uint8_t last_zero, rom_byte_number, search_result; @@ -368,7 +373,11 @@ uint8_t OneWire::search(uint8_t *newAddr) } // issue the search command - write(0xF0); + if (search_mode == true) { + write(0xF0); // NORMAL SEARCH + } else { + write(0xEC); // CONDITIONAL SEARCH + } // loop to do the search do @@ -452,8 +461,9 @@ uint8_t OneWire::search(uint8_t *newAddr) LastDeviceFlag = FALSE; LastFamilyDiscrepancy = 0; search_result = FALSE; + } else { + for (int i = 0; i < 8; i++) newAddr[i] = ROM_NO[i]; } - for (int i = 0; i < 8; i++) newAddr[i] = ROM_NO[i]; return search_result; } @@ -509,8 +519,11 @@ uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len) uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len) { uint8_t crc = 0; - + while (len--) { +#if defined(__AVR__) + crc = _crc_ibutton_update(crc, *addr++); +#else uint8_t inbyte = *addr++; for (uint8_t i = 8; i; i--) { uint8_t mix = (crc ^ inbyte) & 0x01; @@ -518,6 +531,7 @@ uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len) if (mix) crc ^= 0x8C; inbyte >>= 1; } +#endif } return crc; } @@ -532,6 +546,11 @@ bool OneWire::check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inv uint16_t OneWire::crc16(const uint8_t* input, uint16_t len, uint16_t crc) { +#if defined(__AVR__) + for (uint16_t i = 0 ; i < len ; i++) { + crc = _crc16_update(crc, input[i]); + } +#else static const uint8_t oddparity[16] = { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 }; @@ -550,6 +569,7 @@ uint16_t OneWire::crc16(const uint8_t* input, uint16_t len, uint16_t crc) cdata <<= 1; crc ^= cdata; } +#endif return crc; } #endif diff --git a/src/lib/OneWire.h b/src/lib/OneWire.h old mode 100644 new mode 100755 index 916c5290..b66b7eba --- a/src/lib/OneWire.h +++ b/src/lib/OneWire.h @@ -3,6 +3,10 @@ #include +#if defined(__AVR__) +#include +#endif + #if ARDUINO >= 100 #include "Arduino.h" // for delayMicroseconds, digitalPinToBitMask, etc #else @@ -45,8 +49,12 @@ #define ONEWIRE_CRC16 1 #endif +#ifndef FALSE #define FALSE 0 +#endif +#ifndef TRUE #define TRUE 1 +#endif // Platform specific I/O definitions @@ -61,7 +69,7 @@ #define DIRECT_WRITE_LOW(base, mask) ((*((base)+2)) &= ~(mask)) #define DIRECT_WRITE_HIGH(base, mask) ((*((base)+2)) |= (mask)) -#elif defined(__MK20DX128__) +#elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__MK64FX512__) #define PIN_TO_BASEREG(pin) (portOutputRegister(pin)) #define PIN_TO_BITMASK(pin) (1) #define IO_REG_TYPE uint8_t @@ -72,6 +80,17 @@ #define DIRECT_WRITE_LOW(base, mask) (*((base)+256) = 1) #define DIRECT_WRITE_HIGH(base, mask) (*((base)+128) = 1) +#elif defined(__MKL26Z64__) +#define PIN_TO_BASEREG(pin) (portOutputRegister(pin)) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint8_t +#define IO_REG_ASM +#define DIRECT_READ(base, mask) ((*((base)+16) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) (*((base)+20) &= ~(mask)) +#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+20) |= (mask)) +#define DIRECT_WRITE_LOW(base, mask) (*((base)+8) = (mask)) +#define DIRECT_WRITE_HIGH(base, mask) (*((base)+4) = (mask)) + #elif defined(__SAM3X8E__) // Arduino 1.5.1 may have a bug in delayMicroseconds() on Arduino Due. // http://arduino.cc/forum/index.php/topic,141030.msg1076268.html#msg1076268 @@ -104,8 +123,131 @@ #define DIRECT_WRITE_LOW(base, mask) ((*(base+8+1)) = (mask)) //LATXCLR + 0x24 #define DIRECT_WRITE_HIGH(base, mask) ((*(base+8+2)) = (mask)) //LATXSET + 0x28 +#elif defined(ARDUINO_ARCH_ESP8266) +#define PIN_TO_BASEREG(pin) ((volatile uint32_t*) GPO) +#define PIN_TO_BITMASK(pin) (1 << pin) +#define IO_REG_TYPE uint32_t +#define IO_REG_ASM +#define DIRECT_READ(base, mask) ((GPI & (mask)) ? 1 : 0) //GPIO_IN_ADDRESS +#define DIRECT_MODE_INPUT(base, mask) (GPE &= ~(mask)) //GPIO_ENABLE_W1TC_ADDRESS +#define DIRECT_MODE_OUTPUT(base, mask) (GPE |= (mask)) //GPIO_ENABLE_W1TS_ADDRESS +#define DIRECT_WRITE_LOW(base, mask) (GPOC = (mask)) //GPIO_OUT_W1TC_ADDRESS +#define DIRECT_WRITE_HIGH(base, mask) (GPOS = (mask)) //GPIO_OUT_W1TS_ADDRESS + +#elif defined(__SAMD21G18A__) +#define PIN_TO_BASEREG(pin) portModeRegister(digitalPinToPort(pin)) +#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) +#define IO_REG_TYPE uint32_t +#define IO_REG_ASM +#define DIRECT_READ(base, mask) (((*((base)+8)) & (mask)) ? 1 : 0) +#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) = (mask)) +#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+2)) = (mask)) +#define DIRECT_WRITE_LOW(base, mask) ((*((base)+5)) = (mask)) +#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+6)) = (mask)) + +#elif defined(RBL_NRF51822) +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) (pin) +#define IO_REG_TYPE uint32_t +#define IO_REG_ASM +#define DIRECT_READ(base, pin) nrf_gpio_pin_read(pin) +#define DIRECT_WRITE_LOW(base, pin) nrf_gpio_pin_clear(pin) +#define DIRECT_WRITE_HIGH(base, pin) nrf_gpio_pin_set(pin) +#define DIRECT_MODE_INPUT(base, pin) nrf_gpio_cfg_input(pin, NRF_GPIO_PIN_NOPULL) +#define DIRECT_MODE_OUTPUT(base, pin) nrf_gpio_cfg_output(pin) + +#elif defined(__arc__) /* Arduino101/Genuino101 specifics */ + +#include "scss_registers.h" +#include "portable.h" +#include "avr/pgmspace.h" + +#define GPIO_ID(pin) (g_APinDescription[pin].ulGPIOId) +#define GPIO_TYPE(pin) (g_APinDescription[pin].ulGPIOType) +#define GPIO_BASE(pin) (g_APinDescription[pin].ulGPIOBase) +#define DIR_OFFSET_SS 0x01 +#define DIR_OFFSET_SOC 0x04 +#define EXT_PORT_OFFSET_SS 0x0A +#define EXT_PORT_OFFSET_SOC 0x50 + +/* GPIO registers base address */ +#define PIN_TO_BASEREG(pin) ((volatile uint32_t *)g_APinDescription[pin].ulGPIOBase) +#define PIN_TO_BITMASK(pin) pin +#define IO_REG_TYPE uint32_t +#define IO_REG_ASM + +static inline __attribute__((always_inline)) +IO_REG_TYPE directRead(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) +{ + IO_REG_TYPE ret; + if (SS_GPIO == GPIO_TYPE(pin)) { + ret = READ_ARC_REG(((IO_REG_TYPE)base + EXT_PORT_OFFSET_SS)); + } else { + ret = MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, EXT_PORT_OFFSET_SOC); + } + return ((ret >> GPIO_ID(pin)) & 0x01); +} + +static inline __attribute__((always_inline)) +void directModeInput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) +{ + if (SS_GPIO == GPIO_TYPE(pin)) { + WRITE_ARC_REG(READ_ARC_REG((((IO_REG_TYPE)base) + DIR_OFFSET_SS)) & ~(0x01 << GPIO_ID(pin)), + ((IO_REG_TYPE)(base) + DIR_OFFSET_SS)); + } else { + MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) &= ~(0x01 << GPIO_ID(pin)); + } +} + +static inline __attribute__((always_inline)) +void directModeOutput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) +{ + if (SS_GPIO == GPIO_TYPE(pin)) { + WRITE_ARC_REG(READ_ARC_REG(((IO_REG_TYPE)(base) + DIR_OFFSET_SS)) | (0x01 << GPIO_ID(pin)), + ((IO_REG_TYPE)(base) + DIR_OFFSET_SS)); + } else { + MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) |= (0x01 << GPIO_ID(pin)); + } +} + +static inline __attribute__((always_inline)) +void directWriteLow(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) +{ + if (SS_GPIO == GPIO_TYPE(pin)) { + WRITE_ARC_REG(READ_ARC_REG(base) & ~(0x01 << GPIO_ID(pin)), base); + } else { + MMIO_REG_VAL(base) &= ~(0x01 << GPIO_ID(pin)); + } +} + +static inline __attribute__((always_inline)) +void directWriteHigh(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) +{ + if (SS_GPIO == GPIO_TYPE(pin)) { + WRITE_ARC_REG(READ_ARC_REG(base) | (0x01 << GPIO_ID(pin)), base); + } else { + MMIO_REG_VAL(base) |= (0x01 << GPIO_ID(pin)); + } +} + +#define DIRECT_READ(base, pin) directRead(base, pin) +#define DIRECT_MODE_INPUT(base, pin) directModeInput(base, pin) +#define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(base, pin) +#define DIRECT_WRITE_LOW(base, pin) directWriteLow(base, pin) +#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(base, pin) + #else -#error "Please define I/O register types here" +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) (pin) +#define IO_REG_TYPE unsigned int +#define IO_REG_ASM +#define DIRECT_READ(base, pin) digitalRead(pin) +#define DIRECT_WRITE_LOW(base, pin) digitalWrite(pin, LOW) +#define DIRECT_WRITE_HIGH(base, pin) digitalWrite(pin, HIGH) +#define DIRECT_MODE_INPUT(base, pin) pinMode(pin,INPUT) +#define DIRECT_MODE_OUTPUT(base, pin) pinMode(pin,OUTPUT) +#warning "OneWire. Fallback mode. Using API calls for pinMode,digitalRead and digitalWrite. Operation of this library is not guaranteed on this architecture." + #endif @@ -178,7 +320,7 @@ class OneWire // might be a good idea to check the CRC to make sure you didn't // get garbage. The order is deterministic. You will always get // the same devices in the same order. - uint8_t search(uint8_t *newAddr); + uint8_t search(uint8_t *newAddr, bool search_mode = true); #endif #if ONEWIRE_CRC From 5af5c1804f5c8a93e24962c3bbac1cc0cc211a35 Mon Sep 17 00:00:00 2001 From: Vickash Mahabir Date: Wed, 23 Mar 2016 17:20:43 -0400 Subject: [PATCH 088/296] Small hack to avoid string -> char* conversion deprecation in latest Arduino IDE --- src/lib/Dino.cpp | 3 ++- src/lib/Dino.h | 13 +++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 7cca89a1..a54004c5 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -17,6 +17,7 @@ DHT dht; #endif Dino::Dino(){ + newline[0] = '\n'; newline[1] = '\0'; messageFragments[0] = cmdStr; messageFragments[1] = pinStr; messageFragments[2] = valStr; @@ -117,7 +118,7 @@ void Dino::setupWrite(void (*writeCallback)(char *str)) { } void Dino::writeResponse() { _writeCallback(response); - _writeCallback("\n"); + _writeCallback(newline); } // LISTNENERS diff --git a/src/lib/Dino.h b/src/lib/Dino.h index eb0364bf..b43733fb 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -38,7 +38,7 @@ class Dino { void setupWrite(void (*writeCallback)(char *str)); void parse(char c); void updateListeners(); - + private: // API-accessible functions. void setMode (); //cmd = 0 @@ -60,7 +60,7 @@ class Dino { void setAnalogResolution (); //cmd = 96 void setAnalogDivider (); //cmd = 97 void setHeartRate (); //cmd = 98 - + // Parser state storage and utility functions. char *messageFragments[4]; byte fragmentIndex; @@ -68,7 +68,7 @@ class Dino { boolean backslash; void append(char c); void process(); - + // Parsed message storage. char cmdStr[5]; int cmd; char pinStr[5]; int pin; @@ -78,7 +78,8 @@ class Dino { // Value and response storage. int rval; char response[16]; - + char newline[2]; + // Use a write callback from the main sketch to respond. void (*_writeCallback)(char *str); void writeResponse(); @@ -92,11 +93,11 @@ class Dino { unsigned int loopCount; unsigned int analogDivider; long timeSince (long event); - + // Listeners correspond to raw pin number by array index, and store boolean. false == disabled. boolean analogListeners[PIN_COUNT]; boolean digitalListeners[PIN_COUNT]; - + // Keep track of the last read values for digital listeners. Only write responses when changed. byte digitalListenerValues[PIN_COUNT]; From a7f29b7711498fdb3be92e4e3489d9d16d333fc8 Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 11 Sep 2017 11:13:27 -0400 Subject: [PATCH 089/296] Add the modified IRremote library as a submodule --- .gitmodules | 3 +++ src/vendor/Arduino-IRremote | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 src/vendor/Arduino-IRremote diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..ae49a950 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/vendor/Arduino-IRremote"] + path = src/vendor/Arduino-IRremote + url = https://github.com/vickash/Arduino-IRremote.git diff --git a/src/vendor/Arduino-IRremote b/src/vendor/Arduino-IRremote new file mode 160000 index 00000000..34fab166 --- /dev/null +++ b/src/vendor/Arduino-IRremote @@ -0,0 +1 @@ +Subproject commit 34fab166bb1021f050e80cfceb7acc0e01360a3e From 76ad10264f927b392aa36ec575e62b87e0254606 Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 11 Sep 2017 11:57:01 -0400 Subject: [PATCH 090/296] Arduino half of infrared send. Reorganize lib/vendor folders for sketches --- lib/dino_cli/generator.rb | 24 +++++++++++++++++++++--- src/lib/Dino.cpp | 9 +++++++++ src/lib/Dino.h | 4 +++- src/{lib => vendor/DHT}/DHT.cpp | 0 src/{lib => vendor/DHT}/DHT.h | 0 src/{lib => vendor/OneWire}/OneWire.cpp | 0 src/{lib => vendor/OneWire}/OneWire.h | 0 7 files changed, 33 insertions(+), 4 deletions(-) rename src/{lib => vendor/DHT}/DHT.cpp (100%) rename src/{lib => vendor/DHT}/DHT.h (100%) rename src/{lib => vendor/OneWire}/OneWire.cpp (100%) rename src/{lib => vendor/OneWire}/OneWire.h (100%) diff --git a/lib/dino_cli/generator.rb b/lib/dino_cli/generator.rb index 6a2103d1..78a2039e 100644 --- a/lib/dino_cli/generator.rb +++ b/lib/dino_cli/generator.rb @@ -1,6 +1,24 @@ class DinoCLI::Generator require "fileutils" - LIB_FILENAMES = ["Dino.h", "Dino.cpp", "DinoLCD.h", "DinoLCD.cpp", "DinoSerial.cpp","DinoSerial.h", "DHT.cpp", "DHT.h", "OneWire.cpp", "OneWire.h"] + LIB_FILENAMES = [ + "lib/Dino.cpp", + "lib/Dino.h", + "lib/DinoLCD.cpp", + "lib/DinoLCD.h", + "lib/DinoSerial.cpp", + "lib/DinoSerial.h", + + "vendor/DHT/DHT.cpp", + "vendor/DHT/DHT.h", + "vendor/OneWire/OneWire.cpp", + "vendor/OneWire/OneWire.h", + "vendor/Arduino-IRremote/boarddefs.h", + "vendor/Arduino-IRremote/IRremote.cpp", + "vendor/Arduino-IRremote/IRremote.h", + "vendor/Arduino-IRremote/IRremoteInt.h", + "vendor/Arduino-IRremote/irSend.cpp" + ] + attr_accessor :options def initialize(options={}) @@ -17,7 +35,7 @@ def self.run!(options={}) def read @libs = LIB_FILENAMES.map do |f| - File.read(File.join(options[:src_dir], "lib", f)) + File.read(File.join(options[:src_dir], f)) end @sketch = File.read File.join(options[:src_dir], sketch_filename) end @@ -52,7 +70,7 @@ def write sketch = File.join(output_dir, sketch_filename) File.open(sketch, 'w') { |f| f.write @sketch } - libs = LIB_FILENAMES.map { |f| File.join(output_dir, f)} + libs = LIB_FILENAMES.map { |f| File.join(output_dir, f.split('/')[-1])} libs.each_with_index do |file, index| File.open(file, 'w') { |f| f.write @libs[index]} end diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index a54004c5..98d708eb 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -10,6 +10,7 @@ DinoLCD dinoLCD; DHT dht; +IRsend irsend; // SoftwareSerial doesn't work on the Due yet. #if !defined(__SAM3X8E__) @@ -328,6 +329,8 @@ void Dino::handleDHT() { #endif } + +// CMD = 15 void Dino::ds18Read() { OneWire ds(pin); @@ -377,6 +380,12 @@ void Dino::ds18Read() { } +// CMD = 16 +void Dino::irSend(){ + irsend.sendRawChar(auxMsg, val); +} + + // CMD = 90 void Dino::reset() { heartRate = 4000; // Default heartRate is ~4ms. diff --git a/src/lib/Dino.h b/src/lib/Dino.h index b43733fb..fd4ca166 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -10,6 +10,7 @@ #include "DinoLCD.h" #include "DHT.h" #include "OneWire.h" +#include "IRremote.h" // SoftwareSerial doesn't work on the Due yet. #if !defined(__SAM3X8E__) @@ -56,6 +57,7 @@ class Dino { void handleSerial (); //cmd = 12 void handleDHT (); //cmd = 13 void ds18Read (); //cmd = 15 + void irSend (); //cmd = 16 void reset (); //cmd = 90 void setAnalogResolution (); //cmd = 96 void setAnalogDivider (); //cmd = 97 @@ -73,7 +75,7 @@ class Dino { char cmdStr[5]; int cmd; char pinStr[5]; int pin; char valStr[5]; int val; - char auxMsg[256]; + char auxMsg[512]; // Value and response storage. int rval; diff --git a/src/lib/DHT.cpp b/src/vendor/DHT/DHT.cpp similarity index 100% rename from src/lib/DHT.cpp rename to src/vendor/DHT/DHT.cpp diff --git a/src/lib/DHT.h b/src/vendor/DHT/DHT.h similarity index 100% rename from src/lib/DHT.h rename to src/vendor/DHT/DHT.h diff --git a/src/lib/OneWire.cpp b/src/vendor/OneWire/OneWire.cpp similarity index 100% rename from src/lib/OneWire.cpp rename to src/vendor/OneWire/OneWire.cpp diff --git a/src/lib/OneWire.h b/src/vendor/OneWire/OneWire.h similarity index 100% rename from src/lib/OneWire.h rename to src/vendor/OneWire/OneWire.h From 0f6c39fdff988ac50cf7f4e0fee01b0dcf37c55c Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 11 Sep 2017 11:57:31 -0400 Subject: [PATCH 091/296] Don't need DHT or OneWire includes at sketch level --- src/dino_ethernet.ino | 4 +--- src/dino_serial.ino | 4 +--- src/dino_wifi.ino | 10 ++++------ 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/dino_ethernet.ino b/src/dino_ethernet.ino index 737c2040..e3da0652 100644 --- a/src/dino_ethernet.ino +++ b/src/dino_ethernet.ino @@ -3,8 +3,6 @@ #include #include #include -#include "DHT.h" -#include "OneWire.h" // SoftwareSerial doesn't work on the Due yet. #if !defined(__SAM3X8E__) @@ -66,7 +64,7 @@ void setup() { void loop() { // Listen for connections. client = server.available(); - + // Handle a connection. if (client) { while (client.connected()) { diff --git a/src/dino_serial.ino b/src/dino_serial.ino index e276e5d3..406e4216 100644 --- a/src/dino_serial.ino +++ b/src/dino_serial.ino @@ -1,8 +1,6 @@ #include "Dino.h" #include #include -#include "DHT.h" -#include "OneWire.h" // SoftwareSerial doesn't work on the Due yet. #if !defined(__SAM3X8E__) @@ -27,7 +25,7 @@ void (*writeCallback)(char *str) = writeResponse; void setup() { serial.begin(115200); - + // Wait for Leonardo serial port to connect. #if defined(__AVR_ATmega32U4__) while(!serial); diff --git a/src/dino_wifi.ino b/src/dino_wifi.ino index 3a99e701..d519706f 100644 --- a/src/dino_wifi.ino +++ b/src/dino_wifi.ino @@ -3,8 +3,6 @@ #include #include #include -#include "DHT.h" -#include "OneWire.h" // SoftwareSerial doesn't work on the Due yet. #if !defined(__SAM3X8E__) @@ -57,13 +55,13 @@ void setup() { Serial.begin(9600); // Try to connect to the specified network. - while ( status != WL_CONNECTED) { + while ( status != WL_CONNECTED) { Serial.print("Attempting to connect to SSID: "); Serial.println(ssid); status = WiFi.begin(ssid, pass); delay(10000); - } - + } + // Start the server. server.begin(); printWifiStatus(); @@ -75,7 +73,7 @@ void setup() { void loop() { // Listen for connections. client = server.available(); - + // Handle a connection. if (client) { while (client.connected()) { From 66c4f1bd4aa1d7b783922d82053116157a5c84b1 Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 11 Sep 2017 18:45:24 -0400 Subject: [PATCH 092/296] Forgot to actually call the infrared send function --- src/lib/Dino.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 98d708eb..d20e0b96 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -95,6 +95,7 @@ void Dino::process() { case 12: handleSerial (); break; case 13: handleDHT (); break; case 15: ds18Read (); break; + case 16: irSend (); break; case 90: reset (); break; case 96: setAnalogResolution (); break; case 97: setAnalogDivider (); break; From 3ad2e1262c524af9f75a0c99846b3f73b9e955a8 Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 11 Sep 2017 20:09:39 -0400 Subject: [PATCH 093/296] Remove need for sendRawChar since we can cast inside Dino.cpp. Still using a modified fork of the IR library to keep memory cost low. --- src/lib/Dino.cpp | 2 +- src/vendor/Arduino-IRremote | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index d20e0b96..1da90a00 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -383,7 +383,7 @@ void Dino::ds18Read() { // CMD = 16 void Dino::irSend(){ - irsend.sendRawChar(auxMsg, val); + irsend.sendRaw((uint16_t*)&auxMsg[1], auxMsg[0], val); } diff --git a/src/vendor/Arduino-IRremote b/src/vendor/Arduino-IRremote index 34fab166..b6cbb73a 160000 --- a/src/vendor/Arduino-IRremote +++ b/src/vendor/Arduino-IRremote @@ -1 +1 @@ -Subproject commit 34fab166bb1021f050e80cfceb7acc0e01360a3e +Subproject commit b6cbb73abc176f241942724a257c7217baeeee5d From 9ebf8df10d1e6a130c5d543c2b080874a369bbd5 Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 12 Sep 2017 12:20:31 -0400 Subject: [PATCH 094/296] Aux length to 512 like sketch. Allows up to 255 IR timings. --- lib/dino/message.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dino/message.rb b/lib/dino/message.rb index af2b8531..91fa9959 100644 --- a/lib/dino/message.rb +++ b/lib/dino/message.rb @@ -11,7 +11,7 @@ def self.encode(options={}) raise Exception.new('commands can only be four digits') if cmd.to_s.length > 4 raise Exception.new('pins can only be four digits') if pin.to_s.length > 4 raise Exception.new('values can only be four digits') if val.to_s.length > 4 - raise Exception.new('auxillary messages are limited to 255 characters') if aux.to_s.length > 255 + raise Exception.new('auxillary messages are limited to 512 characters') if aux.to_s.length > 512 message = "" [aux, val, pin].each do |fragment| From b89064c32db0cd6c6bd36e218b1aae1228b0db3f Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 12 Sep 2017 14:14:02 -0400 Subject: [PATCH 095/296] Add infrared emitter Ruby class and example --- examples/ir_emitter/ir_emitter.rb | 55 +++++++++++++++++++++++++++++++ lib/dino/components.rb | 1 + lib/dino/components/ir_emitter.rb | 42 +++++++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 examples/ir_emitter/ir_emitter.rb create mode 100644 lib/dino/components/ir_emitter.rb diff --git a/examples/ir_emitter/ir_emitter.rb b/examples/ir_emitter/ir_emitter.rb new file mode 100644 index 00000000..8a82f564 --- /dev/null +++ b/examples/ir_emitter/ir_emitter.rb @@ -0,0 +1,55 @@ +# +# This is a simple test of the IREmitter (infrared blaster) class. +# It is based on part of "IRtest2" from the included Arduino library: +# https://github.com/z3t0/Arduino-IRremote/tree/master/examples +# +# To verify your emitter is working, you could flash "IRrecvDump2" from that +# examples directory to a second board. With an IR receiver on the correct pin +# of the board (see https://github.com/z3t0/Arduino-IRremote#hardware-specifications), +# open a serial monitor at 9600 and check that it receives the signal sent by this script. +# +# If you don't have 2 boards, use "IRrecvDump2" to capture a code from a button +# of a remote you have. Copy the raw code (long list of numbers in curly braces), +# from the serial output, replace the curly braces with square brackets, and +# those raw values are now compatible with Ruby and this script. +# +# Flash the dino sketch onto your board and connect the IR emitter. +# Substitute a code you captured for the NEC example below. +# The corresponding action should happen on your device, eg. "Power" on your TV. +# +# Both of these methods require you to have an IR receiver handy. +# If you do not have one, there are IR codes in Raw format for many devices +# available on sites like http://irdb.tk/codes/ +# When formatted as a string of numbers with + and - in front of each number, +# you will need to convert them to the array format before use. +# +require 'bundler/setup' +require 'dino' + +# Note: If testing with 2 boards connected to the same computer, you want to be +# explicit about which serial device this script must use. The relevant +# TxRx call is below, but commented out. Enable it and substitute the approriate +# device for the board that has the IR emitter connected and dino sketch loaded. +txrx = Dino::TxRx::Serial.new +# txrx = Dino::TxRx::Serial.new(device: "/dev/ttyACM0") +board = Dino::Board.new(txrx) + +# Setting the IR emitter pin is currently unsupported. +# Although the value gets passed to the board, it always uses the default pin +# for your specfic board/chip, as defined by the library (in bold) at: +# https://github.com/z3t0/Arduino-IRremote#hardware-specifications +ir = Dino::Components::IREmitter.new(board: board, pin: 3) + +# This is the raw data corresponding to NEC 0x12345678 +code = [9000, 4500, + 560, 560, 560, 560, 560, 560, 560, 1690, + 560, 560, 560, 560, 560, 1690, 560, 560, + 560, 560, 560, 560, 560, 1690, 560, 1690, + 560, 560, 560, 1690, 560, 560, 560, 560, + 560, 560, 560, 1690, 560, 560, 560, 1690, + 560, 560, 560, 1690, 560, 1690, 560, 560, + 560, 560, 560, 1690, 560, 1690, 560, 1690, + 560, 1690, 560, 560, 560, 560, 560, 560, + 560] + +ir.send(code) diff --git a/lib/dino/components.rb b/lib/dino/components.rb index bdbc86d7..4d2c2eec 100644 --- a/lib/dino/components.rb +++ b/lib/dino/components.rb @@ -17,5 +17,6 @@ module Components autoload :SoftwareSerial, 'dino/components/softserial' autoload :DHT, 'dino/components/dht' autoload :DS18B20, 'dino/components/ds18b20' + autoload :IREmitter, 'dino/components/ir_emitter' end end diff --git a/lib/dino/components/ir_emitter.rb b/lib/dino/components/ir_emitter.rb new file mode 100644 index 00000000..cfe16d3f --- /dev/null +++ b/lib/dino/components/ir_emitter.rb @@ -0,0 +1,42 @@ +module Dino + module Components + class IREmitter < Basic::DigitalOutput + def send(pulses=[], options={}) + raise Exception.new('infrared signals are limited to a total of 255 pulses (marks + ticks)') if pulses.length > 255 + pulses.each do |p| + raise Exception.new('invalid infrared signal, please ensure all microsecond durations are integers') unless p.is_a? Integer + raise Exception.new('pulse lengths are limited to 65536 microseconds') if p > 65536 + end + + # Default to 38kHz + frequency = options[:freqency] || 38 + + message = Dino::Message.encode( + command: 16, + # Setting the IR emitter pin is currently unsupported. + # Although the value gets passed through, it always uses the default pin + # for your specfic board/chip, as defined by the library (in bold) at: + # https://github.com/z3t0/Arduino-IRremote#hardware-specifications + pin: pin, + value: frequency, + aux_message: pack(pulses) + ) + board.write(message) + end + + # Pack pulse lengths (in microseconds) into a string (byte array really) such that: + # 1) Each pulse length is converted to a little-endian unsigned 16-bit integer. + # 2) Each pulse occupies 2 consecutive bytes of the byte array. + # 3) The first pulse is at index 1 of the array, and subsequent pulses + # start on consecutive odd-numbered indices. + # 4) The 0th byte of the array contains the total number of pulses (NOT bytes). + # + # This keeps compatbility with the aux format normally used for sending data as + # ASCII, but sends higher density binary data whch the IR library needs. + # + def pack(pulses=[]) + "#{[pulses.count].pack('C')}#{pulses.pack('v*')}" + end + end + end +end From b43296967e1f1842d89f19c22528aa2fd58498d3 Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 12 Sep 2017 15:41:06 -0400 Subject: [PATCH 096/296] Patching in Piezo support by @MinasMazar from #74, since his repo is missing --- lib/dino/board.rb | 7 +++++++ lib/dino/components.rb | 1 + lib/dino/components/piezo.rb | 23 +++++++++++++++++++++++ src/lib/Dino.cpp | 12 ++++++++++++ src/lib/Dino.h | 2 ++ 5 files changed, 45 insertions(+) create mode 100644 lib/dino/components/piezo.rb diff --git a/lib/dino/board.rb b/lib/dino/board.rb index 01fab882..eb0760b1 100644 --- a/lib/dino/board.rb +++ b/lib/dino/board.rb @@ -79,6 +79,8 @@ def set_pullup(pin, pullup) servo_write: '9', dht_read: '13', ds18b20_read: '15' + tone: '20', + no_tone: '21' } PIN_COMMANDS.each_key do |command| @@ -87,6 +89,11 @@ def set_pullup(pin, pullup) end end + # Redefinition tone to accept duration. + def tone(pin, value, duration) + write Dino::Message.encode(command: PIN_COMMANDS[:tone], pin: convert_pin(pin), value: value, aux_message: duration) + end + DIGITAL_REGEX = /\A\d+\z/i ANALOG_REGEX = /\A(a)\d+\z/i DAC_REGEX = /\A(dac)\d+\z/i diff --git a/lib/dino/components.rb b/lib/dino/components.rb index 4d2c2eec..d1b5c4e3 100644 --- a/lib/dino/components.rb +++ b/lib/dino/components.rb @@ -18,5 +18,6 @@ module Components autoload :DHT, 'dino/components/dht' autoload :DS18B20, 'dino/components/ds18b20' autoload :IREmitter, 'dino/components/ir_emitter' + autoload :Piezo, 'dino/components/piezo' end end diff --git a/lib/dino/components/piezo.rb b/lib/dino/components/piezo.rb new file mode 100644 index 00000000..4896646b --- /dev/null +++ b/lib/dino/components/piezo.rb @@ -0,0 +1,23 @@ +module Dino + module Components + class Piezo < Basic::AnalogOutput + include Setup::SinglePin + include Setup::Output + include Mixins::Threaded + interrupt_with :digital_write + + def after_initialize(options={}) + low + end + + # Duration is in mills + def tone(value, duration = 500) + board.tone pin, value, duration + end + + def no_tone + board.no_tone pin + end + end + end +end diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 1da90a00..208e6cc7 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -96,6 +96,8 @@ void Dino::process() { case 13: handleDHT (); break; case 15: ds18Read (); break; case 16: irSend (); break; + case 20: tone (); break; + case 21: noTone (); break; case 90: reset (); break; case 96: setAnalogResolution (); break; case 97: setAnalogDivider (); break; @@ -386,6 +388,16 @@ void Dino::irSend(){ irsend.sendRaw((uint16_t*)&auxMsg[1], auxMsg[0], val); } +// CMD = 20 +void Dino::tone() { + unsigned int duration = atoi(auxMsg); + ::tone(pin, val, duration); +} + +// CMD = 21 +void Dino::noTone() { + ::noTone(pin); +} // CMD = 90 void Dino::reset() { diff --git a/src/lib/Dino.h b/src/lib/Dino.h index fd4ca166..ada36cd6 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -58,6 +58,8 @@ class Dino { void handleDHT (); //cmd = 13 void ds18Read (); //cmd = 15 void irSend (); //cmd = 16 + void tone (); //cmd = 20 + void noTone (); //cmd = 21 void reset (); //cmd = 90 void setAnalogResolution (); //cmd = 96 void setAnalogDivider (); //cmd = 97 From eebbc86a98013715e24297278afe78baa5e29be3 Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 12 Sep 2017 16:06:47 -0400 Subject: [PATCH 097/296] Fix aux message test and forgot a comma --- lib/dino/board.rb | 2 +- spec/lib/message_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/dino/board.rb b/lib/dino/board.rb index eb0760b1..1e081663 100644 --- a/lib/dino/board.rb +++ b/lib/dino/board.rb @@ -78,7 +78,7 @@ def set_pullup(pin, pullup) servo_toggle: '8', servo_write: '9', dht_read: '13', - ds18b20_read: '15' + ds18b20_read: '15', tone: '20', no_tone: '21' } diff --git a/spec/lib/message_spec.rb b/spec/lib/message_spec.rb index 4750f559..c8c5767e 100644 --- a/spec/lib/message_spec.rb +++ b/spec/lib/message_spec.rb @@ -21,8 +21,8 @@ module Dino expect { Dino::Message.encode(command:90, value:90000) }.to raise_exception(/four/) end - it 'should not allow values longer than 4 digits' do - expect { Dino::Message.encode(command:90, aux_message: "0" * 256) }.to raise_exception(/255/) + it 'should not allow aux messages longer than 512' do + expect { Dino::Message.encode(command:90, aux_message: "0" * 513) }.to raise_exception(/512/) end it 'should build messages correctly' do From 3239ffc615eff31df8b3b49052c04aab6a643b81 Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 12 Sep 2017 16:14:25 -0400 Subject: [PATCH 098/296] Testing Travis CI workaround --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 96ba4de9..4c6681f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,8 @@ language: ruby + +before_install: + - gem install bundler + rvm: - 2.2.0 - 2.0.0 From 3481ec20a9e501337d90286a007286cbc8268c23 Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 24 Dec 2017 12:43:16 -0400 Subject: [PATCH 099/296] Update travis ruby version --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4c6681f9..8499f94c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ before_install: - gem install bundler rvm: - - 2.2.0 + - 2.4.1 - 2.0.0 - 1.9.3 - jruby-19mode From 8bb60f77565dc3bc58c631d309493c8bbbfa6279 Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 24 Dec 2017 12:45:42 -0400 Subject: [PATCH 100/296] Must cast IR length as uint8 or it hangs. Stub out tone for now because of timer conflict with IR emitter. --- src/lib/Dino.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 208e6cc7..663b748e 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -385,18 +385,18 @@ void Dino::ds18Read() { // CMD = 16 void Dino::irSend(){ - irsend.sendRaw((uint16_t*)&auxMsg[1], auxMsg[0], val); + irsend.sendRaw((uint16_t*)&auxMsg[1], (uint8_t)auxMsg[0], val); } // CMD = 20 void Dino::tone() { - unsigned int duration = atoi(auxMsg); - ::tone(pin, val, duration); + //unsigned int duration = atoi(auxMsg); + //::tone(pin, val, duration); } // CMD = 21 void Dino::noTone() { - ::noTone(pin); + //::noTone(pin); } // CMD = 90 From 2a9df8af55b1e09d903de9b4ddcf8731266b8cbb Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 24 Dec 2017 12:46:43 -0400 Subject: [PATCH 101/296] Fix spelling error in frequency option. --- lib/dino/components/ir_emitter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dino/components/ir_emitter.rb b/lib/dino/components/ir_emitter.rb index cfe16d3f..930b8823 100644 --- a/lib/dino/components/ir_emitter.rb +++ b/lib/dino/components/ir_emitter.rb @@ -9,7 +9,7 @@ def send(pulses=[], options={}) end # Default to 38kHz - frequency = options[:freqency] || 38 + frequency = options[:frequency] || 38 message = Dino::Message.encode( command: 16, From a732154e93effaf4beaa659ae3bdc99285754670 Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 24 Dec 2017 12:47:18 -0400 Subject: [PATCH 102/296] Remove some unnecessary includes from the main sketch files. --- src/dino_ethernet.ino | 2 -- src/dino_serial.ino | 2 -- src/dino_wifi.ino | 2 -- 3 files changed, 6 deletions(-) diff --git a/src/dino_ethernet.ino b/src/dino_ethernet.ino index e3da0652..40805e90 100644 --- a/src/dino_ethernet.ino +++ b/src/dino_ethernet.ino @@ -1,8 +1,6 @@ #include "Dino.h" #include #include -#include -#include // SoftwareSerial doesn't work on the Due yet. #if !defined(__SAM3X8E__) diff --git a/src/dino_serial.ino b/src/dino_serial.ino index 406e4216..becc210f 100644 --- a/src/dino_serial.ino +++ b/src/dino_serial.ino @@ -1,6 +1,4 @@ #include "Dino.h" -#include -#include // SoftwareSerial doesn't work on the Due yet. #if !defined(__SAM3X8E__) diff --git a/src/dino_wifi.ino b/src/dino_wifi.ino index d519706f..704a058c 100644 --- a/src/dino_wifi.ino +++ b/src/dino_wifi.ino @@ -1,8 +1,6 @@ #include "Dino.h" #include #include -#include -#include // SoftwareSerial doesn't work on the Due yet. #if !defined(__SAM3X8E__) From 48a60eabe50bc0c833294c3c8497587e4f6fd1e1 Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 24 Dec 2017 15:50:44 -0400 Subject: [PATCH 103/296] Clearer comments for how multi pin components work. --- lib/dino/components/setup/multi_pin.rb | 44 ++++++++++++++++---------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/lib/dino/components/setup/multi_pin.rb b/lib/dino/components/setup/multi_pin.rb index 75c26e14..01271f64 100644 --- a/lib/dino/components/setup/multi_pin.rb +++ b/lib/dino/components/setup/multi_pin.rb @@ -3,7 +3,10 @@ module Components module Setup module MultiPin # - # Build complex components by modeling them as separate single pin subcomponents. + # Model complex components by allowing them to use multiple pins. + # Pins may be "required" or not, and "proxied" or not. + # Proxying a pin creates a single-pin component on that pin of the given + # class and stores it in @proxies. # include Base attr_reader :pin, :pins, :pullups, :proxies @@ -26,23 +29,14 @@ def self.included(base) base.extend ClassMethods end - # - # A subcomponent's pin can be required, proxied, or both. - # - # Requiring a pin simply raises an error if a value for it is not specified in the options[:pins] hash. - # It will NOT automatically set up a subcomponent or do anything further. - # This can be useful for native libraries where modeling the pin as a subcomponent is not necessary. - # Or for components with pins that may be left disconnected. - # - # Proxying a pin automatically requires it, and specifies what single-pin component it should be modeled as. - # This module will automatically intialize the proxy component and store it in the @proxies instance variable. - # It also creates an attr_accessor on the singleton class for the subcomponent. - # The accessor and the hash element are both named using the pin's key from the options[:pins] hash. - # - # A proxied pin can be NOT required by including `optional: true` in the options hash passed to #proxy_pins. - # Please see the source for the SSD and RgbLed components for good examples of how all this works. - # module ClassMethods + # + # Requiring a pin simply raises an error if not specified in options[:pins]. + # It will not automatically set up a subcomponent or do anything else. + # This is useful for calling Arduino-native libraries, where we need + # to say what pins we are using, but don't need to do more in Ruby. + # See the LCD component for an example. + # def require_pins(*args) required_pins = self.class_eval('@@required_pins') rescue [] required_pins = (required_pins + args).uniq @@ -50,6 +44,22 @@ def require_pins(*args) end alias :require_pin :require_pins + # + # Proxying a pin models it as a single-pin component and requires it. + # It can be made optional by adding `optional: true` to the hash. + # + # On initializing a multi-pin component, its proxy instances are + # created and stored in the @proxies variable. Call #proxies for access. + # + # Lastly, convenience methods for each proxy are defined on the singleton + # class of the multi-pin instance. + # + # For example, you can call `rgb_led.red` for the AnalogOutput + # instance that controls the red part of that RGB led. + # + # These method names correspond to the hash keys (pin names) defined in + # the class definition call to `proxy_pins`. See RgbLed class for examples. + # def proxy_pins(options={}) if options[:optional] options.reject! { |k| k == :optional } From d987b6684251238353499e8754247c19fa1857aa Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 24 Dec 2017 15:51:27 -0400 Subject: [PATCH 104/296] Better implementation of delayed write when shift register acts as a board. --- lib/dino/components/shift_register.rb | 73 ++++++++++++++++++--------- 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/lib/dino/components/shift_register.rb b/lib/dino/components/shift_register.rb index 7e09f105..777bfdff 100644 --- a/lib/dino/components/shift_register.rb +++ b/lib/dino/components/shift_register.rb @@ -9,22 +9,38 @@ class ShiftRegister latch: Basic::DigitalOutput, data: Basic::DigitalOutput + def after_initialize(options={}) + # + # Before the register is used as a board proxy, we need to know how many + # bytes there are to map the register pins to virtual pins. + # Defaults to 1 byte. Ignore if writing to the register directly. + # @bytes = options[:bytes] || 1 + + # + # When used as a board proxy, store the state of each register + # output pins as a 0 or 1 in an array that is (@bytes * 8) long. + # @state = Array.new(@bytes*8) {|i| 0} write_state - super - end - def write_state - bytes = [] - @state.each_slice(8) do |slice| - bytes << slice.join("").reverse.to_i(2) - end - write_bytes(bytes) + # + # When used as a board proxy, only write sate if @write_delay seconds + # has passed since last input. Looks better for things like SSDs + # where many bits may change in sequence, but not exactly the same time. + # + @write_delay = options[:write_delay] || 0.005 + + super end + # + # Send a single byte per message as text, so 255 as 3 bytes, not 1. + # Use this when writing values directly to the register. + # def write_bytes(*bytes) + latch.low bytes.flatten.each do |byte| board.write Dino::Message.encode(command: 11, pin: data.pin, value: byte, aux_message: clock.pin) @@ -36,32 +52,39 @@ def write_bytes(*bytes) alias :write :write_bytes # - # Make the shift register behave like a board. - # We can use each output pin on it individually for digital out components. - # To instantiate a component, pass the register as a 'board' and the corresponding pin numbers on the register. + # Make the shift register act as a board for components that just need + # digital output pins. To initialize a component, pass the register as a + # 'board' and pin numbers such that pin 0 is the 1st bit of the + # 1st regiser byte, pin 9 is the first bit of the second byte, and so on. # include Mixins::BoardProxy - include Mixins::Threaded - def digital_write(pin, value) - @last_input = Time.now @state[pin] = value - start_write + delayed_write(@state) + end + + # + # Do writes in a separate thread and with a small delay. + # Lets us catch multiple changed bits, like with an SSD. + # + include Mixins::Threaded + def delayed_write(state) + threaded do + sleep @write_delay + write_state if (state == @state) + end end # - # Wait until we have not had a digital_write for 1ms before writing to the board. - # Reduces the amount of wrting required for things like SSDs that change many bits in sequence. + # Convert state into array of 0-255 integers (each byte), then write normally. # - def start_write - return if @thread - @last_output = Time.now - threaded_loop do - if ((Time.now - @last_input) > 0.001) && (@last_input > @last_output) - write_state - @last_output = Time.now - end + def write_state + puts "wrote state" + bytes = [] + @state.each_slice(8) do |slice| + bytes << slice.join("").reverse.to_i(2) end + write_bytes(bytes) end end end From 97d09061b1da31eb8865d41c7b4527b1d78c438f Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 25 Dec 2017 18:48:34 -0400 Subject: [PATCH 105/296] Use byte (8 bit uint) instead of char for auxmsg. Less casting when content is not text. --- src/lib/Dino.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Dino.h b/src/lib/Dino.h index ada36cd6..e49dbe11 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -77,7 +77,7 @@ class Dino { char cmdStr[5]; int cmd; char pinStr[5]; int pin; char valStr[5]; int val; - char auxMsg[512]; + byte auxMsg[512]; // Value and response storage. int rval; From 7b7ab86de98b4935c968e0e453481cf69f05b294 Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 25 Dec 2017 19:41:00 -0400 Subject: [PATCH 106/296] Add shift register read to Arduino sketch. Prevent listeners from mutating main loop global vars. Ruby code in next commit. --- src/lib/Dino.cpp | 122 +++++++++++++++++++++++++++++++++-------------- src/lib/Dino.h | 38 ++++++++------- 2 files changed, 106 insertions(+), 54 deletions(-) diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 663b748e..860bcd46 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -80,24 +80,34 @@ void Dino::process() { // Call the command. switch(cmd) { - case 0: setMode (); break; - case 1: dWrite (); break; - case 2: dRead (); break; - case 3: aWrite (); break; - case 4: aRead (); break; - case 5: addDigitalListener (); break; - case 6: addAnalogListener (); break; - case 7: removeListener (); break; - case 8: servoToggle (); break; - case 9: servoWrite (); break; - case 10: handleLCD (); break; - case 11: shiftWrite (); break; - case 12: handleSerial (); break; - case 13: handleDHT (); break; - case 15: ds18Read (); break; - case 16: irSend (); break; - case 20: tone (); break; - case 21: noTone (); break; + case 0: setMode (); break; + case 1: dWrite (); break; + case 2: dRead (pin); break; + case 3: aWrite (); break; + case 4: aRead (pin); break; + case 5: addDigitalListener (); break; + case 6: addAnalogListener (); break; + case 7: removeListener (); break; + case 8: servoToggle (); break; + case 9: servoWrite (); break; + case 10: handleLCD (); break; + case 12: handleSerial (); break; + case 13: handleDHT (); break; + case 15: ds18Read (); break; + case 16: irSend (); break; + case 20: tone (); break; + case 21: noTone (); break; + + // Request format for shift register read, write and add listener functions. + // pin = data pin (int) + // val = clock pin (int) + // auxMsg[0] = latch pin (byte) + // auxMsg[1] = send clock high before reading (byte) (0/1) (read func only) + // auxMsg[2] = length (byte) + // auxMsg[3]+ = data (bytes) (write func only) + case 22: shiftWrite (pin, val, auxMsg[0], auxMsg[2], &auxMsg[3]); break; + case 23: shiftRead (pin, val, auxMsg[0], auxMsg[2], auxMsg[1]); break; + case 90: reset (); break; case 96: setAnalogResolution (); break; case 97: setAnalogDivider (); break; @@ -115,7 +125,6 @@ void Dino::process() { } - // WRITE CALLBACK void Dino::setupWrite(void (*writeCallback)(char *str)) { _writeCallback = writeCallback; @@ -137,20 +146,19 @@ void Dino::updateListeners() { void Dino::updateDigitalListeners() { for (int i = 0; i < PIN_COUNT; i++) { if (digitalListeners[i]) { - pin = i; - dRead(); + dRead(i); if (rval != digitalListenerValues[i]) { digitalListenerValues[i] = rval; writeResponse(); } } } + shiftRead(14, 16, 15, 2, 1); } void Dino::updateAnalogListeners() { for (int i = 0; i < PIN_COUNT; i++) { if (analogListeners[i]) { - pin = i; - aRead(); + aRead(i); writeResponse(); } } @@ -197,7 +205,7 @@ void Dino::dWrite() { } // CMD = 02 // Digital Read -void Dino::dRead() { +void Dino::dRead(int pin) { rval = digitalRead(pin); sprintf(response, "%02d:%02d", pin, rval); } @@ -211,7 +219,7 @@ void Dino::aWrite() { } // CMD = 04 // Analog Read -void Dino::aRead() { +void Dino::aRead(int pin) { rval = analogRead(pin); sprintf(response, "%02d:%02d", pin, rval); } @@ -282,16 +290,6 @@ void Dino::handleLCD() { dinoLCD.process(val, auxMsg); } -// CMD = 11 -// Write to a shift register. -void Dino::shiftWrite() { - #ifdef debug - Serial.print("Shift write :"); Serial.print(val); Serial.print(" to pin "); Serial.print(pin); Serial.print(". Clock pin: "); Serial.println(auxMsg); - #endif - // auxMsg should be the clock pin. - shiftOut(pin, atoi(auxMsg), MSBFIRST, val); -} - // CMD = 12 // Control the SoftwareSerial. @@ -385,7 +383,7 @@ void Dino::ds18Read() { // CMD = 16 void Dino::irSend(){ - irsend.sendRaw((uint16_t*)&auxMsg[1], (uint8_t)auxMsg[0], val); + irsend.sendRaw((uint16_t)&auxMsg[1], (uint8_t)auxMsg[0], val); } // CMD = 20 @@ -399,6 +397,58 @@ void Dino::noTone() { //::noTone(pin); } + +// CMD = 22 +// Write to a shift register. +void Dino::shiftWrite(int dataPin, int clockPin, byte latchPin, byte len, byte data[]) { + // Set latch pin low to begin serial write. + digitalWrite(latchPin, LOW); + + // Write one byte at a time. + for (uint8_t i = 0; i < len; i++) { + shiftOut(dataPin, clockPin, MSBFIRST, data[i]); + } + + // Set latch pin high so register writes to parallel output. + digitalWrite(latchPin, HIGH); +} + + +// CMD = 23 +// Read from a shift register. +void Dino::shiftRead(int dataPin, int clockPin, byte latchPin, byte len, byte clockHighFirst) { + // Send clock pin high if using a register that clocks on rising edges. + // If not, the MSB will not be read on those registers (always 1), + // and all other bits will be shifted by 1 towards the LSB. + if (clockHighFirst > 0) digitalWrite(clockPin, HIGH); + + // Latch high to read parallel state, then low again to stop. + digitalWrite(latchPin, HIGH); + digitalWrite(latchPin, LOW); + + // Send the pin number and the colon alone for now. + sprintf(response, "%02d:", dataPin); + _writeCallback(response); + + for (byte i = 1; i <= len; i++) { + // Read a single byte from the register. + byte reading = shiftIn(dataPin, clockPin, LSBFIRST); + + // If we're on the last byte, append \n. If not, append a comma, then write. + if (i == len) { + sprintf(response, "%03d\n", reading); + } else { + sprintf(response, "%03d,", dataPin); + } + _writeCallback(response); + } + + // Leave latch pin high and clear response so main loop doesn't send anything. + digitalWrite(latchPin, HIGH); + response[0] = "\0"; +} + + // CMD = 90 void Dino::reset() { heartRate = 4000; // Default heartRate is ~4ms. diff --git a/src/lib/Dino.h b/src/lib/Dino.h index e49dbe11..2498d2a9 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -42,24 +42,26 @@ class Dino { private: // API-accessible functions. - void setMode (); //cmd = 0 - void dWrite (); //cmd = 1 - void dRead (); //cmd = 2 - void aWrite (); //cmd = 3 - void aRead (); //cmd = 4 - void addDigitalListener (); //cmd = 5 - void addAnalogListener (); //cmd = 6 - void removeListener (); //cmd = 7 - void servoToggle (); //cmd = 8 - void servoWrite (); //cmd = 9 - void handleLCD (); //cmd = 10 - void shiftWrite (); //cmd = 11 - void handleSerial (); //cmd = 12 - void handleDHT (); //cmd = 13 - void ds18Read (); //cmd = 15 - void irSend (); //cmd = 16 - void tone (); //cmd = 20 - void noTone (); //cmd = 21 + void setMode (); //cmd = 0 + void dWrite (); //cmd = 1 + void dRead (int pin); //cmd = 2 + void aWrite (); //cmd = 3 + void aRead (int pin); //cmd = 4 + void addDigitalListener (); //cmd = 5 + void addAnalogListener (); //cmd = 6 + void removeListener (); //cmd = 7 + void servoToggle (); //cmd = 8 + void servoWrite (); //cmd = 9 + void handleLCD (); //cmd = 10 + void shiftWrite (); //cmd = 11 + void handleSerial (); //cmd = 12 + void handleDHT (); //cmd = 13 + void ds18Read (); //cmd = 15 + void irSend (); //cmd = 16 + void tone (); //cmd = 20 + void noTone (); //cmd = 21 + void shiftWrite (int dataPin, int clockPin, byte latchPin, byte len, byte data[]); //cmd = 22 + void shiftRead (int dataPin, int clockPin, byte latchPin, byte len, byte clockHighFirst); //cmd = 23 void reset (); //cmd = 90 void setAnalogResolution (); //cmd = 96 void setAnalogDivider (); //cmd = 97 From a9a9907599f2cd37a9313f0f73271d9336b7b3aa Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 25 Dec 2017 21:02:39 -0400 Subject: [PATCH 107/296] Ruby implementation for shift register read, updated register write, and related bug fixes. --- examples/shift_register/shift_register_in.rb | 19 +++ ...hift_register.rb => shift_register_out.rb} | 2 +- examples/shift_register/shift_register_ssd.rb | 2 +- lib/dino/components.rb | 33 +++--- lib/dino/components/mixins/board_proxy.rb | 7 ++ lib/dino/components/mixins/reader.rb | 4 +- lib/dino/components/shift_register.rb | 91 -------------- lib/dino/components/shift_register_in.rb | 112 ++++++++++++++++++ lib/dino/components/shift_register_out.rb | 92 ++++++++++++++ src/lib/Dino.cpp | 5 +- 10 files changed, 253 insertions(+), 114 deletions(-) create mode 100644 examples/shift_register/shift_register_in.rb rename examples/shift_register/{shift_register.rb => shift_register_out.rb} (76%) delete mode 100644 lib/dino/components/shift_register.rb create mode 100644 lib/dino/components/shift_register_in.rb create mode 100644 lib/dino/components/shift_register_out.rb diff --git a/examples/shift_register/shift_register_in.rb b/examples/shift_register/shift_register_in.rb new file mode 100644 index 00000000..696524a1 --- /dev/null +++ b/examples/shift_register/shift_register_in.rb @@ -0,0 +1,19 @@ +# +# This is a simple example to write to a shift register. +# Writing a byte of 255 sets all the output pins to high. +# +require 'bundler/setup' +require 'dino' + +board = Dino::Board.new(Dino::TxRx::Serial.new) +shift_register = Dino::Components::ShiftRegisterIn.new(bytes: 1, clock_high_first: true, pins: {data: 14, latch: 15, clock: 16}, board: board) + +button = Dino::Components::Button.new(pin: 7, board: shift_register) + +button.down { puts "down"} +button.up { puts "up" } + +loop do + button.read + sleep 0.1 +end diff --git a/examples/shift_register/shift_register.rb b/examples/shift_register/shift_register_out.rb similarity index 76% rename from examples/shift_register/shift_register.rb rename to examples/shift_register/shift_register_out.rb index fe5945b6..5e157418 100644 --- a/examples/shift_register/shift_register.rb +++ b/examples/shift_register/shift_register_out.rb @@ -6,7 +6,7 @@ require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) -shift_register = Dino::Components::ShiftRegister.new(pins: {data: 11, latch: 8, clock: 12}, board: board) +shift_register = Dino::Components::ShiftRegisterOut.new(pins: {data: 11, latch: 8, clock: 12}, board: board) # Write a single byte shift_register.write(255) diff --git a/examples/shift_register/shift_register_ssd.rb b/examples/shift_register/shift_register_ssd.rb index 7af26409..81737214 100644 --- a/examples/shift_register/shift_register_ssd.rb +++ b/examples/shift_register/shift_register_ssd.rb @@ -7,7 +7,7 @@ require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) -shift_register = Dino::Components::ShiftRegister.new(pins: {data: 11, latch: 8, clock: 12}, board: board) +shift_register = Dino::Components::ShiftRegisterOut.new(pins: {data: 11, latch: 8, clock: 12}, board: board) ssd = Dino::Components::SSD.new( board: shift_register, diff --git a/lib/dino/components.rb b/lib/dino/components.rb index d1b5c4e3..6114a33f 100644 --- a/lib/dino/components.rb +++ b/lib/dino/components.rb @@ -3,21 +3,22 @@ module Components require 'dino/components/setup' require 'dino/components/mixins' require 'dino/components/basic' - autoload :Led, 'dino/components/led' - autoload :Button, 'dino/components/button' - autoload :Sensor, 'dino/components/sensor' - autoload :RgbLed, 'dino/components/rgb_led' - autoload :Servo, 'dino/components/servo' - autoload :SSD, 'dino/components/ssd' - autoload :Stepper, 'dino/components/stepper' - autoload :IrReceiver, 'dino/components/ir_receiver' - autoload :LCD, 'dino/components/lcd' - autoload :ShiftRegister, 'dino/components/shift_register' - autoload :Relay, 'dino/components/relay' - autoload :SoftwareSerial, 'dino/components/softserial' - autoload :DHT, 'dino/components/dht' - autoload :DS18B20, 'dino/components/ds18b20' - autoload :IREmitter, 'dino/components/ir_emitter' - autoload :Piezo, 'dino/components/piezo' + autoload :Led, 'dino/components/led' + autoload :Button, 'dino/components/button' + autoload :Sensor, 'dino/components/sensor' + autoload :RgbLed, 'dino/components/rgb_led' + autoload :Servo, 'dino/components/servo' + autoload :SSD, 'dino/components/ssd' + autoload :Stepper, 'dino/components/stepper' + autoload :IrReceiver, 'dino/components/ir_receiver' + autoload :LCD, 'dino/components/lcd' + autoload :ShiftRegisterIn, 'dino/components/shift_register_in' + autoload :ShiftRegisterOut, 'dino/components/shift_register_out' + autoload :Relay, 'dino/components/relay' + autoload :SoftwareSerial, 'dino/components/softserial' + autoload :DHT, 'dino/components/dht' + autoload :DS18B20, 'dino/components/ds18b20' + autoload :IREmitter, 'dino/components/ir_emitter' + autoload :Piezo, 'dino/components/piezo' end end diff --git a/lib/dino/components/mixins/board_proxy.rb b/lib/dino/components/mixins/board_proxy.rb index 30f7f6b8..961a4753 100644 --- a/lib/dino/components/mixins/board_proxy.rb +++ b/lib/dino/components/mixins/board_proxy.rb @@ -3,6 +3,7 @@ module Components module Mixins module BoardProxy def after_initialize(options={}) + super(options) if defined?(super) @high = 1 @low = 0 @components = [] @@ -25,6 +26,12 @@ def convert_pin(pin) def set_pin_mode(pin, mode) nil end + + def set_pullup(pin, pullup) + nil + end + + def start_read; end end end end diff --git a/lib/dino/components/mixins/reader.rb b/lib/dino/components/mixins/reader.rb index 89810135..6f3c90ff 100644 --- a/lib/dino/components/mixins/reader.rb +++ b/lib/dino/components/mixins/reader.rb @@ -7,9 +7,9 @@ module Reader def read(&block) add_callback(:read, &block) if block_given? _read - loop { break if @callbacks[:read].empty? } + loop { break if !@callbacks[:read] || @callbacks[:read].empty? } end - + # # Including component should define this to perform a single read on the board. # diff --git a/lib/dino/components/shift_register.rb b/lib/dino/components/shift_register.rb deleted file mode 100644 index 777bfdff..00000000 --- a/lib/dino/components/shift_register.rb +++ /dev/null @@ -1,91 +0,0 @@ -module Dino - module Components - class ShiftRegister - # - # options = {board: my_board, pins: {clock: clock_pin, latch: latch_pin, data: data_pin} - # - include Setup::MultiPin - proxy_pins clock: Basic::DigitalOutput, - latch: Basic::DigitalOutput, - data: Basic::DigitalOutput - - - def after_initialize(options={}) - # - # Before the register is used as a board proxy, we need to know how many - # bytes there are to map the register pins to virtual pins. - # Defaults to 1 byte. Ignore if writing to the register directly. - # - @bytes = options[:bytes] || 1 - - # - # When used as a board proxy, store the state of each register - # output pins as a 0 or 1 in an array that is (@bytes * 8) long. - # - @state = Array.new(@bytes*8) {|i| 0} - write_state - - # - # When used as a board proxy, only write sate if @write_delay seconds - # has passed since last input. Looks better for things like SSDs - # where many bits may change in sequence, but not exactly the same time. - # - @write_delay = options[:write_delay] || 0.005 - - super - end - - # - # Send a single byte per message as text, so 255 as 3 bytes, not 1. - # Use this when writing values directly to the register. - # - def write_bytes(*bytes) - - latch.low - bytes.flatten.each do |byte| - board.write Dino::Message.encode(command: 11, pin: data.pin, value: byte, aux_message: clock.pin) - end - latch.high - end - - alias :write_byte :write_bytes - alias :write :write_bytes - - # - # Make the shift register act as a board for components that just need - # digital output pins. To initialize a component, pass the register as a - # 'board' and pin numbers such that pin 0 is the 1st bit of the - # 1st regiser byte, pin 9 is the first bit of the second byte, and so on. - # - include Mixins::BoardProxy - def digital_write(pin, value) - @state[pin] = value - delayed_write(@state) - end - - # - # Do writes in a separate thread and with a small delay. - # Lets us catch multiple changed bits, like with an SSD. - # - include Mixins::Threaded - def delayed_write(state) - threaded do - sleep @write_delay - write_state if (state == @state) - end - end - - # - # Convert state into array of 0-255 integers (each byte), then write normally. - # - def write_state - puts "wrote state" - bytes = [] - @state.each_slice(8) do |slice| - bytes << slice.join("").reverse.to_i(2) - end - write_bytes(bytes) - end - end - end -end diff --git a/lib/dino/components/shift_register_in.rb b/lib/dino/components/shift_register_in.rb new file mode 100644 index 00000000..5538f0c6 --- /dev/null +++ b/lib/dino/components/shift_register_in.rb @@ -0,0 +1,112 @@ +module Dino + module Components + class ShiftRegisterIn + # + # options = {board: my_board, pins: {clock: clock_pin, latch: latch_pin, data: data_pin} + # + include Setup::MultiPin + proxy_pins clock: Basic::DigitalOutput, + latch: Basic::DigitalOutput, + data: Basic::AnalogInput + + def after_initialize(options={}) + # + # To use the register as a board proxy, we need to know how many + # bytes there are and map each bit to a virtual pin. + # Defaults to 1 byte. Ignore if writing to the register directly. + # + @bytes = options[:bytes] || 1 + + # + # When used as a board proxy, store the state of each register + # pin as a 0 or 1 in an array that is (@bytes * 8) long. Zero out to start. + # + @state = Array.new(@bytes*8) {|i| 0} + + # + # Certain registers which use rising edges for clock signals produce + # errors with the native Arduino shiftIn function unless you set the clock + # pin high before reading. Setting this instance var to 1 will include + # that instruction on every call to read or listen. + # + @clock_high_first = options[:clock_high_first] ? 1 : 0 + + super(options) + bubble_callbacks + enable_proxy + end + + include Mixins::Callbacks + # + # Data will arrive on the data pin, similar to an analog read. + # Bubble it up to the register object (self) and then deal with it there. + # + def bubble_callbacks + proxies[:data].add_callback do |byte| + self.update(byte) + end + end + + # + # Callbacks#update mostly works, but we need to convert the state from + # the format we get it in to an array of bits. + # + def update(message) + # Bytes arrive as numbers in text separated by commas. + # Convert them into a bit array matching @state before calling super. + bits = byte_array_to_bit_array(message.split(",")) + super(bits) + end + + # + # Convert an array of cached bytes into an array of integer 1s and 0s + # Convenient for bubbling to proxy components, but is used for all callbacks. + # + def byte_array_to_bit_array(byte_array) + byte_array.map do |byte| + byte.to_i.to_s(2).rjust(8, "0").split("") + end.flatten.map { |bit| bit.to_i } + end + + def read + # Pack the extra parameters we need to send in the aux message then send. + aux = [latch.pin, @clock_high_first, @bytes] + aux = aux.pack('C*') + board.write Dino::Message.encode(command: 23, pin: data.pin, value: clock.pin, aux_message: aux) + end + + # + # Make the register act as a board for components that need only digital + # input pins. Pass the register as a 'board' and pin numbers such that pin 0 + # is the 1st bit of the 1st byte, pin 9 is 1st bit of the 2nd byte, and so on. + # + include Mixins::BoardProxy + + def digital_read(pin) + read + end + + def digital_listen(pin) + # + # Start the remote listener + # Need to implement this on Arduino. + # + end + + # + # Mimic Board#update, but in a callback fired through Callbacks#update. + # This doesn't interfere with using the register directly, + # and doesn't fire if the board isn't acting as a proxy (no components). + # + def enable_proxy + self.add_callback(:board_proxy) do |bit_array| + bit_array.each_with_index do |value, pin| + @components.each do |part| + part.update(value) if pin.to_i == part.pin + end + end + end + end + end + end +end diff --git a/lib/dino/components/shift_register_out.rb b/lib/dino/components/shift_register_out.rb new file mode 100644 index 00000000..866272c4 --- /dev/null +++ b/lib/dino/components/shift_register_out.rb @@ -0,0 +1,92 @@ +module Dino + module Components + class ShiftRegisterOut + # + # options = {board: my_board, pins: {clock: clock_pin, latch: latch_pin, data: data_pin} + # + include Setup::MultiPin + proxy_pins clock: Basic::DigitalOutput, + latch: Basic::DigitalOutput, + data: Basic::DigitalOutput + + def after_initialize(options={}) + # + # To use the register as a board proxy, we need to know how many + # bytes there are and map each bit to a virtual pin. + # Defaults to 1 byte. Ignore if writing to the register directly. + # + @bytes = options[:bytes] || 1 + + # + # When used as a board proxy, store the state of each register + # pin as a 0 or 1 in an array that is (@bytes * 8) long. Zero out to start. + # + @state = Array.new(@bytes*8) {|i| 0} + write_state + + # + # When used as a board proxy, only write sate if @write_delay seconds + # have passed since this object last got input. Better for things like SSDs + # where many bits change in sequence, but not at exactly the same time. + # + @write_delay = options[:write_delay] || 0.005 + + super(options) + end + + # + # Send one or more bytes to the register. Clock and latch toggling + # are handled by the board, but we still must set mode in Ruby. + # + def write_bytes(*bytes) + aux = bytes.flatten + # + # Format request by putting 3 elements before the data bytes, so we get: + # [latch pin, (unused byte), number of data bytes, data byte 1, data byte 2...] + # + aux = [latch.pin, 0, aux.count].concat(aux) + + # Pack into literal format for aux msg compatbility then send. + aux = aux.pack('C*') + board.write Dino::Message.encode(command: 22, pin: data.pin, value: clock.pin, aux_message: aux) + end + + alias :write_byte :write_bytes + alias :write :write_bytes + + # + # Make the register act as a board for components that need only digital + # output pins. Pass the register as a 'board' and pin numbers such that pin 0 + # is the 1st bit of the 1st byte, pin 9 is 1st bit of the 2nd byte, and so on. + # + include Mixins::BoardProxy + def digital_write(pin, value) + @state[pin] = value + delayed_write(@state) + end + + # + # If acting as board, do writes in a separate thread and with small delay. + # Lets us catch multiple changed bits, like when hosting an SSD. + # + include Mixins::Threaded + def delayed_write(state) + threaded do + sleep @write_delay + write_state if (state == @state) + end + end + + # + # Convert bit state to array of 0-255 integers (bytes) then write normally. + # + def write_state + bytes = [] + @state.each_slice(8) do |slice| + bytes << slice.join("").to_i(2) + end + write_bytes(bytes) + end + end + end +end diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 860bcd46..1633ff39 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -153,7 +153,6 @@ void Dino::updateDigitalListeners() { } } } - shiftRead(14, 16, 15, 2, 1); } void Dino::updateAnalogListeners() { for (int i = 0; i < PIN_COUNT; i++) { @@ -406,7 +405,7 @@ void Dino::shiftWrite(int dataPin, int clockPin, byte latchPin, byte len, byte d // Write one byte at a time. for (uint8_t i = 0; i < len; i++) { - shiftOut(dataPin, clockPin, MSBFIRST, data[i]); + shiftOut(dataPin, clockPin, LSBFIRST, data[i]); } // Set latch pin high so register writes to parallel output. @@ -438,7 +437,7 @@ void Dino::shiftRead(int dataPin, int clockPin, byte latchPin, byte len, byte cl if (i == len) { sprintf(response, "%03d\n", reading); } else { - sprintf(response, "%03d,", dataPin); + sprintf(response, "%03d,", reading); } _writeCallback(response); } From 779c30dde7d96211bdd218fabc4fa52cd579c333 Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 25 Dec 2017 21:08:59 -0400 Subject: [PATCH 108/296] Make callback thread update component state after running all callbacks instead of before. Now you can compare the new state (callback parameter) to the old state (instance variable) inside callbacks. --- lib/dino/components/mixins/callbacks.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dino/components/mixins/callbacks.rb b/lib/dino/components/mixins/callbacks.rb index 8084ea24..8822fd3f 100644 --- a/lib/dino/components/mixins/callbacks.rb +++ b/lib/dino/components/mixins/callbacks.rb @@ -25,13 +25,13 @@ def remove_callback(key=nil) alias :remove_callbacks :remove_callback def update(data) - @state = data @callback_mutex.synchronize { @callbacks.each_value do |array| array.each { |callback| callback.call(@state) } end } remove_callback :read + @state = data end end end From 7f941fa23ee489301ea4947f6eb309591a2dfc5c Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 26 Dec 2017 16:32:48 -0400 Subject: [PATCH 109/296] Make sure callbacks get the news data, not the old state. --- lib/dino/components/mixins/callbacks.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dino/components/mixins/callbacks.rb b/lib/dino/components/mixins/callbacks.rb index 8822fd3f..a2661a65 100644 --- a/lib/dino/components/mixins/callbacks.rb +++ b/lib/dino/components/mixins/callbacks.rb @@ -27,7 +27,7 @@ def remove_callback(key=nil) def update(data) @callback_mutex.synchronize { @callbacks.each_value do |array| - array.each { |callback| callback.call(@state) } + array.each { |callback| callback.call(data) } end } remove_callback :read From 76c53da49fc88449e9bbb6ca210d940139e997ef Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 26 Dec 2017 16:37:54 -0400 Subject: [PATCH 110/296] Fix shift register output tests. --- .../lib/components/shift_register_out_spec.rb | 35 ++++++++++++++++ spec/lib/components/shift_register_spec.rb | 40 ------------------- 2 files changed, 35 insertions(+), 40 deletions(-) create mode 100644 spec/lib/components/shift_register_out_spec.rb delete mode 100644 spec/lib/components/shift_register_spec.rb diff --git a/spec/lib/components/shift_register_out_spec.rb b/spec/lib/components/shift_register_out_spec.rb new file mode 100644 index 00000000..cf25d6d3 --- /dev/null +++ b/spec/lib/components/shift_register_out_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +module Dino + module Components + describe ShiftRegisterOut do + include BoardMock + let(:options) { { board: board, pins: {clock: 12, data: 11, latch: 8} } } + subject { ShiftRegisterOut.new(options) } + + describe '#initialize' do + it 'should create a BaseOutput instance for each pin' do + expect(subject.clock.class).to eq(Basic::DigitalOutput) + expect(subject.latch.class).to eq(Basic::DigitalOutput) + expect(subject.data.class).to eq(Basic::DigitalOutput) + end + end + + describe '#write' do + before(:each) { subject } + + it 'should send message for single byte in the request format the board expects' do + expect(board).to receive(:write).with "22.11.12.#{[8,0,1,255].pack('C*')}\n" + + subject.write(255) + end + + it 'should send message for array of bytes in the request format the board expects' do + expect(board).to receive(:write).with "22.11.12.#{[8,0,2,255,0].pack('C*')}\n" + + subject.write([255,0]) + end + end + end + end +end diff --git a/spec/lib/components/shift_register_spec.rb b/spec/lib/components/shift_register_spec.rb deleted file mode 100644 index 246ae490..00000000 --- a/spec/lib/components/shift_register_spec.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - describe ShiftRegister do - include BoardMock - let(:options) { { board: board, pins: {clock: 12, data: 11, latch: 8} } } - subject { ShiftRegister.new(options) } - - describe '#initialize' do - it 'should create a BaseOutput instance for each pin' do - expect(subject.clock.class).to eq(Basic::DigitalOutput) - expect(subject.latch.class).to eq(Basic::DigitalOutput) - expect(subject.data.class).to eq(Basic::DigitalOutput) - end - end - - describe '#write' do - before(:each) { subject } - - it 'should write a single byte as value and clock pin as aux to the data pin' do - expect(subject.latch).to receive(:digital_write).with(board.low) - expect(board).to receive(:write).with "11.11.255.12\n" - expect(subject.latch).to receive(:digital_write).with(board.high) - - subject.write(255) - end - - it 'should write an array of bytes as value and clock pin as aux to the data pin' do - expect(subject.latch).to receive(:digital_write).with(board.low) - expect(board).to receive(:write).with "11.11.255.12\n" - expect(board).to receive(:write).with "11.11.0.12\n" - expect(subject.latch).to receive(:digital_write).with(board.high) - - subject.write([255,0]) - end - end - end - end -end From 70073ac3b6a0267b8b769da601a72be1a2016418 Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 26 Dec 2017 17:22:18 -0400 Subject: [PATCH 111/296] Add tests for IR emitter and shift register in. --- examples/shift_register/shift_register_in.rb | 2 +- lib/dino/components/shift_register_in.rb | 4 +- spec/lib/components/ir_emitter_spec.rb | 27 ++++++ spec/lib/components/shift_register_in_spec.rb | 82 +++++++++++++++++++ .../lib/components/shift_register_out_spec.rb | 2 +- 5 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 spec/lib/components/ir_emitter_spec.rb create mode 100644 spec/lib/components/shift_register_in_spec.rb diff --git a/examples/shift_register/shift_register_in.rb b/examples/shift_register/shift_register_in.rb index 696524a1..0b450504 100644 --- a/examples/shift_register/shift_register_in.rb +++ b/examples/shift_register/shift_register_in.rb @@ -6,7 +6,7 @@ require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) -shift_register = Dino::Components::ShiftRegisterIn.new(bytes: 1, clock_high_first: true, pins: {data: 14, latch: 15, clock: 16}, board: board) +shift_register = Dino::Components::ShiftRegisterIn.new(bytes: 1, preclock_high: true, pins: {data: 14, latch: 15, clock: 16}, board: board) button = Dino::Components::Button.new(pin: 7, board: shift_register) diff --git a/lib/dino/components/shift_register_in.rb b/lib/dino/components/shift_register_in.rb index 5538f0c6..ce314291 100644 --- a/lib/dino/components/shift_register_in.rb +++ b/lib/dino/components/shift_register_in.rb @@ -29,7 +29,7 @@ def after_initialize(options={}) # pin high before reading. Setting this instance var to 1 will include # that instruction on every call to read or listen. # - @clock_high_first = options[:clock_high_first] ? 1 : 0 + @preclock_high = options[:preclock_high] ? 1 : 0 super(options) bubble_callbacks @@ -70,7 +70,7 @@ def byte_array_to_bit_array(byte_array) def read # Pack the extra parameters we need to send in the aux message then send. - aux = [latch.pin, @clock_high_first, @bytes] + aux = [latch.pin, @preclock_high, @bytes] aux = aux.pack('C*') board.write Dino::Message.encode(command: 23, pin: data.pin, value: clock.pin, aux_message: aux) end diff --git a/spec/lib/components/ir_emitter_spec.rb b/spec/lib/components/ir_emitter_spec.rb new file mode 100644 index 00000000..f4331f41 --- /dev/null +++ b/spec/lib/components/ir_emitter_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +module Dino + module Components + describe IREmitter do + include BoardMock + let(:options) { { board: board, pin: 3 } } + subject { IREmitter.new(options) } + + describe '#send' do + before(:each) { subject } + + it 'should send messages in the request format the board expects' do + expect(board).to receive(:write).with "16.3.38.#{[4].pack('C')}#{[100,200,300,400].pack('v*')}\n" + + subject.send([100,200,300,400]) + end + + it 'should put modulation frequency (kHz) in the value field if given as option to #send' do + expect(board).to receive(:write).with "16.3.40.#{[4].pack('C')}#{[100,200,300,400].pack('v*')}\n" + + subject.send([100,200,300,400], frequency: 40) + end + end + end + end +end diff --git a/spec/lib/components/shift_register_in_spec.rb b/spec/lib/components/shift_register_in_spec.rb new file mode 100644 index 00000000..99b34365 --- /dev/null +++ b/spec/lib/components/shift_register_in_spec.rb @@ -0,0 +1,82 @@ +require 'spec_helper' + +module Dino + module Components + describe ShiftRegisterIn do + include BoardMock + let(:options) { { board: board, pins: {clock: 12, data: 11, latch: 8} } } + subject { ShiftRegisterIn.new(options) } + + describe '#initialize' do + it 'should create DigitalOutput instances for clock and latch pins' do + expect(subject.clock.class).to eq(Basic::DigitalOutput) + expect(subject.latch.class).to eq(Basic::DigitalOutput) + end + + it 'should create an AnalogInput instance for data pin' do + expect(subject.data.class).to eq(Basic::AnalogInput) + end + + it 'should set the number of bytes when given as option' do + subject = ShiftRegisterIn.new(options.merge(bytes:2)) + expect(subject.instance_variable_get(:@bytes)).to eq(2) + end + + it 'should default the preclock_high variable to 0' do + expect(subject.instance_variable_get(:@preclock_high)).to eq(0) + end + + it 'should set @preclock_high to 1 if given anything other than 0' do + subject = ShiftRegisterIn.new(options.merge(preclock_high: :yes)) + expect(subject.instance_variable_get(:@preclock_high)).to eq(1) + end + end + + describe '#read' do + before(:each) { subject } + + it 'should send message for single byte in the request format the board expects' do + expect(board).to receive(:write).with "23.11.12.#{[8,0,1].pack('C*')}\n" + subject.read + end + + it 'should request the correct number of bytes to be read' do + subject = ShiftRegisterIn.new(options.merge(bytes: 2)) + expect(board).to receive(:write).with "23.11.12.#{[8,0,2].pack('C*')}\n" + subject.read + end + + it 'should request clock pin to go high before reading if set' do + subject = ShiftRegisterIn.new(options.merge(preclock_high: 1)) + expect(board).to receive(:write).with "23.11.12.#{[8,1,1].pack('C*')}\n" + subject.read + end + end + + describe '#update' do + before(:each) { subject } + + it 'should bubble #update from the data pin up to itself' do + expect(subject).to receive(:update).once.with("127,255") + subject.data.update("127,255") + end + + it 'should update @state with data converted to array of 0/1 integers' do + subject.update("127") + expect(subject.instance_variable_get(:@state)).to eq([0,1,1,1,1,1,1,1]) + + subject = ShiftRegisterIn.new(options.merge(bytes: 2)) + subject.update("127,255") + expect(subject.instance_variable_get(:@state)).to eq([0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]) + end + + it 'should pass data to the callbacks as an array of 0/1 integers' do + @callback = Proc.new{} + subject.add_callback(&@callback) + expect(@callback).to receive(:call).once.with([0,1,1,1,1,1,1,1]) + subject.update("127") + end + end + end + end +end diff --git a/spec/lib/components/shift_register_out_spec.rb b/spec/lib/components/shift_register_out_spec.rb index cf25d6d3..68d85a51 100644 --- a/spec/lib/components/shift_register_out_spec.rb +++ b/spec/lib/components/shift_register_out_spec.rb @@ -8,7 +8,7 @@ module Components subject { ShiftRegisterOut.new(options) } describe '#initialize' do - it 'should create a BaseOutput instance for each pin' do + it 'should create a DigitalOutput instance for each pin' do expect(subject.clock.class).to eq(Basic::DigitalOutput) expect(subject.latch.class).to eq(Basic::DigitalOutput) expect(subject.data.class).to eq(Basic::DigitalOutput) From 39f085488a6899b888da6b8daabc2c4f6f4c71c9 Mon Sep 17 00:00:00 2001 From: vickash Date: Wed, 27 Dec 2017 12:12:58 -0400 Subject: [PATCH 112/296] Move register classes into module. Receive on latch pin instead of data pin. --- .../shift_in.rb} | 2 +- .../shift_out.rb} | 2 +- .../shift_ssd.rb} | 2 +- lib/dino/components.rb | 3 +- lib/dino/components/register.rb | 9 ++ lib/dino/components/register/select.rb | 25 ++++ lib/dino/components/register/shift_in.rb | 114 ++++++++++++++++++ lib/dino/components/register/shift_out.rb | 95 +++++++++++++++ lib/dino/components/shift_register_in.rb | 112 ----------------- lib/dino/components/shift_register_out.rb | 92 -------------- spec/lib/components/register/select_spec.rb | 35 ++++++ spec/lib/components/register/shift_in_spec.rb | 87 +++++++++++++ .../lib/components/register/shift_out_spec.rb | 40 ++++++ spec/lib/components/shift_register_in_spec.rb | 82 ------------- .../lib/components/shift_register_out_spec.rb | 35 ------ src/lib/Dino.cpp | 21 ++-- 16 files changed, 420 insertions(+), 336 deletions(-) rename examples/{shift_register/shift_register_in.rb => register/shift_in.rb} (72%) rename examples/{shift_register/shift_register_out.rb => register/shift_out.rb} (75%) rename examples/{shift_register/shift_register_ssd.rb => register/shift_ssd.rb} (84%) create mode 100644 lib/dino/components/register.rb create mode 100644 lib/dino/components/register/select.rb create mode 100644 lib/dino/components/register/shift_in.rb create mode 100644 lib/dino/components/register/shift_out.rb delete mode 100644 lib/dino/components/shift_register_in.rb delete mode 100644 lib/dino/components/shift_register_out.rb create mode 100644 spec/lib/components/register/select_spec.rb create mode 100644 spec/lib/components/register/shift_in_spec.rb create mode 100644 spec/lib/components/register/shift_out_spec.rb delete mode 100644 spec/lib/components/shift_register_in_spec.rb delete mode 100644 spec/lib/components/shift_register_out_spec.rb diff --git a/examples/shift_register/shift_register_in.rb b/examples/register/shift_in.rb similarity index 72% rename from examples/shift_register/shift_register_in.rb rename to examples/register/shift_in.rb index 0b450504..609f57ed 100644 --- a/examples/shift_register/shift_register_in.rb +++ b/examples/register/shift_in.rb @@ -6,7 +6,7 @@ require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) -shift_register = Dino::Components::ShiftRegisterIn.new(bytes: 1, preclock_high: true, pins: {data: 14, latch: 15, clock: 16}, board: board) +shift_register = Dino::Components::Register::ShiftIn.new(bytes: 1, preclock_high: true, pins: {data: 14, latch: 15, clock: 16}, board: board) button = Dino::Components::Button.new(pin: 7, board: shift_register) diff --git a/examples/shift_register/shift_register_out.rb b/examples/register/shift_out.rb similarity index 75% rename from examples/shift_register/shift_register_out.rb rename to examples/register/shift_out.rb index 5e157418..adb770e1 100644 --- a/examples/shift_register/shift_register_out.rb +++ b/examples/register/shift_out.rb @@ -6,7 +6,7 @@ require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) -shift_register = Dino::Components::ShiftRegisterOut.new(pins: {data: 11, latch: 8, clock: 12}, board: board) +shift_register = Dino::Components::Register::ShiftOut.new(pins: {data: 11, latch: 8, clock: 12}, board: board) # Write a single byte shift_register.write(255) diff --git a/examples/shift_register/shift_register_ssd.rb b/examples/register/shift_ssd.rb similarity index 84% rename from examples/shift_register/shift_register_ssd.rb rename to examples/register/shift_ssd.rb index 81737214..4fe31a7e 100644 --- a/examples/shift_register/shift_register_ssd.rb +++ b/examples/register/shift_ssd.rb @@ -7,7 +7,7 @@ require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) -shift_register = Dino::Components::ShiftRegisterOut.new(pins: {data: 11, latch: 8, clock: 12}, board: board) +shift_register = Dino::Components::Register::ShiftOut.new(pins: {data: 11, latch: 8, clock: 12}, board: board) ssd = Dino::Components::SSD.new( board: shift_register, diff --git a/lib/dino/components.rb b/lib/dino/components.rb index 6114a33f..eeceb214 100644 --- a/lib/dino/components.rb +++ b/lib/dino/components.rb @@ -3,6 +3,7 @@ module Components require 'dino/components/setup' require 'dino/components/mixins' require 'dino/components/basic' + require 'dino/components/register' autoload :Led, 'dino/components/led' autoload :Button, 'dino/components/button' autoload :Sensor, 'dino/components/sensor' @@ -12,8 +13,6 @@ module Components autoload :Stepper, 'dino/components/stepper' autoload :IrReceiver, 'dino/components/ir_receiver' autoload :LCD, 'dino/components/lcd' - autoload :ShiftRegisterIn, 'dino/components/shift_register_in' - autoload :ShiftRegisterOut, 'dino/components/shift_register_out' autoload :Relay, 'dino/components/relay' autoload :SoftwareSerial, 'dino/components/softserial' autoload :DHT, 'dino/components/dht' diff --git a/lib/dino/components/register.rb b/lib/dino/components/register.rb new file mode 100644 index 00000000..7bfd9df6 --- /dev/null +++ b/lib/dino/components/register.rb @@ -0,0 +1,9 @@ +module Dino + module Components + module Register + require 'dino/components/register/select' + require 'dino/components/register/shift_in' + require 'dino/components/register/shift_out' + end + end +end diff --git a/lib/dino/components/register/select.rb b/lib/dino/components/register/select.rb new file mode 100644 index 00000000..855f6814 --- /dev/null +++ b/lib/dino/components/register/select.rb @@ -0,0 +1,25 @@ +module Dino + module Components + module Register + class Select + # + # Register select is an active-low output pin, used to choose which + # register on a bus we're accessing. For input registers, the board + # sends readings prefixed with the register select pin number, since + # each register uses a unique select pin, while clock and data are shared. + # + # There is no need to write this pin directly, but it must be in output + # mode, and must follow the callback pattern to receive updates. + # + include Setup::SinglePin + include Setup::Output + include Mixins::Callbacks + + def initialize_pins(options={}) + super(options) if defined?(super) + board.start_read + end + end + end + end +end diff --git a/lib/dino/components/register/shift_in.rb b/lib/dino/components/register/shift_in.rb new file mode 100644 index 00000000..9c1e2d2f --- /dev/null +++ b/lib/dino/components/register/shift_in.rb @@ -0,0 +1,114 @@ +module Dino + module Components + module Register + class ShiftIn + # + # options = {board: my_board, pins: {clock: clock_pin, latch: latch_pin, data: data_pin} + # + include Setup::MultiPin + proxy_pins clock: Basic::DigitalOutput, + data: Basic::DigitalInput, + latch: Register::Select + + def after_initialize(options={}) + # + # To use the register as a board proxy, we need to know how many + # bytes there are and map each bit to a virtual pin. + # Defaults to 1 byte. Ignore if writing to the register directly. + # + @bytes = options[:bytes] || 1 + + # + # When used as a board proxy, store the state of each register + # pin as a 0 or 1 in an array that is (@bytes * 8) long. Zero out to start. + # + @state = Array.new(@bytes*8) {|i| 0} + + # + # Certain registers which use rising edges for clock signals produce + # errors with the native Arduino shiftIn function unless you set the clock + # pin high before reading. Setting this instance var to 1 will include + # that instruction on every call to read or listen. + # + @preclock_high = options[:preclock_high] ? 1 : 0 + + super(options) + bubble_callbacks + enable_proxy + end + + include Mixins::Callbacks + # + # Data will arrive on the data pin, similar to an analog read. + # Bubble it up to the register object (self) and then deal with it there. + # + def bubble_callbacks + proxies[:latch].add_callback do |byte| + self.update(byte) + end + end + + # + # Callbacks#update mostly works, but we need to convert the state from + # the format we get it in to an array of bits. + # + def update(message) + # Bytes arrive as numbers in text separated by commas. + # Convert them into a bit array matching @state before calling super. + bits = byte_array_to_bit_array(message.split(",")) + super(bits) + end + + # + # Convert an array of cached bytes into an array of integer 1s and 0s + # Convenient for bubbling to proxy components, but is used for all callbacks. + # + def byte_array_to_bit_array(byte_array) + byte_array.map do |byte| + byte.to_i.to_s(2).rjust(8, "0").split("") + end.flatten.map { |bit| bit.to_i } + end + + def read + # Pack the extra parameters we need to send in the aux message then send. + aux = [data.pin, clock.pin, @preclock_high] + aux = aux.pack('C*') + board.write Dino::Message.encode(command: 23, pin: latch.pin, value: @bytes, aux_message: aux) + end + + # + # Make the register act as a board for components that need only digital + # input pins. Pass the register as a 'board' and pin numbers such that pin 0 + # is the 1st bit of the 1st byte, pin 9 is 1st bit of the 2nd byte, and so on. + # + include Mixins::BoardProxy + + def digital_read(pin) + read + end + + def digital_listen(pin) + # + # Start the remote listener + # Need to implement this on Arduino. + # + end + + # + # Mimic Board#update, but in a callback fired through Callbacks#update. + # This doesn't interfere with using the register directly, + # and doesn't fire if the board isn't acting as a proxy (no components). + # + def enable_proxy + self.add_callback(:board_proxy) do |bit_array| + bit_array.each_with_index do |value, pin| + @components.each do |part| + part.update(value) if pin.to_i == part.pin + end + end + end + end + end + end + end +end diff --git a/lib/dino/components/register/shift_out.rb b/lib/dino/components/register/shift_out.rb new file mode 100644 index 00000000..c4180c9e --- /dev/null +++ b/lib/dino/components/register/shift_out.rb @@ -0,0 +1,95 @@ +module Dino + module Components + module Register + class ShiftOut + # + # options = {board: my_board, pins: {clock: clock_pin, latch: latch_pin, data: data_pin} + # + include Setup::MultiPin + proxy_pins clock: Basic::DigitalOutput, + latch: Register::Select, + data: Basic::DigitalOutput + + def after_initialize(options={}) + # + # To use the register as a board proxy, we need to know how many + # bytes there are and map each bit to a virtual pin. + # Defaults to 1 byte. Ignore if writing to the register directly. + # + @bytes = options[:bytes] || 1 + + # + # When used as a board proxy, store the state of each register + # pin as a 0 or 1 in an array that is (@bytes * 8) long. Zero out to start. + # + @state = Array.new(@bytes*8) {|i| 0} + write_state + + # + # When used as a board proxy, only write sate if @write_delay seconds + # have passed since this object last got input. Better for things like SSDs + # where many bits change in sequence, but not at exactly the same time. + # + @write_delay = options[:write_delay] || 0.005 + + super(options) + end + + # + # Send one or more bytes to the register. Clock and latch toggling + # are handled by the board, but we still must set mode in Ruby. + # + def write_bytes(*bytes) + aux = bytes.flatten + length = aux.count + # + # Format request by putting 3 elements before the data bytes, so we get: + # [data pin, clock pin, unused byte, data byte 1, data byte 2...] + # + aux = [data.pin, clock.pin, 0].concat(aux) + + # Pack into literal format for aux msg compatbility then send. + aux = aux.pack('C*') + board.write Dino::Message.encode(command: 22, pin: latch.pin, value: length, aux_message: aux) + end + + alias :write_byte :write_bytes + alias :write :write_bytes + + # + # Make the register act as a board for components that need only digital + # output pins. Pass the register as a 'board' and pin numbers such that pin 0 + # is the 1st bit of the 1st byte, pin 9 is 1st bit of the 2nd byte, and so on. + # + include Mixins::BoardProxy + def digital_write(pin, value) + @state[pin] = value + delayed_write(@state) + end + + # + # If acting as board, do writes in a separate thread and with small delay. + # Lets us catch multiple changed bits, like when hosting an SSD. + # + include Mixins::Threaded + def delayed_write(state) + threaded do + sleep @write_delay + write_state if (state == @state) + end + end + + # + # Convert bit state to array of 0-255 integers (bytes) then write normally. + # + def write_state + bytes = [] + @state.each_slice(8) do |slice| + bytes << slice.join("").to_i(2) + end + write_bytes(bytes) + end + end + end + end +end diff --git a/lib/dino/components/shift_register_in.rb b/lib/dino/components/shift_register_in.rb deleted file mode 100644 index ce314291..00000000 --- a/lib/dino/components/shift_register_in.rb +++ /dev/null @@ -1,112 +0,0 @@ -module Dino - module Components - class ShiftRegisterIn - # - # options = {board: my_board, pins: {clock: clock_pin, latch: latch_pin, data: data_pin} - # - include Setup::MultiPin - proxy_pins clock: Basic::DigitalOutput, - latch: Basic::DigitalOutput, - data: Basic::AnalogInput - - def after_initialize(options={}) - # - # To use the register as a board proxy, we need to know how many - # bytes there are and map each bit to a virtual pin. - # Defaults to 1 byte. Ignore if writing to the register directly. - # - @bytes = options[:bytes] || 1 - - # - # When used as a board proxy, store the state of each register - # pin as a 0 or 1 in an array that is (@bytes * 8) long. Zero out to start. - # - @state = Array.new(@bytes*8) {|i| 0} - - # - # Certain registers which use rising edges for clock signals produce - # errors with the native Arduino shiftIn function unless you set the clock - # pin high before reading. Setting this instance var to 1 will include - # that instruction on every call to read or listen. - # - @preclock_high = options[:preclock_high] ? 1 : 0 - - super(options) - bubble_callbacks - enable_proxy - end - - include Mixins::Callbacks - # - # Data will arrive on the data pin, similar to an analog read. - # Bubble it up to the register object (self) and then deal with it there. - # - def bubble_callbacks - proxies[:data].add_callback do |byte| - self.update(byte) - end - end - - # - # Callbacks#update mostly works, but we need to convert the state from - # the format we get it in to an array of bits. - # - def update(message) - # Bytes arrive as numbers in text separated by commas. - # Convert them into a bit array matching @state before calling super. - bits = byte_array_to_bit_array(message.split(",")) - super(bits) - end - - # - # Convert an array of cached bytes into an array of integer 1s and 0s - # Convenient for bubbling to proxy components, but is used for all callbacks. - # - def byte_array_to_bit_array(byte_array) - byte_array.map do |byte| - byte.to_i.to_s(2).rjust(8, "0").split("") - end.flatten.map { |bit| bit.to_i } - end - - def read - # Pack the extra parameters we need to send in the aux message then send. - aux = [latch.pin, @preclock_high, @bytes] - aux = aux.pack('C*') - board.write Dino::Message.encode(command: 23, pin: data.pin, value: clock.pin, aux_message: aux) - end - - # - # Make the register act as a board for components that need only digital - # input pins. Pass the register as a 'board' and pin numbers such that pin 0 - # is the 1st bit of the 1st byte, pin 9 is 1st bit of the 2nd byte, and so on. - # - include Mixins::BoardProxy - - def digital_read(pin) - read - end - - def digital_listen(pin) - # - # Start the remote listener - # Need to implement this on Arduino. - # - end - - # - # Mimic Board#update, but in a callback fired through Callbacks#update. - # This doesn't interfere with using the register directly, - # and doesn't fire if the board isn't acting as a proxy (no components). - # - def enable_proxy - self.add_callback(:board_proxy) do |bit_array| - bit_array.each_with_index do |value, pin| - @components.each do |part| - part.update(value) if pin.to_i == part.pin - end - end - end - end - end - end -end diff --git a/lib/dino/components/shift_register_out.rb b/lib/dino/components/shift_register_out.rb deleted file mode 100644 index 866272c4..00000000 --- a/lib/dino/components/shift_register_out.rb +++ /dev/null @@ -1,92 +0,0 @@ -module Dino - module Components - class ShiftRegisterOut - # - # options = {board: my_board, pins: {clock: clock_pin, latch: latch_pin, data: data_pin} - # - include Setup::MultiPin - proxy_pins clock: Basic::DigitalOutput, - latch: Basic::DigitalOutput, - data: Basic::DigitalOutput - - def after_initialize(options={}) - # - # To use the register as a board proxy, we need to know how many - # bytes there are and map each bit to a virtual pin. - # Defaults to 1 byte. Ignore if writing to the register directly. - # - @bytes = options[:bytes] || 1 - - # - # When used as a board proxy, store the state of each register - # pin as a 0 or 1 in an array that is (@bytes * 8) long. Zero out to start. - # - @state = Array.new(@bytes*8) {|i| 0} - write_state - - # - # When used as a board proxy, only write sate if @write_delay seconds - # have passed since this object last got input. Better for things like SSDs - # where many bits change in sequence, but not at exactly the same time. - # - @write_delay = options[:write_delay] || 0.005 - - super(options) - end - - # - # Send one or more bytes to the register. Clock and latch toggling - # are handled by the board, but we still must set mode in Ruby. - # - def write_bytes(*bytes) - aux = bytes.flatten - # - # Format request by putting 3 elements before the data bytes, so we get: - # [latch pin, (unused byte), number of data bytes, data byte 1, data byte 2...] - # - aux = [latch.pin, 0, aux.count].concat(aux) - - # Pack into literal format for aux msg compatbility then send. - aux = aux.pack('C*') - board.write Dino::Message.encode(command: 22, pin: data.pin, value: clock.pin, aux_message: aux) - end - - alias :write_byte :write_bytes - alias :write :write_bytes - - # - # Make the register act as a board for components that need only digital - # output pins. Pass the register as a 'board' and pin numbers such that pin 0 - # is the 1st bit of the 1st byte, pin 9 is 1st bit of the 2nd byte, and so on. - # - include Mixins::BoardProxy - def digital_write(pin, value) - @state[pin] = value - delayed_write(@state) - end - - # - # If acting as board, do writes in a separate thread and with small delay. - # Lets us catch multiple changed bits, like when hosting an SSD. - # - include Mixins::Threaded - def delayed_write(state) - threaded do - sleep @write_delay - write_state if (state == @state) - end - end - - # - # Convert bit state to array of 0-255 integers (bytes) then write normally. - # - def write_state - bytes = [] - @state.each_slice(8) do |slice| - bytes << slice.join("").to_i(2) - end - write_bytes(bytes) - end - end - end -end diff --git a/spec/lib/components/register/select_spec.rb b/spec/lib/components/register/select_spec.rb new file mode 100644 index 00000000..b644780f --- /dev/null +++ b/spec/lib/components/register/select_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +module Dino + module Components + module Register + describe Select do + include BoardMock + let(:options) { { pin: '10', board: board } } + subject { Select.new(options) } + + describe '#initialize' do + it 'should set mode to output' do + expect(board).to receive(:set_pin_mode).with(10, :out) + subject + end + + it 'should start the board reading' do + expect(board).to receive(:start_read) + subject + end + end + + describe '#update' do + it 'should respond to callbacks' do + subject + @callback = Proc.new{} + subject.add_callback(&@callback) + expect(@callback).to receive(:call).once.with("127") + subject.update("127") + end + end + end + end + end +end diff --git a/spec/lib/components/register/shift_in_spec.rb b/spec/lib/components/register/shift_in_spec.rb new file mode 100644 index 00000000..192c0b4d --- /dev/null +++ b/spec/lib/components/register/shift_in_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' + +module Dino + module Components + module Register + describe ShiftIn do + include BoardMock + let(:options) { { board: board, pins: {clock: 12, data: 11, latch: 8} } } + subject { ShiftIn.new(options) } + + describe '#initialize' do + it 'should create a DigitalOutput instance for clock pin' do + expect(subject.clock.class).to eq(Basic::DigitalOutput) + end + + it 'should create a DigitalInput instance for data pin' do + expect(subject.data.class).to eq(Basic::DigitalInput) + end + + it 'should create a Register::Select instance for latch pin' do + expect(subject.latch.class).to eq(Register::Select) + end + + it 'should set the number of bytes when given as option' do + subject = ShiftIn.new(options.merge(bytes:2)) + expect(subject.instance_variable_get(:@bytes)).to eq(2) + end + + it 'should default the preclock_high variable to 0' do + expect(subject.instance_variable_get(:@preclock_high)).to eq(0) + end + + it 'should set @preclock_high to 1 if given anything other than 0' do + subject = ShiftIn.new(options.merge(preclock_high: :yes)) + expect(subject.instance_variable_get(:@preclock_high)).to eq(1) + end + end + + describe '#read' do + before(:each) { subject } + + it 'should send message for single byte in the request format the board expects' do + expect(board).to receive(:write).with "23.8.1.#{[11,12,0].pack('C*')}\n" + subject.read + end + + it 'should request the correct number of bytes to be read' do + subject = ShiftIn.new(options.merge(bytes: 2)) + expect(board).to receive(:write).with "23.8.2.#{[11,12,0].pack('C*')}\n" + subject.read + end + + it 'should request clock pin to go high before reading if set' do + subject = ShiftIn.new(options.merge(preclock_high: 1)) + expect(board).to receive(:write).with "23.8.1.#{[11,12,1].pack('C*')}\n" + subject.read + end + end + + describe '#update' do + before(:each) { subject } + + it 'should bubble #update from the latch pin up to itself' do + expect(subject).to receive(:update).once.with("127,255") + subject.latch.update("127,255") + end + + it 'should update @state with data converted to array of 0/1 integers' do + subject.update("127") + expect(subject.instance_variable_get(:@state)).to eq([0,1,1,1,1,1,1,1]) + + subject = ShiftIn.new(options.merge(bytes: 2)) + subject.update("127,255") + expect(subject.instance_variable_get(:@state)).to eq([0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]) + end + + it 'should pass data to the callbacks as an array of 0/1 integers' do + @callback = Proc.new{} + subject.add_callback(&@callback) + expect(@callback).to receive(:call).once.with([0,1,1,1,1,1,1,1]) + subject.update("127") + end + end + end + end + end +end diff --git a/spec/lib/components/register/shift_out_spec.rb b/spec/lib/components/register/shift_out_spec.rb new file mode 100644 index 00000000..bb7b5e37 --- /dev/null +++ b/spec/lib/components/register/shift_out_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +module Dino + module Components + module Register + describe ShiftOut do + include BoardMock + let(:options) { { board: board, pins: {clock: 12, data: 11, latch: 8} } } + subject { ShiftOut.new(options) } + + describe '#initialize' do + it 'should create a DigitalOutput instance for clock and data pins' do + expect(subject.clock.class).to eq(Basic::DigitalOutput) + expect(subject.data.class).to eq(Basic::DigitalOutput) + end + + it 'should create a Register::Select instance for latch pin' do + expect(subject.latch.class).to eq(Register::Select) + end + end + + describe '#write' do + before(:each) { subject } + + it 'should send message for single byte in the request format the board expects' do + expect(board).to receive(:write).with "22.8.1.#{[11,12,0,255].pack('C*')}\n" + + subject.write(255) + end + + it 'should send message for array of bytes in the request format the board expects' do + expect(board).to receive(:write).with "22.8.2.#{[11,12,0,255,0].pack('C*')}\n" + + subject.write([255,0]) + end + end + end + end + end +end diff --git a/spec/lib/components/shift_register_in_spec.rb b/spec/lib/components/shift_register_in_spec.rb deleted file mode 100644 index 99b34365..00000000 --- a/spec/lib/components/shift_register_in_spec.rb +++ /dev/null @@ -1,82 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - describe ShiftRegisterIn do - include BoardMock - let(:options) { { board: board, pins: {clock: 12, data: 11, latch: 8} } } - subject { ShiftRegisterIn.new(options) } - - describe '#initialize' do - it 'should create DigitalOutput instances for clock and latch pins' do - expect(subject.clock.class).to eq(Basic::DigitalOutput) - expect(subject.latch.class).to eq(Basic::DigitalOutput) - end - - it 'should create an AnalogInput instance for data pin' do - expect(subject.data.class).to eq(Basic::AnalogInput) - end - - it 'should set the number of bytes when given as option' do - subject = ShiftRegisterIn.new(options.merge(bytes:2)) - expect(subject.instance_variable_get(:@bytes)).to eq(2) - end - - it 'should default the preclock_high variable to 0' do - expect(subject.instance_variable_get(:@preclock_high)).to eq(0) - end - - it 'should set @preclock_high to 1 if given anything other than 0' do - subject = ShiftRegisterIn.new(options.merge(preclock_high: :yes)) - expect(subject.instance_variable_get(:@preclock_high)).to eq(1) - end - end - - describe '#read' do - before(:each) { subject } - - it 'should send message for single byte in the request format the board expects' do - expect(board).to receive(:write).with "23.11.12.#{[8,0,1].pack('C*')}\n" - subject.read - end - - it 'should request the correct number of bytes to be read' do - subject = ShiftRegisterIn.new(options.merge(bytes: 2)) - expect(board).to receive(:write).with "23.11.12.#{[8,0,2].pack('C*')}\n" - subject.read - end - - it 'should request clock pin to go high before reading if set' do - subject = ShiftRegisterIn.new(options.merge(preclock_high: 1)) - expect(board).to receive(:write).with "23.11.12.#{[8,1,1].pack('C*')}\n" - subject.read - end - end - - describe '#update' do - before(:each) { subject } - - it 'should bubble #update from the data pin up to itself' do - expect(subject).to receive(:update).once.with("127,255") - subject.data.update("127,255") - end - - it 'should update @state with data converted to array of 0/1 integers' do - subject.update("127") - expect(subject.instance_variable_get(:@state)).to eq([0,1,1,1,1,1,1,1]) - - subject = ShiftRegisterIn.new(options.merge(bytes: 2)) - subject.update("127,255") - expect(subject.instance_variable_get(:@state)).to eq([0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]) - end - - it 'should pass data to the callbacks as an array of 0/1 integers' do - @callback = Proc.new{} - subject.add_callback(&@callback) - expect(@callback).to receive(:call).once.with([0,1,1,1,1,1,1,1]) - subject.update("127") - end - end - end - end -end diff --git a/spec/lib/components/shift_register_out_spec.rb b/spec/lib/components/shift_register_out_spec.rb deleted file mode 100644 index 68d85a51..00000000 --- a/spec/lib/components/shift_register_out_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - describe ShiftRegisterOut do - include BoardMock - let(:options) { { board: board, pins: {clock: 12, data: 11, latch: 8} } } - subject { ShiftRegisterOut.new(options) } - - describe '#initialize' do - it 'should create a DigitalOutput instance for each pin' do - expect(subject.clock.class).to eq(Basic::DigitalOutput) - expect(subject.latch.class).to eq(Basic::DigitalOutput) - expect(subject.data.class).to eq(Basic::DigitalOutput) - end - end - - describe '#write' do - before(:each) { subject } - - it 'should send message for single byte in the request format the board expects' do - expect(board).to receive(:write).with "22.11.12.#{[8,0,1,255].pack('C*')}\n" - - subject.write(255) - end - - it 'should send message for array of bytes in the request format the board expects' do - expect(board).to receive(:write).with "22.11.12.#{[8,0,2,255,0].pack('C*')}\n" - - subject.write([255,0]) - end - end - end - end -end diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 1633ff39..bf6bd6ba 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -99,14 +99,14 @@ void Dino::process() { case 21: noTone (); break; // Request format for shift register read, write and add listener functions. - // pin = data pin (int) - // val = clock pin (int) - // auxMsg[0] = latch pin (byte) - // auxMsg[1] = send clock high before reading (byte) (0/1) (read func only) - // auxMsg[2] = length (byte) + // pin = latch pin (int) + // val = length (int) + // auxMsg[0] = data pin (byte) + // auxMsg[1] = clock pin (byte) + // auxMsg[2] = send clock high before reading (byte) (0/1) (read func only) // auxMsg[3]+ = data (bytes) (write func only) - case 22: shiftWrite (pin, val, auxMsg[0], auxMsg[2], &auxMsg[3]); break; - case 23: shiftRead (pin, val, auxMsg[0], auxMsg[2], auxMsg[1]); break; + case 22: shiftWrite (pin, val, auxMsg[0], auxMsg[1], &auxMsg[3]); break; + case 23: shiftRead (pin, val, auxMsg[0], auxMsg[1], auxMsg[2]); break; case 90: reset (); break; case 96: setAnalogResolution (); break; @@ -399,7 +399,7 @@ void Dino::noTone() { // CMD = 22 // Write to a shift register. -void Dino::shiftWrite(int dataPin, int clockPin, byte latchPin, byte len, byte data[]) { +void Dino::shiftWrite(int latchPin, int len, byte dataPin, byte clockPin, byte data[]) { // Set latch pin low to begin serial write. digitalWrite(latchPin, LOW); @@ -415,7 +415,7 @@ void Dino::shiftWrite(int dataPin, int clockPin, byte latchPin, byte len, byte d // CMD = 23 // Read from a shift register. -void Dino::shiftRead(int dataPin, int clockPin, byte latchPin, byte len, byte clockHighFirst) { +void Dino::shiftRead(int latchPin, int len, byte dataPin, byte clockPin, byte clockHighFirst) { // Send clock pin high if using a register that clocks on rising edges. // If not, the MSB will not be read on those registers (always 1), // and all other bits will be shifted by 1 towards the LSB. @@ -426,7 +426,8 @@ void Dino::shiftRead(int dataPin, int clockPin, byte latchPin, byte len, byte cl digitalWrite(latchPin, LOW); // Send the pin number and the colon alone for now. - sprintf(response, "%02d:", dataPin); + // Send data as if coming from the latch pin. + sprintf(response, "%02d:", latchPin); _writeCallback(response); for (byte i = 1; i <= len; i++) { From 8ecdd97cbde0304ba6ded77bab1502aa5be7c97d Mon Sep 17 00:00:00 2001 From: vickash Date: Wed, 27 Dec 2017 12:53:55 -0400 Subject: [PATCH 113/296] Extract register communication into child classes. --- lib/dino/components/register.rb | 2 + lib/dino/components/register/input.rb | 96 +++++++++++++++++++++++ lib/dino/components/register/output.rb | 71 +++++++++++++++++ lib/dino/components/register/shift_in.rb | 91 ++------------------- lib/dino/components/register/shift_out.rb | 78 ++---------------- 5 files changed, 182 insertions(+), 156 deletions(-) create mode 100644 lib/dino/components/register/input.rb create mode 100644 lib/dino/components/register/output.rb diff --git a/lib/dino/components/register.rb b/lib/dino/components/register.rb index 7bfd9df6..11bddd06 100644 --- a/lib/dino/components/register.rb +++ b/lib/dino/components/register.rb @@ -2,6 +2,8 @@ module Dino module Components module Register require 'dino/components/register/select' + require 'dino/components/register/input' + require 'dino/components/register/output' require 'dino/components/register/shift_in' require 'dino/components/register/shift_out' end diff --git a/lib/dino/components/register/input.rb b/lib/dino/components/register/input.rb new file mode 100644 index 00000000..1489ae24 --- /dev/null +++ b/lib/dino/components/register/input.rb @@ -0,0 +1,96 @@ +module Dino + module Components + module Register + class Input + include Setup::Base + include Mixins::Callbacks + + def after_initialize(options={}) + super(options) if defined?(super) + # + # To use the register as a board proxy, we need to know how many + # bytes there are and map each bit to a virtual pin. + # Defaults to 1 byte. Ignore if writing to the register directly. + # + @bytes = options[:bytes] || 1 + + # + # When used as a board proxy, store the state of each register + # pin as a 0 or 1 in an array that is (@bytes * 8) long. Zero out to start. + # + @state = Array.new(@bytes*8) {|i| 0} + + # + # Certain registers which use rising edges for clock signals produce + # errors with the native Arduino shiftIn function unless you set the clock + # pin high before reading. Setting this instance var to 1 will include + # that instruction on every call to read or listen. + # + @preclock_high = options[:preclock_high] ? 1 : 0 + + enable_proxy + end + + # + # Callbacks#update mostly works, but we need to convert the state from + # comma delimited numbers into an array of bits first. + # + def update(message) + bits = byte_array_to_bit_array(message.split(",")) + super(bits) + end + + # + # Convert an array of cached bytes into an array of integer 1s and 0s + # Convenient for bubbling to proxy components, but is used for all callbacks. + # + def byte_array_to_bit_array(byte_array) + byte_array.map do |byte| + byte.to_i.to_s(2).rjust(8, "0").split("") + end.flatten.map { |bit| bit.to_i } + end + + def read + raise 'define #read in child class depending on communication method' + end + + def listen + raise 'define #listen in child class depending on communication method' + end + + # + # Make the register act as a board for components that need only digital + # input pins. Pass the register as a 'board' and pin numbers such that pin 0 + # is the 1st bit of the 1st byte, pin 9 is 1st bit of the 2nd byte, and so on. + # + include Mixins::BoardProxy + + def digital_read(pin) + read + end + + def digital_listen(pin) + # + # Start the remote listener + # Need to implement this on Arduino. + # + end + + # + # Mimic Board#update, but in a callback fired through Callbacks#update. + # This doesn't interfere with using the register directly, + # and doesn't fire if the board isn't acting as a proxy (no components). + # + def enable_proxy + self.add_callback(:board_proxy) do |bit_array| + bit_array.each_with_index do |value, pin| + @components.each do |part| + part.update(value) if pin.to_i == part.pin + end + end + end + end + end + end + end +end diff --git a/lib/dino/components/register/output.rb b/lib/dino/components/register/output.rb new file mode 100644 index 00000000..f30e61b7 --- /dev/null +++ b/lib/dino/components/register/output.rb @@ -0,0 +1,71 @@ +module Dino + module Components + module Register + class Output + include Setup::Base + + def after_initialize(options={}) + super(options) if defined?(super) + # + # To use the register as a board proxy, we need to know how many + # bytes there are and map each bit to a virtual pin. + # Defaults to 1 byte. Ignore if writing to the register directly. + # + @bytes = options[:bytes] || 1 + + # + # When used as a board proxy, store the state of each register + # pin as a 0 or 1 in an array that is (@bytes * 8) long. Zero out to start. + # + @state = Array.new(@bytes*8) {|i| 0} + write_state + + # + # When used as a board proxy, only write sate if @write_delay seconds + # have passed since this object last got input. Better for things like SSDs + # where many bits change in sequence, but not at exactly the same time. + # + @write_delay = options[:write_delay] || 0.005 + end + + def write + raise 'define #write in child class based on communication method' + end + + # + # Make the register act as a board for components that need only digital + # output pins. Pass the register as a 'board' and pin numbers such that pin 0 + # is the 1st bit of the 1st byte, pin 9 is 1st bit of the 2nd byte, and so on. + # + include Mixins::BoardProxy + def digital_write(pin, value) + @state[pin] = value + delayed_write(@state) + end + + # + # If acting as board, do writes in a separate thread and with small delay. + # Lets us catch multiple changed bits, like when hosting an SSD. + # + include Mixins::Threaded + def delayed_write(state) + threaded do + sleep @write_delay + write_state if (state == @state) + end + end + + # + # Convert bit state to array of 0-255 integers (bytes) then write normally. + # + def write_state + bytes = [] + @state.each_slice(8) do |slice| + bytes << slice.join("").to_i(2) + end + write(bytes) + end + end + end + end +end diff --git a/lib/dino/components/register/shift_in.rb b/lib/dino/components/register/shift_in.rb index 9c1e2d2f..65f0e904 100644 --- a/lib/dino/components/register/shift_in.rb +++ b/lib/dino/components/register/shift_in.rb @@ -1,7 +1,7 @@ module Dino module Components module Register - class ShiftIn + class ShiftIn < Input # # options = {board: my_board, pins: {clock: clock_pin, latch: latch_pin, data: data_pin} # @@ -10,38 +10,15 @@ class ShiftIn data: Basic::DigitalInput, latch: Register::Select + # + # Data will arrive on the latch pin, similar to an analog read. + # Bubble it up to the register object (self), then deal with it there. + # def after_initialize(options={}) - # - # To use the register as a board proxy, we need to know how many - # bytes there are and map each bit to a virtual pin. - # Defaults to 1 byte. Ignore if writing to the register directly. - # - @bytes = options[:bytes] || 1 - - # - # When used as a board proxy, store the state of each register - # pin as a 0 or 1 in an array that is (@bytes * 8) long. Zero out to start. - # - @state = Array.new(@bytes*8) {|i| 0} - - # - # Certain registers which use rising edges for clock signals produce - # errors with the native Arduino shiftIn function unless you set the clock - # pin high before reading. Setting this instance var to 1 will include - # that instruction on every call to read or listen. - # - @preclock_high = options[:preclock_high] ? 1 : 0 - - super(options) + super(options) if defined?(super) bubble_callbacks - enable_proxy end - include Mixins::Callbacks - # - # Data will arrive on the data pin, similar to an analog read. - # Bubble it up to the register object (self) and then deal with it there. - # def bubble_callbacks proxies[:latch].add_callback do |byte| self.update(byte) @@ -49,65 +26,13 @@ def bubble_callbacks end # - # Callbacks#update mostly works, but we need to convert the state from - # the format we get it in to an array of bits. + # Read using the native shiftIn function of the Arduino library. # - def update(message) - # Bytes arrive as numbers in text separated by commas. - # Convert them into a bit array matching @state before calling super. - bits = byte_array_to_bit_array(message.split(",")) - super(bits) - end - - # - # Convert an array of cached bytes into an array of integer 1s and 0s - # Convenient for bubbling to proxy components, but is used for all callbacks. - # - def byte_array_to_bit_array(byte_array) - byte_array.map do |byte| - byte.to_i.to_s(2).rjust(8, "0").split("") - end.flatten.map { |bit| bit.to_i } - end - def read # Pack the extra parameters we need to send in the aux message then send. - aux = [data.pin, clock.pin, @preclock_high] - aux = aux.pack('C*') + aux = [data.pin, clock.pin, @preclock_high].pack('C*') board.write Dino::Message.encode(command: 23, pin: latch.pin, value: @bytes, aux_message: aux) end - - # - # Make the register act as a board for components that need only digital - # input pins. Pass the register as a 'board' and pin numbers such that pin 0 - # is the 1st bit of the 1st byte, pin 9 is 1st bit of the 2nd byte, and so on. - # - include Mixins::BoardProxy - - def digital_read(pin) - read - end - - def digital_listen(pin) - # - # Start the remote listener - # Need to implement this on Arduino. - # - end - - # - # Mimic Board#update, but in a callback fired through Callbacks#update. - # This doesn't interfere with using the register directly, - # and doesn't fire if the board isn't acting as a proxy (no components). - # - def enable_proxy - self.add_callback(:board_proxy) do |bit_array| - bit_array.each_with_index do |value, pin| - @components.each do |part| - part.update(value) if pin.to_i == part.pin - end - end - end - end end end end diff --git a/lib/dino/components/register/shift_out.rb b/lib/dino/components/register/shift_out.rb index c4180c9e..708033d1 100644 --- a/lib/dino/components/register/shift_out.rb +++ b/lib/dino/components/register/shift_out.rb @@ -1,7 +1,7 @@ module Dino module Components module Register - class ShiftOut + class ShiftOut < Output # # options = {board: my_board, pins: {clock: clock_pin, latch: latch_pin, data: data_pin} # @@ -10,85 +10,17 @@ class ShiftOut latch: Register::Select, data: Basic::DigitalOutput - def after_initialize(options={}) - # - # To use the register as a board proxy, we need to know how many - # bytes there are and map each bit to a virtual pin. - # Defaults to 1 byte. Ignore if writing to the register directly. - # - @bytes = options[:bytes] || 1 - - # - # When used as a board proxy, store the state of each register - # pin as a 0 or 1 in an array that is (@bytes * 8) long. Zero out to start. - # - @state = Array.new(@bytes*8) {|i| 0} - write_state - - # - # When used as a board proxy, only write sate if @write_delay seconds - # have passed since this object last got input. Better for things like SSDs - # where many bits change in sequence, but not at exactly the same time. - # - @write_delay = options[:write_delay] || 0.005 - - super(options) - end - # - # Send one or more bytes to the register. Clock and latch toggling - # are handled by the board, but we still must set mode in Ruby. + # Write using the native shiftOut function of the Arduino library. # - def write_bytes(*bytes) + def write(*bytes) aux = bytes.flatten length = aux.count - # - # Format request by putting 3 elements before the data bytes, so we get: - # [data pin, clock pin, unused byte, data byte 1, data byte 2...] - # - aux = [data.pin, clock.pin, 0].concat(aux) - # Pack into literal format for aux msg compatbility then send. - aux = aux.pack('C*') + # Prepend parameters we need to send in the aux message then pack and send. + aux = [data.pin, clock.pin, 0].concat(aux).pack('C*') board.write Dino::Message.encode(command: 22, pin: latch.pin, value: length, aux_message: aux) end - - alias :write_byte :write_bytes - alias :write :write_bytes - - # - # Make the register act as a board for components that need only digital - # output pins. Pass the register as a 'board' and pin numbers such that pin 0 - # is the 1st bit of the 1st byte, pin 9 is 1st bit of the 2nd byte, and so on. - # - include Mixins::BoardProxy - def digital_write(pin, value) - @state[pin] = value - delayed_write(@state) - end - - # - # If acting as board, do writes in a separate thread and with small delay. - # Lets us catch multiple changed bits, like when hosting an SSD. - # - include Mixins::Threaded - def delayed_write(state) - threaded do - sleep @write_delay - write_state if (state == @state) - end - end - - # - # Convert bit state to array of 0-255 integers (bytes) then write normally. - # - def write_state - bytes = [] - @state.each_slice(8) do |slice| - bytes << slice.join("").to_i(2) - end - write_bytes(bytes) - end end end end From 970443bf22e38fc045821d4cce5b30a9a9bcce11 Mon Sep 17 00:00:00 2001 From: vickash Date: Wed, 27 Dec 2017 17:26:03 -0400 Subject: [PATCH 114/296] Add 1-way SPI read function and related shift register model. --- examples/register/shift_in.rb | 21 ++++++-- examples/register/shift_out.rb | 7 +-- examples/register/shift_ssd.rb | 20 ++++--- examples/register/spi_in.rb | 40 ++++++++++++++ lib/dino/components/register.rb | 1 + lib/dino/components/register/input.rb | 2 +- lib/dino/components/register/output.rb | 2 +- lib/dino/components/register/shift_in.rb | 3 +- lib/dino/components/register/shift_out.rb | 3 +- lib/dino/components/register/spi_in.rb | 32 +++++++++++ lib/dino_cli/generator.rb | 5 +- src/lib/Dino.cpp | 66 +++++++++++++++++++++-- src/lib/Dino.h | 7 ++- 13 files changed, 183 insertions(+), 26 deletions(-) create mode 100644 examples/register/spi_in.rb create mode 100644 lib/dino/components/register/spi_in.rb diff --git a/examples/register/shift_in.rb b/examples/register/shift_in.rb index 609f57ed..4293bfb3 100644 --- a/examples/register/shift_in.rb +++ b/examples/register/shift_in.rb @@ -1,14 +1,27 @@ # -# This is a simple example to write to a shift register. -# Writing a byte of 255 sets all the output pins to high. +# Example showing how to read an input shift register using Arduino's "shiftIn". +# +# The register implements #digital_read and other methods expected by Components, +# and makes its parallel pins addressable (zero index), so it can proxy the Board class. +# +# The Button object is created by passing the register instead of the board, and +# the register's parallel output pin that the button is connected to. +# +# Note: preclock_high must be set to true if using TI CD4021B register or similar. +# This should apply to any register which outputs on a rising clock edge. +# Change as needed. # require 'bundler/setup' require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) -shift_register = Dino::Components::Register::ShiftIn.new(bytes: 1, preclock_high: true, pins: {data: 14, latch: 15, clock: 16}, board: board) -button = Dino::Components::Button.new(pin: 7, board: shift_register) +shift_register = Dino::Components::Register::ShiftIn.new board: board, + pins: {latch: 10, data: 12, clock: 13}, + preclock_high: true, + bytes: 1 + +button = Dino::Components::Button.new(pin: 0, board: shift_register) button.down { puts "down"} button.up { puts "up" } diff --git a/examples/register/shift_out.rb b/examples/register/shift_out.rb index adb770e1..eefdd95a 100644 --- a/examples/register/shift_out.rb +++ b/examples/register/shift_out.rb @@ -1,12 +1,13 @@ # -# This is a simple example to write to a shift register. -# Writing a byte of 255 sets all the output pins to high. +# Example showing how to set up an output shift register. +# Multiple bytes may be written in one operation. # require 'bundler/setup' require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) -shift_register = Dino::Components::Register::ShiftOut.new(pins: {data: 11, latch: 8, clock: 12}, board: board) +shift_register = Dino::Components::Register::ShiftOut.new board: board, + pins: {latch: 9, data: 11, clock: 13} # Write a single byte shift_register.write(255) diff --git a/examples/register/shift_ssd.rb b/examples/register/shift_ssd.rb index 4fe31a7e..19a3e6dd 100644 --- a/examples/register/shift_ssd.rb +++ b/examples/register/shift_ssd.rb @@ -1,18 +1,22 @@ # -# This examples sets up a shift register, and an SSD that is connected to its 8 output pins. -# The shift register is passed in as the 'board' when setting up the SSD. -# The individual pins on the register are accessible by the SSD instance, so it works as usual. +# Example showing how to use an output shift register to drive a seven segment display. +# +# The register implements #digital_write and other methods expected by Components, +# and makes its parallel pins addressable (zero index), so it can proxy the Board class. +# +# The SSD object is created by passing the register instead of the board, and +# the registers's parallel pin number that each SSD pin is connected to. +# # require 'bundler/setup' require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) -shift_register = Dino::Components::Register::ShiftOut.new(pins: {data: 11, latch: 8, clock: 12}, board: board) +shift_register = Dino::Components::Register::ShiftOut.new board: board, + pins: {data: 6, clock: 7, latch: 8} -ssd = Dino::Components::SSD.new( - board: shift_register, - pins: { cathode: 0, a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7 } -) +ssd = Dino::Components::SSD.new board: shift_register, + pins: { cathode: 0, a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7 } # Turn off the ssd on exit trap("SIGINT") { exit !ssd.off } diff --git a/examples/register/spi_in.rb b/examples/register/spi_in.rb new file mode 100644 index 00000000..308e6007 --- /dev/null +++ b/examples/register/spi_in.rb @@ -0,0 +1,40 @@ +# +# Example showing how to read an input shift register using SPI. +# +# The register implements #digital_read and other methods expected by Components, +# and makes its parallel pins addressable (zero index), so it can proxy the Board class. +# +# The Button object is created by passing the register instead of the board, and +# the register's parallel output pin that the button is connected to. +# +require 'bundler/setup' +require 'dino' + +board = Dino::Board.new(Dino::TxRx::Serial.new) + +# +# Pin 10 is default slave select on Arduino UNO. Connect to register "latch" input. +# Register data and clock pins go to 12 (MISO) and 13 respectively on the Arduino UNO. +# +# Clock and data pins do not need to be given when using SPI, since they are +# predetermined based on the board you are using, and dealt with by the library. +# But they still must be connected. The exact pins vary depending on the board, +# and a reference can be found here: https://www.arduino.cc/en/Reference/SPI +# +# SPI mode and frequency are specific to a TI CD4021B register. Change as needed. +# +shift_register = Dino::Components::Register::SPIIn.new board: board, + pin: 10, + bytes: 1, + spi_mode: 3, + frequency: 3000000 + +button = Dino::Components::Button.new(pin: 0, board: shift_register) + +button.down { puts "down"} +button.up { puts "up" } + +loop do + button.read + sleep 0.1 +end diff --git a/lib/dino/components/register.rb b/lib/dino/components/register.rb index 11bddd06..6da9383e 100644 --- a/lib/dino/components/register.rb +++ b/lib/dino/components/register.rb @@ -6,6 +6,7 @@ module Register require 'dino/components/register/output' require 'dino/components/register/shift_in' require 'dino/components/register/shift_out' + require 'dino/components/register/spi_in' end end end diff --git a/lib/dino/components/register/input.rb b/lib/dino/components/register/input.rb index 1489ae24..8f5768d2 100644 --- a/lib/dino/components/register/input.rb +++ b/lib/dino/components/register/input.rb @@ -1,7 +1,7 @@ module Dino module Components module Register - class Input + module Input include Setup::Base include Mixins::Callbacks diff --git a/lib/dino/components/register/output.rb b/lib/dino/components/register/output.rb index f30e61b7..25fbb451 100644 --- a/lib/dino/components/register/output.rb +++ b/lib/dino/components/register/output.rb @@ -1,7 +1,7 @@ module Dino module Components module Register - class Output + module Output include Setup::Base def after_initialize(options={}) diff --git a/lib/dino/components/register/shift_in.rb b/lib/dino/components/register/shift_in.rb index 65f0e904..68dc2476 100644 --- a/lib/dino/components/register/shift_in.rb +++ b/lib/dino/components/register/shift_in.rb @@ -1,7 +1,8 @@ module Dino module Components module Register - class ShiftIn < Input + class ShiftIn + include Input # # options = {board: my_board, pins: {clock: clock_pin, latch: latch_pin, data: data_pin} # diff --git a/lib/dino/components/register/shift_out.rb b/lib/dino/components/register/shift_out.rb index 708033d1..5a4b80ae 100644 --- a/lib/dino/components/register/shift_out.rb +++ b/lib/dino/components/register/shift_out.rb @@ -1,7 +1,8 @@ module Dino module Components module Register - class ShiftOut < Output + class ShiftOut + include Output # # options = {board: my_board, pins: {clock: clock_pin, latch: latch_pin, data: data_pin} # diff --git a/lib/dino/components/register/spi_in.rb b/lib/dino/components/register/spi_in.rb new file mode 100644 index 00000000..7c0773c4 --- /dev/null +++ b/lib/dino/components/register/spi_in.rb @@ -0,0 +1,32 @@ +module Dino + module Components + module Register + class SPIIn < Select + # + # options = {board: my_board, pin: slave_select_pin} + # + include Input + + def after_initialize(options={}) + super(options) if defined?(super) + + # Save SPI device settings in instance variables. + @spi_mode = options[:spi_mode] || 0 + + # No default value for clock frequency. + raise 'SPI clock rate (Hz) required in :frequency option' unless options[:frequency] + @frequency = options[:frequency] + end + + # + # Read using a call to the native SPI library. + # + def read + # Pack the extra parameters we need to send in the aux message then send. + aux = "#{[@spi_mode].pack('C')}#{[@frequency].pack('V')}" + board.write Dino::Message.encode(command: 25, pin: pin, value: @bytes, aux_message: aux) + end + end + end + end +end diff --git a/lib/dino_cli/generator.rb b/lib/dino_cli/generator.rb index 78a2039e..09e25b3e 100644 --- a/lib/dino_cli/generator.rb +++ b/lib/dino_cli/generator.rb @@ -1,8 +1,8 @@ class DinoCLI::Generator require "fileutils" LIB_FILENAMES = [ - "lib/Dino.cpp", "lib/Dino.h", + "lib/Dino.cpp", "lib/DinoLCD.cpp", "lib/DinoLCD.h", "lib/DinoSerial.cpp", @@ -64,6 +64,9 @@ def modify if options[:debug] @libs[0].gsub! "// #define debug true", "#define debug true" end + unless serial? + @libs[0].gsub! "#define TXRX_SPI false", "#define TXRX_SPI true" + end end def write diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index bf6bd6ba..6b9f6c29 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -98,7 +98,7 @@ void Dino::process() { case 20: tone (); break; case 21: noTone (); break; - // Request format for shift register read, write and add listener functions. + // Request format for shift register functions. // pin = latch pin (int) // val = length (int) // auxMsg[0] = data pin (byte) @@ -108,6 +108,14 @@ void Dino::process() { case 22: shiftWrite (pin, val, auxMsg[0], auxMsg[1], &auxMsg[3]); break; case 23: shiftRead (pin, val, auxMsg[0], auxMsg[1], auxMsg[2]); break; + // Request format for single direction SPI functions. + // pin = slave select pin (int) + // val = length (int) + // auxMsg[0] = SPI mode (byte) + // auxMsg[1-4] = clock frequency (uint32_t as 4 bytes) + // auxMsg[5]+ = data (bytes) (write func only) + case 25: readSPI (pin, val, auxMsg[0], (uint32_t)auxMsg[1]); break; + case 90: reset (); break; case 96: setAnalogResolution (); break; case 97: setAnalogDivider (); break; @@ -425,12 +433,12 @@ void Dino::shiftRead(int latchPin, int len, byte dataPin, byte clockPin, byte cl digitalWrite(latchPin, HIGH); digitalWrite(latchPin, LOW); - // Send the pin number and the colon alone for now. - // Send data as if coming from the latch pin. + // Send data as if coming from the latch pin so it's easy to identify. + // Start with just pin number and : for now. sprintf(response, "%02d:", latchPin); _writeCallback(response); - for (byte i = 1; i <= len; i++) { + for (int i = 1; i <= len; i++) { // Read a single byte from the register. byte reading = shiftIn(dataPin, clockPin, LSBFIRST); @@ -449,6 +457,56 @@ void Dino::shiftRead(int latchPin, int len, byte dataPin, byte clockPin, byte cl } +// CMD = 25 +// Read from an SPI device. +void Dino::readSPI(int selectPin, int len, byte spiMode, uint32_t clockRate) { + // Start the SPI library if it isn't already being used by the main sketch. + SPI.begin(); + + // Set the mode we want. + switch(spiMode) { + case 0: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE0)); break; + case 1: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE1)); break; + case 2: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE2)); break; + case 3: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE3)); break; + } + + // Select the device. + digitalWrite(selectPin, LOW); + + // Send data as if coming from the slave select pin so it's easy to identify. + // Start with just pin number and : for now. + sprintf(response, "%02d:", selectPin); + _writeCallback(response); + + for (int i = 1; i <= len; i++) { + // Read a single byte from the register. + byte reading = SPI.transfer(i); + + // If we're on the last byte, append \n. If not, append a comma, then write. + if (i == len) { + sprintf(response, "%03d\n", reading); + } else { + sprintf(response, "%03d,", reading); + } + _writeCallback(response); + } + + // End the SPI transaction, and then library if not in use by main sketch. + SPI.endTransaction(); + + // TXRX_SPI is set to false in Dino.h. + // CLI generator will auto set to true for any sketch other than serial. + #if !(TXRX_SPI) + SPI.end(); + #endif + + // Leave select high and clear response so main loop doesn't send anything. + digitalWrite(selectPin, HIGH); + response[0] = "\0"; +} + + // CMD = 90 void Dino::reset() { heartRate = 4000; // Default heartRate is ~4ms. diff --git a/src/lib/Dino.h b/src/lib/Dino.h index 2498d2a9..30c678dc 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -4,9 +4,11 @@ #ifndef Dino_h #define Dino_h +#define TXRX_SPI false #include "Arduino.h" #include +#include #include "DinoLCD.h" #include "DHT.h" #include "OneWire.h" @@ -60,8 +62,9 @@ class Dino { void irSend (); //cmd = 16 void tone (); //cmd = 20 void noTone (); //cmd = 21 - void shiftWrite (int dataPin, int clockPin, byte latchPin, byte len, byte data[]); //cmd = 22 - void shiftRead (int dataPin, int clockPin, byte latchPin, byte len, byte clockHighFirst); //cmd = 23 + void shiftWrite (int latchPin, int len, byte dataPin, byte clockPin, byte data[]); //cmd = 22 + void shiftRead (int latchPin, int len, byte dataPin, byte clockPin, byte clockHighFirst); //cmd = 23 + void readSPI (int selectPin, int len, byte spiMode, uint32_t clockRate); //cmd = 25 void reset (); //cmd = 90 void setAnalogResolution (); //cmd = 96 void setAnalogDivider (); //cmd = 97 From c2e4a1a99216e0b8e53e78fd2c237211501d8693 Mon Sep 17 00:00:00 2001 From: vickash Date: Wed, 27 Dec 2017 17:35:51 -0400 Subject: [PATCH 115/296] Always write 0s when SPI read-only. --- src/lib/Dino.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 6b9f6c29..44003663 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -481,7 +481,7 @@ void Dino::readSPI(int selectPin, int len, byte spiMode, uint32_t clockRate) { for (int i = 1; i <= len; i++) { // Read a single byte from the register. - byte reading = SPI.transfer(i); + byte reading = SPI.transfer(0x00); // If we're on the last byte, append \n. If not, append a comma, then write. if (i == len) { From 90fa23bafdafe4d8c7628582d5f1b996ceac4b02 Mon Sep 17 00:00:00 2001 From: vickash Date: Wed, 27 Dec 2017 20:12:35 -0400 Subject: [PATCH 116/296] Add 1-way SPI register write and SSD example to go with. --- examples/register/shift_ssd.rb | 2 +- examples/register/spi_ssd.rb | 27 +++++++++++++++ lib/dino/components/register.rb | 1 + lib/dino/components/register/output.rb | 1 + lib/dino/components/register/shift_in.rb | 3 ++ lib/dino/components/register/shift_out.rb | 8 +++-- lib/dino/components/register/spi_in.rb | 8 +++-- lib/dino/components/register/spi_out.rb | 38 +++++++++++++++++++++ src/lib/Dino.cpp | 41 +++++++++++++++++++++-- src/lib/Dino.h | 3 +- 10 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 examples/register/spi_ssd.rb create mode 100644 lib/dino/components/register/spi_out.rb diff --git a/examples/register/shift_ssd.rb b/examples/register/shift_ssd.rb index 19a3e6dd..58e77e9e 100644 --- a/examples/register/shift_ssd.rb +++ b/examples/register/shift_ssd.rb @@ -13,7 +13,7 @@ board = Dino::Board.new(Dino::TxRx::Serial.new) shift_register = Dino::Components::Register::ShiftOut.new board: board, - pins: {data: 6, clock: 7, latch: 8} + pins: {data: 12, clock: 13, latch: 9} ssd = Dino::Components::SSD.new board: shift_register, pins: { cathode: 0, a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7 } diff --git a/examples/register/spi_ssd.rb b/examples/register/spi_ssd.rb new file mode 100644 index 00000000..0a05e014 --- /dev/null +++ b/examples/register/spi_ssd.rb @@ -0,0 +1,27 @@ +# +# Example showing how to use an output shift register to drive a seven segment display. +# +# The register implements #digital_write and other methods expected by Components, +# and makes its parallel pins addressable (zero index), so it can proxy the Board class. +# +# The SSD object is created by passing the register instead of the board, and +# the registers's parallel pin number that each SSD pin is connected to. +# +# +require 'bundler/setup' +require 'dino' + +board = Dino::Board.new(Dino::TxRx::Serial.new) +shift_register = Dino::Components::Register::SPIOut.new board: board, + pin: 9, + frequency: 3000000, + spi_mode: 3 + +ssd = Dino::Components::SSD.new board: shift_register, + pins: { cathode: 0, a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7 } + +# Turn off the ssd on exit +trap("SIGINT") { exit !ssd.off } + +# Display each new line on the ssd +loop { ssd.display(gets.chomp) } diff --git a/lib/dino/components/register.rb b/lib/dino/components/register.rb index 6da9383e..e9a91b04 100644 --- a/lib/dino/components/register.rb +++ b/lib/dino/components/register.rb @@ -7,6 +7,7 @@ module Register require 'dino/components/register/shift_in' require 'dino/components/register/shift_out' require 'dino/components/register/spi_in' + require 'dino/components/register/spi_out' end end end diff --git a/lib/dino/components/register/output.rb b/lib/dino/components/register/output.rb index 25fbb451..16f9bc96 100644 --- a/lib/dino/components/register/output.rb +++ b/lib/dino/components/register/output.rb @@ -39,6 +39,7 @@ def write # include Mixins::BoardProxy def digital_write(pin, value) + # puts @state.inspect @state[pin] = value delayed_write(@state) end diff --git a/lib/dino/components/register/shift_in.rb b/lib/dino/components/register/shift_in.rb index 68dc2476..cd1f6d45 100644 --- a/lib/dino/components/register/shift_in.rb +++ b/lib/dino/components/register/shift_in.rb @@ -4,6 +4,9 @@ module Register class ShiftIn include Input # + # Model registers that use the arduino shift functions as multi-pin + # components, specifying clock, data and latch pins. + # # options = {board: my_board, pins: {clock: clock_pin, latch: latch_pin, data: data_pin} # include Setup::MultiPin diff --git a/lib/dino/components/register/shift_out.rb b/lib/dino/components/register/shift_out.rb index 5a4b80ae..b2e53efa 100644 --- a/lib/dino/components/register/shift_out.rb +++ b/lib/dino/components/register/shift_out.rb @@ -4,12 +4,16 @@ module Register class ShiftOut include Output # + # Model registers that use the arduino shift functions as multi-pin + # components, specifying clock, data and latch pins. + # # options = {board: my_board, pins: {clock: clock_pin, latch: latch_pin, data: data_pin} # include Setup::MultiPin proxy_pins clock: Basic::DigitalOutput, - latch: Register::Select, - data: Basic::DigitalOutput + data: Basic::DigitalOutput, + latch: Register::Select + # # Write using the native shiftOut function of the Arduino library. diff --git a/lib/dino/components/register/spi_in.rb b/lib/dino/components/register/spi_in.rb index 7c0773c4..0329d91f 100644 --- a/lib/dino/components/register/spi_in.rb +++ b/lib/dino/components/register/spi_in.rb @@ -2,10 +2,13 @@ module Dino module Components module Register class SPIIn < Select + include Input + # + # Model SPI registers as single pin. Data comes back on the select pin, + # so just inherit from Select. # # options = {board: my_board, pin: slave_select_pin} # - include Input def after_initialize(options={}) super(options) if defined?(super) @@ -23,7 +26,8 @@ def after_initialize(options={}) # def read # Pack the extra parameters we need to send in the aux message then send. - aux = "#{[@spi_mode].pack('C')}#{[@frequency].pack('V')}" + start_address = 0 + aux = "#{[start_address, @spi_mode].pack('C*')}#{[@frequency].pack('V')}" board.write Dino::Message.encode(command: 25, pin: pin, value: @bytes, aux_message: aux) end end diff --git a/lib/dino/components/register/spi_out.rb b/lib/dino/components/register/spi_out.rb new file mode 100644 index 00000000..2b5310d2 --- /dev/null +++ b/lib/dino/components/register/spi_out.rb @@ -0,0 +1,38 @@ +module Dino + module Components + module Register + class SPIOut < Select + include Output + # + # Model SPI registers as single pin. Data comes back on the select pin, + # so just inherit from Select. + # + # options = {board: my_board, pin: slave_select_pin} + # + + def after_initialize(options={}) + # Save SPI device settings in instance variables. + @spi_mode = options[:spi_mode] || 0 + + # No default value for clock frequency. + raise 'SPI clock rate (Hz) required in :frequency option' unless options[:frequency] + @frequency = options[:frequency] + + super(options) if defined?(super) + end + + # + # Write using a call to the native SPI library. + # + def write(*bytes) + # Pack the extra parameters we need to send in the aux message then send. + aux = bytes.flatten + length = aux.count + aux = "#{[@spi_mode].pack('C')}#{[@frequency].pack('V')}#{aux.pack('C*')}" + + board.write Dino::Message.encode(command: 24, pin: pin, value: length, aux_message: aux) + end + end + end + end +end diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 44003663..fdee54b5 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -114,7 +114,8 @@ void Dino::process() { // auxMsg[0] = SPI mode (byte) // auxMsg[1-4] = clock frequency (uint32_t as 4 bytes) // auxMsg[5]+ = data (bytes) (write func only) - case 25: readSPI (pin, val, auxMsg[0], (uint32_t)auxMsg[1]); break; + case 24: writeSPI (pin, val, auxMsg[0], (uint32_t)auxMsg[1], &auxMsg[5]); break; + case 25: readSPI (pin, val, auxMsg[0], (uint32_t)auxMsg[1] ); break; case 90: reset (); break; case 96: setAnalogResolution (); break; @@ -407,7 +408,7 @@ void Dino::noTone() { // CMD = 22 // Write to a shift register. -void Dino::shiftWrite(int latchPin, int len, byte dataPin, byte clockPin, byte data[]) { +void Dino::shiftWrite(int latchPin, int len, byte dataPin, byte clockPin, byte *data) { // Set latch pin low to begin serial write. digitalWrite(latchPin, LOW); @@ -457,6 +458,42 @@ void Dino::shiftRead(int latchPin, int len, byte dataPin, byte clockPin, byte cl } +// CMD = 24 +// Write to an SPI device. +void Dino::writeSPI(int selectPin, int len, byte spiMode, uint32_t clockRate, byte *data) { + // Start the SPI library if it isn't already being used by the main sketch. + SPI.begin(); + + // Set the mode we want. + switch(spiMode) { + case 0: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE0)); break; + case 1: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE1)); break; + case 2: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE2)); break; + case 3: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE3)); break; + } + + // Select the device. + digitalWrite(selectPin, LOW); + + // Write one byte at a time. + for (uint8_t i = 0; i < len; i++) { + SPI.transfer(data[i]); + } + + // End the SPI transaction, and then library if not in use by main sketch. + SPI.endTransaction(); + + // TXRX_SPI is set to false in Dino.h. + // CLI generator will auto set to true for any sketch other than serial. + #if !(TXRX_SPI) + SPI.end(); + #endif + + // Leave select high. + digitalWrite(selectPin, HIGH); +} + + // CMD = 25 // Read from an SPI device. void Dino::readSPI(int selectPin, int len, byte spiMode, uint32_t clockRate) { diff --git a/src/lib/Dino.h b/src/lib/Dino.h index 30c678dc..75efe8c4 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -62,8 +62,9 @@ class Dino { void irSend (); //cmd = 16 void tone (); //cmd = 20 void noTone (); //cmd = 21 - void shiftWrite (int latchPin, int len, byte dataPin, byte clockPin, byte data[]); //cmd = 22 + void shiftWrite (int latchPin, int len, byte dataPin, byte clockPin, byte *data); //cmd = 22 void shiftRead (int latchPin, int len, byte dataPin, byte clockPin, byte clockHighFirst); //cmd = 23 + void writeSPI (int selectPin, int len, byte spiMode, uint32_t clockRate, byte *data); //cmd = 24 void readSPI (int selectPin, int len, byte spiMode, uint32_t clockRate); //cmd = 25 void reset (); //cmd = 90 void setAnalogResolution (); //cmd = 96 From 6d6e9a27d087af7685973cd9c5cf968ab8091d6a Mon Sep 17 00:00:00 2001 From: vickash Date: Fri, 29 Dec 2017 21:58:13 -0400 Subject: [PATCH 117/296] Add software flow control for data transmitted to the board. --- examples/register/spi_ssd.rb | 2 +- lib/dino/board.rb | 1 + lib/dino/tx_rx/base.rb | 86 +++++++++++++++++++++++++++++------ lib/dino/tx_rx/serial.rb | 9 ++-- lib/dino/tx_rx/tcp.rb | 18 ++++---- spec/lib/tx_rx/serial_spec.rb | 5 +- src/dino_ethernet.ino | 27 ++++++++++- src/dino_serial.ino | 30 +++++++++++- src/dino_wifi.ino | 28 +++++++++++- 9 files changed, 174 insertions(+), 32 deletions(-) diff --git a/examples/register/spi_ssd.rb b/examples/register/spi_ssd.rb index 0a05e014..079e0915 100644 --- a/examples/register/spi_ssd.rb +++ b/examples/register/spi_ssd.rb @@ -18,7 +18,7 @@ spi_mode: 3 ssd = Dino::Components::SSD.new board: shift_register, - pins: { cathode: 0, a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7 } + pins: { cathode: 0, a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7 } # Turn off the ssd on exit trap("SIGINT") { exit !ssd.off } diff --git a/lib/dino/board.rb b/lib/dino/board.rb index 1e081663..8098dec4 100644 --- a/lib/dino/board.rb +++ b/lib/dino/board.rb @@ -9,6 +9,7 @@ def initialize(io, options={}) @analog_zero, @dac_zero = @io.handshake.to_s.split(",").map { |pin| pin.to_i } self.analog_resolution = options[:bits] + start_read end def analog_resolution=(value) diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index fee04990..4600817f 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -7,21 +7,12 @@ class BoardNotFound < StandardError; end class Base include Observable + BOARD_BUFFER = 60 def io @io ||= connect end - def _read - line = gets - if line && line.match(/\A\d+:/) - pin, message = line.split(/:/) - pin && message && changed && notify_observers(pin, message) - else - sleep 0.005 - end - end - def read @thread ||= Thread.new { loop { _read } }.abort_on_exception = true end @@ -32,17 +23,23 @@ def close_read @thread = nil end + def write(message) + @write_mutex.synchronize { synced_write(message) } + end + def handshake + initialize_flow_control flush_read - 10.times do + 10.times do |retries| begin Timeout.timeout(1) do write Dino::Message.encode(command: 90) loop do line = gets if line && line.match(/ACK:/) - puts "Connected to board..." flush_read + ignore_retry_bytes(retries) + puts "Connected to board..." return line.split(/:/)[1] end end @@ -54,10 +51,71 @@ def handshake raise BoardNotFound end - def write(message); raise "#write should be defined in TxRx subclasses"; end - private + def synced_write(message) + puts "currently #{@transit_bytes} in transit" + message = message.split("") + loop do + @flow_control.synchronize do + bytes = BOARD_BUFFER - @transit_bytes + break unless bytes > 0 + + bytes = message.length if (message.length < bytes) + fragment = String.new + bytes.times { fragment << message.shift } + io.write(fragment) + @transit_bytes = @transit_bytes + bytes + end + return if message.empty? + sleep 0.005 + end + end + + def io_write(message); raise "#io_write should be defined in TxRx subclasses"; end + + def _read + line = gets + line ? process_line(line) : sleep(0.005) + end + + def process_line(line) + if line.match(/\A\d+:/) + pin, message = line.split(/:/) + pin && message && changed && notify_observers(pin, message) + elsif line.match(/\ARCV:/) + # This is acknowledgement from the board that bytes have been read + # out of the hardware buffer, freeing up that space. + # Subtract from the transit bytes so #synced_write can send more data. + remove_transit_bytes(line.split(/:/)[1].to_i) + end + end + + def initialize_flow_control + @flow_control ||= Mutex.new + @write_mutex ||= Mutex.new + @transit_bytes ||= 0 + end + + def transit_bytes + @flow_control.synchronize { @transit_bytes } + end + + def add_transit_bytes(value) + @flow_control.synchronize { @transit_bytes = @transit_bytes + value } + end + + def remove_transit_bytes(value) + @flow_control.synchronize { @transit_bytes = @transit_bytes - value } + end + + # Subtract bytes for failed handshakes from the total in transit bytes. + # The board will never acknowledge these. + def ignore_retry_bytes(retries) + retry_bytes = Dino::Message.encode(command: 90).length * retries + remove_transit_bytes(retry_bytes) + end + def connect(message); raise "#connect should be defined in TxRx subclasses"; end def gets(message); raise "#gets should be defined in TxRx subclasses"; end diff --git a/lib/dino/tx_rx/serial.rb b/lib/dino/tx_rx/serial.rb index de7a939e..f61a6363 100644 --- a/lib/dino/tx_rx/serial.rb +++ b/lib/dino/tx_rx/serial.rb @@ -10,13 +10,10 @@ def initialize(options={}) @baud = options[:baud] || BAUD end - def write(message) - io.write(message) - end - private def connect + # ::Serial calls the rubyserial gem here. tty_devices.each { |device| return ::Serial.new(device, @baud) rescue nil } raise BoardNotFound end @@ -31,6 +28,10 @@ def on_windows? RUBY_PLATFORM.match /mswin|mingw/i end + def io_write(message) + io.write(message) + end + def gets(timeout=0) buff, escaped = "", false loop do diff --git a/lib/dino/tx_rx/tcp.rb b/lib/dino/tx_rx/tcp.rb index edeb1125..d9610775 100644 --- a/lib/dino/tx_rx/tcp.rb +++ b/lib/dino/tx_rx/tcp.rb @@ -7,15 +7,6 @@ def initialize(host, port=3466) @host, @port = host, port end - def write(message) - loop do - if IO.select(nil, [io], nil) - io.syswrite(message) - break - end - end - end - private def connect @@ -24,6 +15,15 @@ def connect raise BoardNotFound end + def io_write(message) + loop do + if IO.select(nil, [io], nil) + io.syswrite(message) + break + end + end + end + def gets(timeout=0.005) IO.select([io], nil, nil, timeout) && io.gets.gsub(/\n\z/, "") end diff --git a/spec/lib/tx_rx/serial_spec.rb b/spec/lib/tx_rx/serial_spec.rb index 7a2eb07d..3608a181 100644 --- a/spec/lib/tx_rx/serial_spec.rb +++ b/spec/lib/tx_rx/serial_spec.rb @@ -56,7 +56,7 @@ module Dino expect(subject).to receive(:gets).and_return("02:00") expect(subject).to receive(:changed).and_return(true) expect(subject).to receive(:notify_observers).with('02','00') - subject._read + subject.send(:_read) end end @@ -81,8 +81,11 @@ module Dino it 'should write to the device' do expect(subject).to receive(:io).and_return(mock_serial = double) expect(mock_serial).to receive(:write).with('a message') + subject.send(:initialize_flow_control) subject.write('a message') end + + it 'should break up messages larger than the board input buffer' end describe '#gets' do diff --git a/src/dino_ethernet.ino b/src/dino_ethernet.ino index 40805e90..e2abb425 100644 --- a/src/dino_ethernet.ino +++ b/src/dino_ethernet.ino @@ -59,6 +59,20 @@ void setup() { dino.setupWrite(writeCallback); } + +// Keep count of bytes as we receive them and send a dino message with how many. +uint8_t rcvBytes = 0; +uint8_t rcvBuffer = 60; +long lastRcv = micros(); +long rcvWindow = 1000000; + +void acknowledge() { + client.write("RCV:"); + client.write(rcvBytes); + client.write("\n"); + rcvBytes = 0; +} + void loop() { // Listen for connections. client = server.available(); @@ -66,7 +80,18 @@ void loop() { // Handle a connection. if (client) { while (client.connected()) { - while (client.available()) dino.parse(client.read()); + while (client.available()){ + dino.parse(client.read()); + + // Acknowledge when we've received as many bytes as the serial input buffer. + lastRcv = micros(); + rcvBytes ++; + if (rcvBytes == rcvBuffer) acknowledge(); + } + + // Also acknowledge when the last byte received goes outside the receive window. + if ((rcvBytes > 0) && ((micros() - lastRcv) > rcvWindow)) acknowledge(); + dino.updateListeners(); writeResponses(); } diff --git a/src/dino_serial.ino b/src/dino_serial.ino index becc210f..06046473 100644 --- a/src/dino_serial.ino +++ b/src/dino_serial.ino @@ -17,6 +17,7 @@ Dino dino; HardwareSerial &serial = Serial; #endif + // Dino.h doesn't handle TXRX. Create a callback so it can write to serial. void writeResponse(char *response) { serial.print(response); } void (*writeCallback)(char *str) = writeResponse; @@ -32,7 +33,34 @@ void setup() { dino.setupWrite(writeCallback); } + +// Keep count of bytes as we receive them and send a dino message with how many. +uint8_t rcvBytes = 0; +uint8_t rcvBuffer = 60; +long lastRcv = micros(); +long rcvWindow = 1000000; + +void acknowledge() { + serial.print("RCV:"); + serial.print(rcvBytes); + serial.print("\n"); + rcvBytes = 0; +} + + void loop() { - while(serial.available() > 0) dino.parse(serial.read()); + while(serial.available() > 0) { + dino.parse(serial.read()); + + // Acknowledge when we've received as many bytes as the serial input buffer. + lastRcv = micros(); + rcvBytes ++; + if (rcvBytes == rcvBuffer) acknowledge(); + } + + // Also acknowledge when the last byte received goes outside the receive window. + if ((rcvBytes > 0) && ((micros() - lastRcv) > rcvWindow)) acknowledge(); + + // Run dino's listeners. dino.updateListeners(); } diff --git a/src/dino_wifi.ino b/src/dino_wifi.ino index 704a058c..edb436c3 100644 --- a/src/dino_wifi.ino +++ b/src/dino_wifi.ino @@ -68,6 +68,21 @@ void setup() { dino.setupWrite(writeCallback); } + +// Keep count of bytes as we receive them and send a dino message with how many. +uint8_t rcvBytes = 0; +uint8_t rcvBuffer = 60; +long lastRcv = micros(); +long rcvWindow = 1000000; + +void acknowledge() { + client.write("RCV:"); + client.write(rcvBytes); + client.write("\n"); + rcvBytes = 0; +} + + void loop() { // Listen for connections. client = server.available(); @@ -75,7 +90,18 @@ void loop() { // Handle a connection. if (client) { while (client.connected()) { - while (client.available()) dino.parse(client.read()); + while (client.available()){ + dino.parse(client.read()); + + // Acknowledge when we've received as many bytes as the serial input buffer. + lastRcv = micros(); + rcvBytes ++; + if (rcvBytes == rcvBuffer) acknowledge(); + } + + // Also acknowledge when the last byte received goes outside the receive window. + if ((rcvBytes > 0) && ((micros() - lastRcv) > rcvWindow)) acknowledge(); + dino.updateListeners(); writeResponses(); } From 451fcdce87cae306e6ddfd79346c2ea86605e6a1 Mon Sep 17 00:00:00 2001 From: vickash Date: Fri, 29 Dec 2017 22:44:12 -0400 Subject: [PATCH 118/296] Forgot to remove a debugging line. --- lib/dino/tx_rx/base.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index 4600817f..3f0d16de 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -54,7 +54,6 @@ def handshake private def synced_write(message) - puts "currently #{@transit_bytes} in transit" message = message.split("") loop do @flow_control.synchronize do From e437a10b5be36b189fe5586bae3f1350a1e4837d Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 30 Dec 2017 01:40:31 -0400 Subject: [PATCH 119/296] Add listeners for shift and SPI registers. --- examples/button/button.rb | 3 + examples/register/shift_in.rb | 8 +- examples/register/spi_combined.rb | 41 +++++++ examples/register/spi_in.rb | 8 +- lib/dino/components/mixins/callbacks.rb | 5 +- lib/dino/components/mixins/reader.rb | 2 +- lib/dino/components/register/input.rb | 73 +++++++++-- lib/dino/components/register/shift_in.rb | 11 ++ lib/dino/components/register/spi_in.rb | 14 ++- spec/lib/components/mixins/callbacks_spec.rb | 6 +- src/lib/Dino.cpp | 122 ++++++++++++++++--- src/lib/Dino.h | 41 ++++++- 12 files changed, 290 insertions(+), 44 deletions(-) create mode 100644 examples/register/spi_combined.rb diff --git a/examples/button/button.rb b/examples/button/button.rb index 4f9f0d31..eadecf7f 100644 --- a/examples/button/button.rb +++ b/examples/button/button.rb @@ -18,4 +18,7 @@ puts "button up" end +# Force callbacks to run at least once for the initial state. +button.read + sleep diff --git a/examples/register/shift_in.rb b/examples/register/shift_in.rb index 4293bfb3..64ce1521 100644 --- a/examples/register/shift_in.rb +++ b/examples/register/shift_in.rb @@ -26,7 +26,7 @@ button.down { puts "down"} button.up { puts "up" } -loop do - button.read - sleep 0.1 -end +# Force callbacks to run at least once for the initial state. +button.read + +sleep diff --git a/examples/register/spi_combined.rb b/examples/register/spi_combined.rb new file mode 100644 index 00000000..32bfa994 --- /dev/null +++ b/examples/register/spi_combined.rb @@ -0,0 +1,41 @@ +# +# Example showing how to use an output shift register to drive a seven segment display. +# +# The register implements #digital_write and other methods expected by Components, +# and makes its parallel pins addressable (zero index), so it can proxy the Board class. +# +# The SSD object is created by passing the register instead of the board, and +# the registers's parallel pin number that each SSD pin is connected to. +# +# +require 'bundler/setup' +require 'dino' + +board = Dino::Board.new(Dino::TxRx::Serial.new) +output_register = Dino::Components::Register::SPIOut.new board: board, + pin: 9, + frequency: 3000000, + spi_mode: 3 + +ssd = Dino::Components::SSD.new board: output_register, + pins: { cathode: 0, a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7 } + +input_register = Dino::Components::Register::SPIIn.new board: board, + pin: 10, + bytes: 1, + spi_mode: 3, + frequency: 3000000 + +button = Dino::Components::Button.new(pin: 0, board: input_register) + +button.down { puts "down"} +button.up { puts "up" } + +# Force callbacks to run at least once for the initial state. +button.read + +# Turn off the ssd on exit +trap("SIGINT") { exit !ssd.off } + +# Display each new line on the ssd +loop { ssd.display(gets.chomp) } diff --git a/examples/register/spi_in.rb b/examples/register/spi_in.rb index 308e6007..6bacdd9c 100644 --- a/examples/register/spi_in.rb +++ b/examples/register/spi_in.rb @@ -34,7 +34,7 @@ button.down { puts "down"} button.up { puts "up" } -loop do - button.read - sleep 0.1 -end +# Force callbacks to run at least once for the initial state. +button.read + +sleep diff --git a/lib/dino/components/mixins/callbacks.rb b/lib/dino/components/mixins/callbacks.rb index a2661a65..15253360 100644 --- a/lib/dino/components/mixins/callbacks.rb +++ b/lib/dino/components/mixins/callbacks.rb @@ -17,7 +17,7 @@ def add_callback(key=:persistent, &block) def remove_callback(key=nil) @callback_mutex.synchronize { - key ? @callbacks[key] = [] : @callbacks = {} + key ? @callbacks.delete(key) : @callbacks = {} } end @@ -29,8 +29,9 @@ def update(data) @callbacks.each_value do |array| array.each { |callback| callback.call(data) } end + # Remove the special :read callback while still inside the lock. + @callbacks.delete(:read) } - remove_callback :read @state = data end end diff --git a/lib/dino/components/mixins/reader.rb b/lib/dino/components/mixins/reader.rb index 6f3c90ff..285a6eca 100644 --- a/lib/dino/components/mixins/reader.rb +++ b/lib/dino/components/mixins/reader.rb @@ -7,7 +7,7 @@ module Reader def read(&block) add_callback(:read, &block) if block_given? _read - loop { break if !@callbacks[:read] || @callbacks[:read].empty? } + loop { break if !@callbacks[:read] } end # diff --git a/lib/dino/components/register/input.rb b/lib/dino/components/register/input.rb index 8f5768d2..48dc4dac 100644 --- a/lib/dino/components/register/input.rb +++ b/lib/dino/components/register/input.rb @@ -20,6 +20,13 @@ def after_initialize(options={}) # @state = Array.new(@bytes*8) {|i| 0} + # + # Keep track of whether anything is listening or reading a specific pin. + # This might be separate from whether a component is attached or not. + # + @reading_pins = Array.new(@bytes*8) {|i| false} + @listening_pins = Array.new(@bytes*8) {|i| false} + # # Certain registers which use rising edges for clock signals produce # errors with the native Arduino shiftIn function unless you set the clock @@ -32,12 +39,28 @@ def after_initialize(options={}) end # - # Callbacks#update mostly works, but we need to convert the state from - # comma delimited numbers into an array of bits first. + # Override Callbacks#update to make sure we handle :force_update + # within the main mutex lock. # def update(message) bits = byte_array_to_bit_array(message.split(",")) - super(bits) + + @callback_mutex.synchronize { + # + # The Arduino code does not de-duplicate repeated state. + # Do it here, but if a :force_update callback exists, run anyway. + # + if (bits != @state)|| @callbacks[:force_update] + @callbacks.each_value do |array| + array.each { |callback| callback.call(bits) } + end + end + + # Remove both :read and :force update while inside the lock. + @callbacks.delete(:read) + @callbacks.delete(:force_update) + } + @state = bits end # @@ -66,18 +89,37 @@ def listen include Mixins::BoardProxy def digital_read(pin) - read + # + # Remember what pin was read and force callbacks to run next update. + # + add_callback(:force_update) { Proc.new{} } + @reading_pins[pin] = true + + # Don't actually call #read if already listening. + read unless any_listening end def digital_listen(pin) - # - # Start the remote listener - # Need to implement this on Arduino. - # + listen unless any_listening + @listening_pins[pin] = true + end + + def stop_listener(pin) + @listening_pins[pin] = false + stop unless any_listening + end + + def any_listening + @listening_pins.each { |p| return true if p } + false + end + + def stop + raise 'define #stop in child class to stop the listener' end # - # Mimic Board#update, but in a callback fired through Callbacks#update. + # Mimic Board#update, but in a callback fired through #update. # This doesn't interfere with using the register directly, # and doesn't fire if the board isn't acting as a proxy (no components). # @@ -85,11 +127,22 @@ def enable_proxy self.add_callback(:board_proxy) do |bit_array| bit_array.each_with_index do |value, pin| @components.each do |part| - part.update(value) if pin.to_i == part.pin + update_component(part, pin, value) if pin == part.pin end + @reading_pins[pin] = false end end end + + def update_component(part, pin, value) + # Update if component is listening and value has changed. + if @listening_pins[pin] && (value != @state[pin]) + part.update(value) + # Also update if the component forced a read. + elsif @reading_pins[pin] && @callbacks[:force_update] + part.update(value) + end + end end end end diff --git a/lib/dino/components/register/shift_in.rb b/lib/dino/components/register/shift_in.rb index cd1f6d45..c6c65779 100644 --- a/lib/dino/components/register/shift_in.rb +++ b/lib/dino/components/register/shift_in.rb @@ -37,6 +37,17 @@ def read aux = [data.pin, clock.pin, @preclock_high].pack('C*') board.write Dino::Message.encode(command: 23, pin: latch.pin, value: @bytes, aux_message: aux) end + + def listen + # Pack the extra parameters we need to send in the aux message then send. + aux = [data.pin, clock.pin, @preclock_high].pack('C*') + board.write Dino::Message.encode(command: 26, pin: latch.pin, value: @bytes, aux_message: aux) + end + + def stop + # Just need to send the latch pin to stop listening. + board.write Dino::Message.encode(command: 28, pin: latch.pin) + end end end end diff --git a/lib/dino/components/register/spi_in.rb b/lib/dino/components/register/spi_in.rb index 0329d91f..4f7cb5de 100644 --- a/lib/dino/components/register/spi_in.rb +++ b/lib/dino/components/register/spi_in.rb @@ -3,13 +3,13 @@ module Components module Register class SPIIn < Select include Input + # # Model SPI registers as single pin. Data comes back on the select pin, # so just inherit from Select. # # options = {board: my_board, pin: slave_select_pin} # - def after_initialize(options={}) super(options) if defined?(super) @@ -30,6 +30,18 @@ def read aux = "#{[start_address, @spi_mode].pack('C*')}#{[@frequency].pack('V')}" board.write Dino::Message.encode(command: 25, pin: pin, value: @bytes, aux_message: aux) end + + def listen + # Pack the extra parameters we need to send in the aux message then send. + start_address = 0 + aux = "#{[start_address, @spi_mode].pack('C*')}#{[@frequency].pack('V')}" + board.write Dino::Message.encode(command: 27, pin: pin, value: @bytes, aux_message: aux) + end + + def stop + # Just need to send the select pin to stop listening. + board.write Dino::Message.encode(command: 28, pin: pin) + end end end end diff --git a/spec/lib/components/mixins/callbacks_spec.rb b/spec/lib/components/mixins/callbacks_spec.rb index 5f0186a6..cbc23595 100644 --- a/spec/lib/components/mixins/callbacks_spec.rb +++ b/spec/lib/components/mixins/callbacks_spec.rb @@ -52,8 +52,8 @@ def initialize; after_initialize; end it 'should remove only callbacks for a specific key given' do subject.remove_callbacks(:read) - expect(subject.instance_variable_get(:@callbacks)[:read]).to eq([]) - expect(subject.instance_variable_get(:@callbacks)[:persistent]).to_not eq([]) + expect(subject.instance_variable_get(:@callbacks)[:read]).to eq(nil) + expect(subject.instance_variable_get(:@callbacks)[:persistent]).to_not eq(nil) end end @@ -71,7 +71,7 @@ def initialize; after_initialize; end it 'should remove any callbacks saved with the key :read' do subject.update("data") - expect(subject.instance_variable_get(:@callbacks)[:read]).to eq([]) + expect(subject.instance_variable_get(:@callbacks)[:read]).to eq(nil) end end end diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index fdee54b5..6e7cfd79 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -107,7 +107,6 @@ void Dino::process() { // auxMsg[3]+ = data (bytes) (write func only) case 22: shiftWrite (pin, val, auxMsg[0], auxMsg[1], &auxMsg[3]); break; case 23: shiftRead (pin, val, auxMsg[0], auxMsg[1], auxMsg[2]); break; - // Request format for single direction SPI functions. // pin = slave select pin (int) // val = length (int) @@ -116,8 +115,16 @@ void Dino::process() { // auxMsg[5]+ = data (bytes) (write func only) case 24: writeSPI (pin, val, auxMsg[0], (uint32_t)auxMsg[1], &auxMsg[5]); break; case 25: readSPI (pin, val, auxMsg[0], (uint32_t)auxMsg[1] ); break; + // These add listeners for both shift in and SPI registers. + // They mirror the read function interfaces of the respective devices. + case 26: addShiftListener(pin, val, auxMsg[0], auxMsg[1], auxMsg[2]); break; + case 27: addSPIListener (pin, val, auxMsg[0], (uint32_t)auxMsg[1]); break; + // This removes either type of listener and frees its space in the cache. + // The only input it needs is the select/latch pin. + case 28: removeRegisterListener(); case 90: reset (); break; + case 95: setAnalogDivider (); break; case 96: setAnalogResolution (); break; case 97: setAnalogDivider (); break; case 98: setHeartRate (); break; @@ -149,7 +156,8 @@ void Dino::updateListeners() { lastUpdate = micros(); loopCount++; updateDigitalListeners(); - if (loopCount % analogDivider == 0) updateAnalogListeners(); + if (loopCount % registerDivider == 0) updateRegisterListeners(); + if (loopCount % analogDivider == 0) updateAnalogListeners(); } } void Dino::updateDigitalListeners() { @@ -163,6 +171,25 @@ void Dino::updateDigitalListeners() { } } } +void Dino::updateRegisterListeners() { + for (int i = 0; i < SPI_LISTENER_COUNT; i++) { + if (spiListeners[i].enabled) { + readSPI(spiListeners[i].selectPin, + spiListeners[i].len, + spiListeners[i].spiMode, + spiListeners[i].clockRate); + } + } + for (int i = 0; i < SHIFT_LISTENER_COUNT; i++) { + if (shiftListeners[i].enabled) { + shiftRead(shiftListeners[i].latchPin, + shiftListeners[i].len, + shiftListeners[i].dataPin, + shiftListeners[i].clockPin, + shiftListeners[i].clockHighFirst); + } + } +} void Dino::updateAnalogListeners() { for (int i = 0; i < PIN_COUNT; i++) { if (analogListeners[i]) { @@ -543,18 +570,82 @@ void Dino::readSPI(int selectPin, int len, byte spiMode, uint32_t clockRate) { response[0] = "\0"; } +// CMD = 26 +// Start listening to a register using the Arduino shiftIn function. +// Overwrite the first disabled listener in the struct array. +void Dino::addShiftListener(int latchPin, int len, byte dataPin, byte clockPin, byte clockHighFirst) { + for (int i = 0; i < SHIFT_LISTENER_COUNT; i++) { + if (shiftListeners[i].enabled == false) { + shiftListeners[i] = { + latchPin, + len, + dataPin, + clockPin, + clockHighFirst, + true + }; + return; + } else { + // Should send some kind of error if all are in use. + } + } +} + +// CMD = 26 +// Start listening to an SPI register. +// Overwrite the first disabled listener in the struct array. +void Dino::addSPIListener(int selectPin, int len, byte spiMode, uint32_t clockRate) { + for (int i = 0; i < SPI_LISTENER_COUNT; i++) { + if (spiListeners[i].enabled == false) { + spiListeners[i] = { + selectPin, + len, + spiMode, + clockRate, + true + }; + return; + } else { + // Should send some kind of error if all are in use. + } + } +} + + +// CMD = 27 +// Send a number for a select/latch pin to remove either type of register listener. +void Dino::removeRegisterListener() { + for (int i = 0; i < SHIFT_LISTENER_COUNT; i++) { + if (shiftListeners[i].latchPin == pin) { + shiftListeners[i].enabled = false; + } + } + for (int i = 0; i < SPI_LISTENER_COUNT; i++) { + if (spiListeners[i].selectPin == pin) { + spiListeners[i].enabled = false; + } + } +} + // CMD = 90 void Dino::reset() { - heartRate = 4000; // Default heartRate is ~4ms. - loopCount = 0; - analogDivider = 4; // Update analog listeners every ~16ms. + // Clear the analog and digital pin listeners. for (int i = 0; i < PIN_COUNT; i++) digitalListeners[i] = false; for (int i = 0; i < PIN_COUNT; i++) digitalListenerValues[i] = 2; - for (int i = 0; i < PIN_COUNT; i++) analogListeners[i] = false; - lastUpdate = micros(); + for (int i = 0; i < PIN_COUNT; i++) analogListeners[i] = false; + + // Disable the register listeners. + for (int i = 0; i < SHIFT_LISTENER_COUNT; i++) shiftListeners[i].enabled = false; + for (int i = 0; i < SPI_LISTENER_COUNT; i++) spiListeners[i].enabled = false; + + heartRate = 4000; // Update digital listeners every ~4ms. + analogDivider = 4; // Update analog listeners every ~16ms. + registerDivider = 2; // Update register listeners every ~8ms. fragmentIndex = 0; charIndex = 0; + loopCount = 0; + lastUpdate = micros(); #if defined(__SAM3X8E__) sprintf(response, "ACK:%d,%d", A0, DAC0); @@ -563,15 +654,18 @@ void Dino::reset() { #endif } -// CMD = 97 +// CMD = 95 +// Set the register read divider. Powers of 2 up to 128 are valid. +void Dino::setRegisterDivider() { + registerDivider = val; +} + +// CMD = 96 // Set the analog read and write resolution. void Dino::setAnalogResolution() { #if defined(__SAM3X8E__) analogReadResolution(val); analogWriteResolution(val); - #ifdef debug - Serial.print("Analog R/W resolution set to "); Serial.println(val); - #endif #endif } @@ -579,16 +673,10 @@ void Dino::setAnalogResolution() { // Set the analog divider. Powers of 2 up to 128 are valid. void Dino::setAnalogDivider() { analogDivider = val; - #ifdef debug - Serial.print("Analog divider set to "); Serial.println(analogDivider); - #endif } // CMD = 98 // Set the heart rate in milliseconds. Store it in microseconds. void Dino::setHeartRate() { heartRate = atoi(auxMsg); - #ifdef debug - Serial.print("Heart rate set to "); Serial.print(heartRate); Serial.println(" microseconds"); - #endif } diff --git a/src/lib/Dino.h b/src/lib/Dino.h index 75efe8c4..f6c730a3 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -32,6 +32,10 @@ # define SERVO_OFFSET 2 #endif +// Allocate listener storage for serial registers. +#define SPI_LISTENER_COUNT 4 +#define SHIFT_LISTENER_COUNT 4 + // Uncomment this line to enable debugging mode. // #define debug true @@ -66,11 +70,21 @@ class Dino { void shiftRead (int latchPin, int len, byte dataPin, byte clockPin, byte clockHighFirst); //cmd = 23 void writeSPI (int selectPin, int len, byte spiMode, uint32_t clockRate, byte *data); //cmd = 24 void readSPI (int selectPin, int len, byte spiMode, uint32_t clockRate); //cmd = 25 + void addShiftListener (int latchPin, int len, byte dataPin, byte clockPin, byte clockHighFirst); //cmd = 26 + void addSPIListener (int selectPin, int len, byte spiMode, uint32_t clockRate); //cmd = 27 + void removeRegisterListener(); //cmd = 28 void reset (); //cmd = 90 + void setRegisterDivider (); //cmd = 97 void setAnalogResolution (); //cmd = 96 void setAnalogDivider (); //cmd = 97 void setHeartRate (); //cmd = 98 + // Serial flow control variables. + uint8_t rcvBytes = 0; + uint8_t rcvBuffer = 60; + long lastRcv = micros(); + long rcvWindow = 1000000; + // Parser state storage and utility functions. char *messageFragments[4]; byte fragmentIndex; @@ -102,17 +116,40 @@ class Dino { long lastUpdate; unsigned int loopCount; unsigned int analogDivider; + unsigned int registerDivider; long timeSince (long event); // Listeners correspond to raw pin number by array index, and store boolean. false == disabled. boolean analogListeners[PIN_COUNT]; boolean digitalListeners[PIN_COUNT]; + // Create listeners for SPI registers. + struct spiListener{ + byte selectPin; + byte len; + byte spiMode; + uint32_t clockRate; + boolean enabled; + }; + spiListener spiListeners[SPI_LISTENER_COUNT]; + + // Create listeners for ShiftIn registers. + struct shiftListener{ + byte latchPin; + byte len; + byte dataPin; + byte clockPin; + byte clockHighFirst; + boolean enabled; + }; + shiftListener shiftListeners[SHIFT_LISTENER_COUNT]; + // Keep track of the last read values for digital listeners. Only write responses when changed. byte digitalListenerValues[PIN_COUNT]; // Listener update functions. - void updateDigitalListeners (); - void updateAnalogListeners (); + void updateDigitalListeners (); + void updateRegisterListeners (); + void updateAnalogListeners (); }; #endif From c796776004a4aab6a6bb8559bb7dc378166471fa Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 30 Dec 2017 21:08:00 -0400 Subject: [PATCH 120/296] Only split board messages on the fist colon. Allows colon in values. --- lib/dino/tx_rx/base.rb | 2 +- spec/lib/tx_rx/serial_spec.rb | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index 3f0d16de..b38486d8 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -80,7 +80,7 @@ def _read def process_line(line) if line.match(/\A\d+:/) - pin, message = line.split(/:/) + pin, message = line.split(":", 2) pin && message && changed && notify_observers(pin, message) elsif line.match(/\ARCV:/) # This is acknowledgement from the board that bytes have been read diff --git a/spec/lib/tx_rx/serial_spec.rb b/spec/lib/tx_rx/serial_spec.rb index 3608a181..bdb03cb0 100644 --- a/spec/lib/tx_rx/serial_spec.rb +++ b/spec/lib/tx_rx/serial_spec.rb @@ -58,6 +58,13 @@ module Dino expect(subject).to receive(:notify_observers).with('02','00') subject.send(:_read) end + + it 'should not split messages into more than 2 parts on :' do + expect(subject).to receive(:gets).and_return("02:00:00") + expect(subject).to receive(:changed).and_return(true) + expect(subject).to receive(:notify_observers).with('02','00:00') + subject.send(:_read) + end end describe '#read' do From 55af6a901e896bcf478f8c988f5a453aefbc8eea Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 31 Dec 2017 01:12:28 -0400 Subject: [PATCH 121/296] Add I2C functions to arduino sketch. --- .gitmodules | 3 + lib/dino_cli/generator.rb | 5 +- src/lib/Dino.cpp | 136 ++++++++++++++++++++++++++++++++++---- src/lib/Dino.h | 10 ++- 4 files changed, 138 insertions(+), 16 deletions(-) diff --git a/.gitmodules b/.gitmodules index ae49a950..2d2f614d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "src/vendor/Arduino-IRremote"] path = src/vendor/Arduino-IRremote url = https://github.com/vickash/Arduino-IRremote.git +[submodule "src/vendor/I2C-Master-Library"] + path = src/vendor/I2C-Master-Library + url = https://github.com/vickash/I2C-Master-Library.git diff --git a/lib/dino_cli/generator.rb b/lib/dino_cli/generator.rb index 09e25b3e..eb8f3b2c 100644 --- a/lib/dino_cli/generator.rb +++ b/lib/dino_cli/generator.rb @@ -16,7 +16,10 @@ class DinoCLI::Generator "vendor/Arduino-IRremote/IRremote.cpp", "vendor/Arduino-IRremote/IRremote.h", "vendor/Arduino-IRremote/IRremoteInt.h", - "vendor/Arduino-IRremote/irSend.cpp" + "vendor/Arduino-IRremote/irSend.cpp", + + "vendor/I2C-Master-Library/I2C.h", + "vendor/I2C-Master-Library/I2C.cpp" ] attr_accessor :options diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 6e7cfd79..33767c3d 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -26,7 +26,7 @@ Dino::Dino(){ reset(); } -void Dino::parse(char c) { +void Dino::parse(byte c) { if ((c == '\n') || (c == '\\')) { // If last char was a \, this \ or \n is escaped. if(backslash){ @@ -62,7 +62,7 @@ void Dino::parse(char c) { else append(c); } -void Dino::append(char c) { +void Dino::append(byte c) { messageFragments[fragmentIndex][charIndex++] = c; } @@ -123,6 +123,13 @@ void Dino::process() { // The only input it needs is the select/latch pin. case 28: removeRegisterListener(); + // I2C functions. + case 30: i2cBegin (); break; + case 31: i2cEnd (); break; + case 32: i2cScan (); break; + case 33: i2cWrite (); break; + case 34: i2cRead (); break; + case 90: reset (); break; case 95: setAnalogDivider (); break; case 96: setAnalogResolution (); break; @@ -242,7 +249,7 @@ void Dino::dWrite() { // CMD = 02 // Digital Read void Dino::dRead(int pin) { rval = digitalRead(pin); - sprintf(response, "%02d:%02d", pin, rval); + sprintf(response, "%d:%d", pin, rval); } // CMD = 03 // Analog (PWM) Write @@ -256,7 +263,7 @@ void Dino::aWrite() { // CMD = 04 // Analog Read void Dino::aRead(int pin) { rval = analogRead(pin); - sprintf(response, "%02d:%02d", pin, rval); + sprintf(response, "%d:%d", pin, rval); } // CMD = 05 @@ -463,7 +470,7 @@ void Dino::shiftRead(int latchPin, int len, byte dataPin, byte clockPin, byte cl // Send data as if coming from the latch pin so it's easy to identify. // Start with just pin number and : for now. - sprintf(response, "%02d:", latchPin); + sprintf(response, "%d:", latchPin); _writeCallback(response); for (int i = 1; i <= len; i++) { @@ -472,16 +479,16 @@ void Dino::shiftRead(int latchPin, int len, byte dataPin, byte clockPin, byte cl // If we're on the last byte, append \n. If not, append a comma, then write. if (i == len) { - sprintf(response, "%03d\n", reading); + sprintf(response, "%d\n", reading); } else { - sprintf(response, "%03d,", reading); + sprintf(response, "%d,", reading); } _writeCallback(response); } // Leave latch pin high and clear response so main loop doesn't send anything. digitalWrite(latchPin, HIGH); - response[0] = "\0"; + response[0] = '\0'; } @@ -540,7 +547,7 @@ void Dino::readSPI(int selectPin, int len, byte spiMode, uint32_t clockRate) { // Send data as if coming from the slave select pin so it's easy to identify. // Start with just pin number and : for now. - sprintf(response, "%02d:", selectPin); + sprintf(response, "%d:", selectPin); _writeCallback(response); for (int i = 1; i <= len; i++) { @@ -549,9 +556,9 @@ void Dino::readSPI(int selectPin, int len, byte spiMode, uint32_t clockRate) { // If we're on the last byte, append \n. If not, append a comma, then write. if (i == len) { - sprintf(response, "%03d\n", reading); + sprintf(response, "%d\n", reading); } else { - sprintf(response, "%03d,", reading); + sprintf(response, "%d,", reading); } _writeCallback(response); } @@ -567,7 +574,7 @@ void Dino::readSPI(int selectPin, int len, byte spiMode, uint32_t clockRate) { // Leave select high and clear response so main loop doesn't send anything. digitalWrite(selectPin, HIGH); - response[0] = "\0"; + response[0] = '\0'; } // CMD = 26 @@ -628,6 +635,109 @@ void Dino::removeRegisterListener() { } +// CMD = 30 +// Start I2C communication. +void Dino::i2cBegin() { + I2c.begin(); + // I2c.setSpeed(??); + // I2c.pullup(??); + // I2c.timeOut(??); + sprintf(response, "%d:I2C:1", SDA); +} + + +// CMD = 31 +// Stop I2C communication. +void Dino::i2cEnd() { + I2c.end(); + sprintf(response, "%d:I2C:0", SDA); +} + + +// CMD = 32 +// Scan for I2C devices. +// +// WARNING: This takes a long time! Try to record the device addresses +// results and put them into your code. +// +// Returns each found address as if a separate reading from SDA pin, eg. "18:104". +// Returns 128 as if read from SDA pin for search complete, eg. "18:128". +// Returns 255 as if read from SDA pin for I2C errors, eg. "18:255". +// +void Dino::i2cScan() { + uint8_t address = 0; + while (address < 128) { + // Scan for the next device. + address = I2c.scanOne(address); + + // Write whatever we get including address space end or errors. + sprintf(response, "%d:%d", SDA, address); + writeResponse(); + address++; + } + // Clear the response to make sure it doesn't get sent twice. + response[0] = '\0'; +} + + +// CMD = 33 +// Write to an I2C device. +// All parameters need to be sent in binary in the auxMsg. +// +// auxMsg[0] = device address +// auxMsg[1] = register start address +// auxMsg[2] = number of bytes +// auxMsg[3]+ = data +// +// Limited to 255 bytes. Validate on remote end. +// +void Dino::i2cWrite() { + I2c.write(auxMsg[0], auxMsg[1], &auxMsg[3], auxMsg[2]); +} + + +// CMD = 34 +// Read from an I2C device. +// All params need to be sent in binary in the auxMsg. +// +// auxMsg[0] = device address +// auxMsg[1] = register start address +// auxMsg[2] = number of bytes +// +// Streams data back in comma delimited ASCII decimal for now, +// matching shiftRead and readSPI. Limited to 32 bytes by I2C library buffer. +// Validate on remote end. +// +void Dino::i2cRead() { + // Force length to be min 1, max 32. + if (auxMsg[2] > 32) auxMsg[2] = 32; + if (auxMsg[2] == 0) auxMsg[2] = 1; + + // Read all the bytes into the library buffer. + I2c.read(auxMsg[0], auxMsg[1], auxMsg[2]); + + // Send back the SDA pin, the device address, and start register address first. + sprintf(response, "%d:%d:%d:", SDA, auxMsg[0], auxMsg[1]); + _writeCallback(response); + + // Send back the data bytes. + uint8_t currentByte = 0; + while(I2c.available()){ + currentByte++; + // Append comma, but \n for last byte, then write. + if (currentByte == auxMsg[2]){ + sprintf(response, "%d\n", I2c.receive()); + } else { + sprintf(response, "%d,", I2c.receive()); + } + _writeCallback(response); + } + + // Clear the response to make sure it doesn't get sent twice. + response[0] = '\0'; +} + + // CMD = 90 void Dino::reset() { // Clear the analog and digital pin listeners. @@ -637,7 +747,7 @@ void Dino::reset() { // Disable the register listeners. for (int i = 0; i < SHIFT_LISTENER_COUNT; i++) shiftListeners[i].enabled = false; - for (int i = 0; i < SPI_LISTENER_COUNT; i++) spiListeners[i].enabled = false; + for (int i = 0; i < SPI_LISTENER_COUNT; i++) spiListeners[i].enabled = false; heartRate = 4000; // Update digital listeners every ~4ms. analogDivider = 4; // Update analog listeners every ~16ms. diff --git a/src/lib/Dino.h b/src/lib/Dino.h index f6c730a3..fb53adc2 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -13,6 +13,7 @@ #include "DHT.h" #include "OneWire.h" #include "IRremote.h" +#include "I2C.h" // SoftwareSerial doesn't work on the Due yet. #if !defined(__SAM3X8E__) @@ -43,7 +44,7 @@ class Dino { public: Dino(); void setupWrite(void (*writeCallback)(char *str)); - void parse(char c); + void parse(byte c); void updateListeners(); private: @@ -73,6 +74,11 @@ class Dino { void addShiftListener (int latchPin, int len, byte dataPin, byte clockPin, byte clockHighFirst); //cmd = 26 void addSPIListener (int selectPin, int len, byte spiMode, uint32_t clockRate); //cmd = 27 void removeRegisterListener(); //cmd = 28 + void i2cBegin (); //cmd = 30 + void i2cEnd (); //cmd = 31 + void i2cScan (); //cmd = 32 + void i2cWrite (); //cmd = 33 + void i2cRead (); //cmd = 34 void reset (); //cmd = 90 void setRegisterDivider (); //cmd = 97 void setAnalogResolution (); //cmd = 96 @@ -90,7 +96,7 @@ class Dino { byte fragmentIndex; int charIndex; boolean backslash; - void append(char c); + void append(byte c); void process(); // Parsed message storage. From b88f1dd4e95e511fd72faeb919852d5d697803e2 Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 31 Dec 2017 01:22:43 -0400 Subject: [PATCH 122/296] =?UTF-8?q?Missing=20gitlink=20for=20I2C=20library?= =?UTF-8?q?=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/vendor/I2C-Master-Library | 1 + 1 file changed, 1 insertion(+) create mode 160000 src/vendor/I2C-Master-Library diff --git a/src/vendor/I2C-Master-Library b/src/vendor/I2C-Master-Library new file mode 160000 index 00000000..32d3fd72 --- /dev/null +++ b/src/vendor/I2C-Master-Library @@ -0,0 +1 @@ +Subproject commit 32d3fd7253e6b39d7674b843435fb9e4160a1619 From 13e84f71f4c5c33cd5a313ad6edbf677b8218db4 Mon Sep 17 00:00:00 2001 From: vickash Date: Fri, 5 Jan 2018 11:24:27 -0400 Subject: [PATCH 123/296] Ack bytes received by board more often. Minor sketch cleanup. --- examples/register/shift_ssd.rb | 2 +- src/dino_ethernet.ino | 9 +---- src/dino_serial.ino | 9 +---- src/dino_wifi.ino | 9 +---- src/lib/Dino.cpp | 74 ++++++++++++++++++++-------------- src/lib/Dino.h | 8 +--- 6 files changed, 52 insertions(+), 59 deletions(-) diff --git a/examples/register/shift_ssd.rb b/examples/register/shift_ssd.rb index 58e77e9e..6bfd0e82 100644 --- a/examples/register/shift_ssd.rb +++ b/examples/register/shift_ssd.rb @@ -13,7 +13,7 @@ board = Dino::Board.new(Dino::TxRx::Serial.new) shift_register = Dino::Components::Register::ShiftOut.new board: board, - pins: {data: 12, clock: 13, latch: 9} + pins: {data: 11, clock: 13, latch: 9} ssd = Dino::Components::SSD.new board: shift_register, pins: { cathode: 0, a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7 } diff --git a/src/dino_ethernet.ino b/src/dino_ethernet.ino index e2abb425..d5de3e36 100644 --- a/src/dino_ethernet.ino +++ b/src/dino_ethernet.ino @@ -2,11 +2,6 @@ #include #include -// SoftwareSerial doesn't work on the Due yet. -#if !defined(__SAM3X8E__) - #include -#endif - // Configure your MAC address, IP address, and HTTP port here. byte mac[] = { 0xDE, 0xAD, 0xBE, 0x30, 0x31, 0x32 }; IPAddress ip(192,168,0,77); @@ -62,7 +57,7 @@ void setup() { // Keep count of bytes as we receive them and send a dino message with how many. uint8_t rcvBytes = 0; -uint8_t rcvBuffer = 60; +uint8_t rcvThreshold = 30; long lastRcv = micros(); long rcvWindow = 1000000; @@ -86,7 +81,7 @@ void loop() { // Acknowledge when we've received as many bytes as the serial input buffer. lastRcv = micros(); rcvBytes ++; - if (rcvBytes == rcvBuffer) acknowledge(); + if (rcvBytes == rcvThreshold) acknowledge(); } // Also acknowledge when the last byte received goes outside the receive window. diff --git a/src/dino_serial.ino b/src/dino_serial.ino index 06046473..70cf652e 100644 --- a/src/dino_serial.ino +++ b/src/dino_serial.ino @@ -1,10 +1,5 @@ #include "Dino.h" -// SoftwareSerial doesn't work on the Due yet. -#if !defined(__SAM3X8E__) - #include -#endif - Dino dino; // Use 'serial' to reference the right interface depending on the device. @@ -36,7 +31,7 @@ void setup() { // Keep count of bytes as we receive them and send a dino message with how many. uint8_t rcvBytes = 0; -uint8_t rcvBuffer = 60; +uint8_t rcvThreshold = 30; long lastRcv = micros(); long rcvWindow = 1000000; @@ -55,7 +50,7 @@ void loop() { // Acknowledge when we've received as many bytes as the serial input buffer. lastRcv = micros(); rcvBytes ++; - if (rcvBytes == rcvBuffer) acknowledge(); + if (rcvBytes == rcvThreshold) acknowledge(); } // Also acknowledge when the last byte received goes outside the receive window. diff --git a/src/dino_wifi.ino b/src/dino_wifi.ino index edb436c3..17807eb1 100644 --- a/src/dino_wifi.ino +++ b/src/dino_wifi.ino @@ -2,11 +2,6 @@ #include #include -// SoftwareSerial doesn't work on the Due yet. -#if !defined(__SAM3X8E__) - #include -#endif - // Configure your WiFi options here. MAC address and IP address are not configurable. int port = 3466; char ssid [] = "yourNetwork"; @@ -71,7 +66,7 @@ void setup() { // Keep count of bytes as we receive them and send a dino message with how many. uint8_t rcvBytes = 0; -uint8_t rcvBuffer = 60; +uint8_t rcvThreshold = 30; long lastRcv = micros(); long rcvWindow = 1000000; @@ -96,7 +91,7 @@ void loop() { // Acknowledge when we've received as many bytes as the serial input buffer. lastRcv = micros(); rcvBytes ++; - if (rcvBytes == rcvBuffer) acknowledge(); + if (rcvBytes == rcvThreshold) acknowledge(); } // Also acknowledge when the last byte received goes outside the receive window. diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 33767c3d..f2c4ee22 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -73,9 +73,9 @@ void Dino::process() { response[0] = '\0'; #ifdef debug - Serial.print("Command - "); Serial.println(cmdStr); - Serial.print("Pin - "); Serial.println(pinStr); - Serial.print("Value - "); Serial.println(valStr); + Serial.print("Received - Command: "); Serial.print(cmdStr); + Serial.print(" Pin: "); Serial.print(pinStr); + Serial.print(" Value: "); Serial.print(valStr); Serial.print("\n") #endif // Call the command. @@ -142,8 +142,7 @@ void Dino::process() { if (response[0] != '\0') writeResponse(); #ifdef debug - Serial.print("Responded with - "); Serial.println(response); - Serial.println(); + Serial.print("Responded with - "); Serial.print(response); Serial.print("\n\n"); #endif } @@ -211,89 +210,104 @@ long Dino::timeSince(long event) { } - // API FUNCTIONS -// CMD = 00 // Pin Mode + +// CMD = 00 +// Set a pin to output (0), or input (1). void Dino::setMode() { if (val == 0) { removeListener(); pinMode(pin, OUTPUT); - #ifdef debug - Serial.print("Set pin "); Serial.print(pin); Serial.print(" to "); Serial.println("OUTPUT mode"); - #endif } else { pinMode(pin, INPUT); - #ifdef debug - Serial.print("Set pin "); Serial.print(pin); Serial.print(" to "); Serial.println("INPTUT mode"); - #endif } + + #ifdef debug + Serial.print("Called Dino::setMode()\n"); + #endif } -// CMD = 01 // Digital Write +// CMD = 01 +// Write a digital output pin. 0 for LOW, 1 or >0 for HIGH. void Dino::dWrite() { if (val == 0) { digitalWrite(pin, LOW); - #ifdef debug - Serial.print("Digital write "); Serial.print(LOW); Serial.print(" to pin "); Serial.println(pin); - #endif } else { digitalWrite(pin, HIGH); - #ifdef debug - Serial.print("Digital write "); Serial.print(HIGH); Serial.print(" to pin "); Serial.println(pin); - #endif } + + #ifdef debug + Serial.print("Called Dino::dWrite()\n"); + #endif } -// CMD = 02 // Digital Read +// CMD = 02 +// Read a digital input pin. 0 for LOW, 1 for HIGH. void Dino::dRead(int pin) { rval = digitalRead(pin); sprintf(response, "%d:%d", pin, rval); + + #ifdef debug + Serial.print("Called Dino::dRead()\n"); + #endif } -// CMD = 03 // Analog (PWM) Write +// CMD = 03 +// Write an analog output pin. 0 for LOW, up to 255 for HIGH @ 8-bit resolution. void Dino::aWrite() { analogWrite(pin,val); + #ifdef debug - Serial.print("Analog write "); Serial.print(val); Serial.print(" to pin "); Serial.println(pin); + Serial.print("Called Dino::aWrite()\n"); #endif } -// CMD = 04 // Analog Read +// CMD = 04 +// Read an analog input pin. 0 for LOW, up to 1023 for HIGH @ 10-bit resolution. void Dino::aRead(int pin) { rval = analogRead(pin); sprintf(response, "%d:%d", pin, rval); + + #ifdef debug + Serial.print("Called Dino::aRead()\n"); + #endif } // CMD = 05 -// Listen for a digital signal on any pin. +// Set a flag to periodically read a digital pin without further requests. +// Runs around other requests in the main loop. Only sends value when changed. void Dino::addDigitalListener() { removeListener(); digitalListeners[pin] = true; digitalListenerValues[pin] = 2; + #ifdef debug - Serial.print("Added digital listener on pin "); Serial.println(pin); + Serial.print("Called Dino::addDigitalListener()\n"); #endif } // CMD = 06 -// Listen for an analog signal on analog pins only. +// Set a flag to periodically read an analog pin without further requests. +// Runs around other requests in the main loop. Sends value on every read. void Dino::addAnalogListener() { removeListener(); analogListeners[pin] = true; + #ifdef debug - Serial.print("Added analog listener on pin "); Serial.println(pin); + Serial.print("Called Dino::addAnalogListener()\n"); #endif } // CMD = 07 -// Remove analog and digital listeners from any pin. +// Remove analog and digital listen flags on the pin. void Dino::removeListener() { analogListeners[pin] = false; digitalListeners[pin] = false; + #ifdef debug - Serial.print("Removed listeners on pin "); Serial.println(pin); + Serial.print("Called Dino::removeListener()\n"); #endif } diff --git a/src/lib/Dino.h b/src/lib/Dino.h index fb53adc2..96b549ab 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -84,13 +84,7 @@ class Dino { void setAnalogResolution (); //cmd = 96 void setAnalogDivider (); //cmd = 97 void setHeartRate (); //cmd = 98 - - // Serial flow control variables. - uint8_t rcvBytes = 0; - uint8_t rcvBuffer = 60; - long lastRcv = micros(); - long rcvWindow = 1000000; - + // Parser state storage and utility functions. char *messageFragments[4]; byte fragmentIndex; From 3ebd05101622bbbf0d1ed1836b4a0abb0fb1ce0c Mon Sep 17 00:00:00 2001 From: vickash Date: Fri, 5 Jan 2018 19:42:51 -0400 Subject: [PATCH 124/296] Split into many cpp files. Toggle features with 1 #define. --- lib/dino/board.rb | 10 +- lib/dino/components/register/shift_in.rb | 6 +- lib/dino/components/register/shift_out.rb | 2 +- lib/dino/components/register/spi_in.rb | 6 +- lib/dino/components/register/spi_out.rb | 2 +- lib/dino_cli/generator.rb | 17 +- spec/lib/components/register/shift_in_spec.rb | 6 +- .../lib/components/register/shift_out_spec.rb | 4 +- src/lib/Dino.cpp | 767 +++--------------- src/lib/Dino.h | 172 ++-- src/lib/DinoBugWorkaround.cpp | 24 + src/lib/DinoCoreIO.cpp | 136 ++++ src/lib/DinoDHT.cpp | 41 + src/lib/DinoI2C.cpp | 112 +++ src/lib/DinoIROut.cpp | 16 + src/lib/DinoIncludes.cpp | 34 + src/lib/DinoOneWire.cpp | 62 ++ src/lib/DinoSPI.cpp | 153 ++++ src/lib/DinoSerial.cpp | 5 - src/lib/DinoSerial.h | 5 - src/lib/DinoServo.cpp | 73 ++ src/lib/DinoShift.cpp | 133 +++ src/lib/DinoTone.cpp | 18 + 23 files changed, 1043 insertions(+), 761 deletions(-) create mode 100644 src/lib/DinoBugWorkaround.cpp create mode 100644 src/lib/DinoCoreIO.cpp create mode 100644 src/lib/DinoDHT.cpp create mode 100644 src/lib/DinoI2C.cpp create mode 100644 src/lib/DinoIROut.cpp create mode 100644 src/lib/DinoIncludes.cpp create mode 100644 src/lib/DinoOneWire.cpp create mode 100644 src/lib/DinoSPI.cpp create mode 100644 src/lib/DinoServo.cpp create mode 100644 src/lib/DinoShift.cpp create mode 100644 src/lib/DinoTone.cpp diff --git a/lib/dino/board.rb b/lib/dino/board.rb index 8098dec4..3dce145a 100644 --- a/lib/dino/board.rb +++ b/lib/dino/board.rb @@ -69,6 +69,7 @@ def set_pullup(pin, pullup) end PIN_COMMANDS = { + # set_pin_mode: '0' digital_write: '1', digital_read: '2', analog_write: '3', @@ -78,10 +79,15 @@ def set_pullup(pin, pullup) stop_listener: '7', servo_toggle: '8', servo_write: '9', + # LCD '10' + # Unused '11' + # SoftSerial '12' dht_read: '13', + # HCSR04 '14' ds18b20_read: '15', - tone: '20', - no_tone: '21' + # IR send: '16' + tone: '17', + no_tone: '18' } PIN_COMMANDS.each_key do |command| diff --git a/lib/dino/components/register/shift_in.rb b/lib/dino/components/register/shift_in.rb index c6c65779..5e1aace0 100644 --- a/lib/dino/components/register/shift_in.rb +++ b/lib/dino/components/register/shift_in.rb @@ -35,18 +35,18 @@ def bubble_callbacks def read # Pack the extra parameters we need to send in the aux message then send. aux = [data.pin, clock.pin, @preclock_high].pack('C*') - board.write Dino::Message.encode(command: 23, pin: latch.pin, value: @bytes, aux_message: aux) + board.write Dino::Message.encode(command: 22, pin: latch.pin, value: @bytes, aux_message: aux) end def listen # Pack the extra parameters we need to send in the aux message then send. aux = [data.pin, clock.pin, @preclock_high].pack('C*') - board.write Dino::Message.encode(command: 26, pin: latch.pin, value: @bytes, aux_message: aux) + board.write Dino::Message.encode(command: 23, pin: latch.pin, value: @bytes, aux_message: aux) end def stop # Just need to send the latch pin to stop listening. - board.write Dino::Message.encode(command: 28, pin: latch.pin) + board.write Dino::Message.encode(command: 24, pin: latch.pin) end end end diff --git a/lib/dino/components/register/shift_out.rb b/lib/dino/components/register/shift_out.rb index b2e53efa..c8e2d005 100644 --- a/lib/dino/components/register/shift_out.rb +++ b/lib/dino/components/register/shift_out.rb @@ -24,7 +24,7 @@ def write(*bytes) # Prepend parameters we need to send in the aux message then pack and send. aux = [data.pin, clock.pin, 0].concat(aux).pack('C*') - board.write Dino::Message.encode(command: 22, pin: latch.pin, value: length, aux_message: aux) + board.write Dino::Message.encode(command: 21, pin: latch.pin, value: length, aux_message: aux) end end end diff --git a/lib/dino/components/register/spi_in.rb b/lib/dino/components/register/spi_in.rb index 4f7cb5de..87d11099 100644 --- a/lib/dino/components/register/spi_in.rb +++ b/lib/dino/components/register/spi_in.rb @@ -28,19 +28,19 @@ def read # Pack the extra parameters we need to send in the aux message then send. start_address = 0 aux = "#{[start_address, @spi_mode].pack('C*')}#{[@frequency].pack('V')}" - board.write Dino::Message.encode(command: 25, pin: pin, value: @bytes, aux_message: aux) + board.write Dino::Message.encode(command: 27, pin: pin, value: @bytes, aux_message: aux) end def listen # Pack the extra parameters we need to send in the aux message then send. start_address = 0 aux = "#{[start_address, @spi_mode].pack('C*')}#{[@frequency].pack('V')}" - board.write Dino::Message.encode(command: 27, pin: pin, value: @bytes, aux_message: aux) + board.write Dino::Message.encode(command: 28, pin: pin, value: @bytes, aux_message: aux) end def stop # Just need to send the select pin to stop listening. - board.write Dino::Message.encode(command: 28, pin: pin) + board.write Dino::Message.encode(command: 29, pin: pin) end end end diff --git a/lib/dino/components/register/spi_out.rb b/lib/dino/components/register/spi_out.rb index 2b5310d2..96645e9f 100644 --- a/lib/dino/components/register/spi_out.rb +++ b/lib/dino/components/register/spi_out.rb @@ -30,7 +30,7 @@ def write(*bytes) length = aux.count aux = "#{[@spi_mode].pack('C')}#{[@frequency].pack('V')}#{aux.pack('C*')}" - board.write Dino::Message.encode(command: 24, pin: pin, value: length, aux_message: aux) + board.write Dino::Message.encode(command: 26, pin: pin, value: length, aux_message: aux) end end end diff --git a/lib/dino_cli/generator.rb b/lib/dino_cli/generator.rb index eb8f3b2c..87bd7e78 100644 --- a/lib/dino_cli/generator.rb +++ b/lib/dino_cli/generator.rb @@ -3,10 +3,23 @@ class DinoCLI::Generator LIB_FILENAMES = [ "lib/Dino.h", "lib/Dino.cpp", + "lib/DinoCoreIO.cpp", + "lib/DinoIncludes.cpp", "lib/DinoLCD.cpp", "lib/DinoLCD.h", "lib/DinoSerial.cpp", "lib/DinoSerial.h", + "lib/DinoServo.cpp", + "lib/DinoDHT.cpp", + "lib/DinoOneWire.cpp", + "lib/DinoIROut.cpp", + "lib/DinoTone.cpp", + "lib/DinoShift.cpp", + "lib/DinoSPI.cpp", + "lib/DinoI2C.cpp", + + # See explanation at top of src/lib/DinoBugWorkaround.cpp + "lib/DinoBugWorkaround.cpp", "vendor/DHT/DHT.cpp", "vendor/DHT/DHT.h", @@ -65,10 +78,10 @@ def modify @sketch.gsub! "int port = 3466", "int port = #{options[:port]}" end if options[:debug] - @libs[0].gsub! "// #define debug true", "#define debug true" + @libs[0].gsub! "// #define debug", "#define debug" end unless serial? - @libs[0].gsub! "#define TXRX_SPI false", "#define TXRX_SPI true" + @libs[0].gsub! "// #define TXRX_SPI", "#define TXRX_SPI" end end diff --git a/spec/lib/components/register/shift_in_spec.rb b/spec/lib/components/register/shift_in_spec.rb index 192c0b4d..ac6c3aa1 100644 --- a/spec/lib/components/register/shift_in_spec.rb +++ b/spec/lib/components/register/shift_in_spec.rb @@ -40,19 +40,19 @@ module Register before(:each) { subject } it 'should send message for single byte in the request format the board expects' do - expect(board).to receive(:write).with "23.8.1.#{[11,12,0].pack('C*')}\n" + expect(board).to receive(:write).with "22.8.1.#{[11,12,0].pack('C*')}\n" subject.read end it 'should request the correct number of bytes to be read' do subject = ShiftIn.new(options.merge(bytes: 2)) - expect(board).to receive(:write).with "23.8.2.#{[11,12,0].pack('C*')}\n" + expect(board).to receive(:write).with "22.8.2.#{[11,12,0].pack('C*')}\n" subject.read end it 'should request clock pin to go high before reading if set' do subject = ShiftIn.new(options.merge(preclock_high: 1)) - expect(board).to receive(:write).with "23.8.1.#{[11,12,1].pack('C*')}\n" + expect(board).to receive(:write).with "22.8.1.#{[11,12,1].pack('C*')}\n" subject.read end end diff --git a/spec/lib/components/register/shift_out_spec.rb b/spec/lib/components/register/shift_out_spec.rb index bb7b5e37..a5104f65 100644 --- a/spec/lib/components/register/shift_out_spec.rb +++ b/spec/lib/components/register/shift_out_spec.rb @@ -23,13 +23,13 @@ module Register before(:each) { subject } it 'should send message for single byte in the request format the board expects' do - expect(board).to receive(:write).with "22.8.1.#{[11,12,0,255].pack('C*')}\n" + expect(board).to receive(:write).with "21.8.1.#{[11,12,0,255].pack('C*')}\n" subject.write(255) end it 'should send message for array of bytes in the request format the board expects' do - expect(board).to receive(:write).with "22.8.2.#{[11,12,0,255,0].pack('C*')}\n" + expect(board).to receive(:write).with "21.8.2.#{[11,12,0,255,0].pack('C*')}\n" subject.write([255,0]) end diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index f2c4ee22..9bbb92ef 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -1,24 +1,9 @@ /* Library for dino ruby gem. */ - -#include "Arduino.h" #include "Dino.h" -#if defined(__SAM3X8E__) - #include -#endif - -DinoLCD dinoLCD; -DHT dht; -IRsend irsend; - -// SoftwareSerial doesn't work on the Due yet. -#if !defined(__SAM3X8E__) - DinoSerial dinoSerial; -#endif Dino::Dino(){ - newline[0] = '\n'; newline[1] = '\0'; messageFragments[0] = cmdStr; messageFragments[1] = pinStr; messageFragments[2] = valStr; @@ -67,9 +52,9 @@ void Dino::append(byte c) { } void Dino::process() { - cmd = atoi(cmdStr); - pin = atoi(pinStr); - val = atoi(valStr); + cmd = atoi((char *)cmdStr); + pin = atoi((char *)pinStr); + val = atoi((char *)valStr); response[0] = '\0'; #ifdef debug @@ -80,6 +65,10 @@ void Dino::process() { // Call the command. switch(cmd) { + // See explanation at top of DinoBugWorkaround.cpp + case 999999: bugWorkaround (); break; + + // Implemented in this file. case 0: setMode (); break; case 1: dWrite (); break; case 2: dRead (pin); break; @@ -88,53 +77,77 @@ void Dino::process() { case 5: addDigitalListener (); break; case 6: addAnalogListener (); break; case 7: removeListener (); break; + + // Implemented in DinoServo.cpp + #ifdef DINO_SERVO case 8: servoToggle (); break; case 9: servoWrite (); break; + #endif + + // Implemented in DinoLCD.cpp + #ifdef DINO_LCD case 10: handleLCD (); break; + #endif + + // Implemented in DinoSerial.cpp + #ifdef DINO_SERIAL case 12: handleSerial (); break; - case 13: handleDHT (); break; + #endif + + // Implemented in DinoDHT.cpp + #ifdef DINO_DHT + case 13: dhtRead (); break; + #endif + + // Implemented in DinoOneWire.cpp + #ifdef DINO_ONE_WIRE case 15: ds18Read (); break; + #endif + + // Implemented in DinoIROut.cpp + #ifdef DINO_IR_OUT case 16: irSend (); break; - case 20: tone (); break; - case 21: noTone (); break; - - // Request format for shift register functions. - // pin = latch pin (int) - // val = length (int) - // auxMsg[0] = data pin (byte) - // auxMsg[1] = clock pin (byte) - // auxMsg[2] = send clock high before reading (byte) (0/1) (read func only) - // auxMsg[3]+ = data (bytes) (write func only) - case 22: shiftWrite (pin, val, auxMsg[0], auxMsg[1], &auxMsg[3]); break; - case 23: shiftRead (pin, val, auxMsg[0], auxMsg[1], auxMsg[2]); break; - // Request format for single direction SPI functions. - // pin = slave select pin (int) - // val = length (int) - // auxMsg[0] = SPI mode (byte) - // auxMsg[1-4] = clock frequency (uint32_t as 4 bytes) - // auxMsg[5]+ = data (bytes) (write func only) - case 24: writeSPI (pin, val, auxMsg[0], (uint32_t)auxMsg[1], &auxMsg[5]); break; - case 25: readSPI (pin, val, auxMsg[0], (uint32_t)auxMsg[1] ); break; - // These add listeners for both shift in and SPI registers. - // They mirror the read function interfaces of the respective devices. - case 26: addShiftListener(pin, val, auxMsg[0], auxMsg[1], auxMsg[2]); break; - case 27: addSPIListener (pin, val, auxMsg[0], (uint32_t)auxMsg[1]); break; - // This removes either type of listener and frees its space in the cache. - // The only input it needs is the select/latch pin. - case 28: removeRegisterListener(); - - // I2C functions. - case 30: i2cBegin (); break; - case 31: i2cEnd (); break; - case 32: i2cScan (); break; - case 33: i2cWrite (); break; - case 34: i2cRead (); break; + #endif + // Implemented in DinoTone.cpp + #ifdef DINO_TONE + case 17: tone (); break; + case 18: noTone (); break; + #endif + + // Implemented in DinoShift.cpp + #ifdef DINO_SHIFT + case 21: shiftWrite (pin, val, auxMsg[0], auxMsg[1], &auxMsg[3]); break; + case 22: shiftRead (pin, val, auxMsg[0], auxMsg[1], auxMsg[2]); break; + case 23: addShiftListener (pin, val, auxMsg[0], auxMsg[1], auxMsg[2]); break; + case 24: removeShiftListener (); break; + #endif + + // Implemented in DinoSPI.cpp + #ifdef DINO_SPI + case 26: spiWrite (pin, val, auxMsg[0], (uint32_t)auxMsg[1], &auxMsg[5]); break; + case 27: spiRead (pin, val, auxMsg[0], (uint32_t)auxMsg[1] ); break; + case 28: addSpiListener (pin, val, auxMsg[0], (uint32_t)auxMsg[1]); break; + case 29: removeSpiListener(); break; + #endif + + // Implemented in DinoI2C.cpp + #ifdef DINO_I2C + case 31: i2cBegin (); break; + case 32: i2cEnd (); break; + case 33: i2cScan (); break; + case 34: i2cWrite (); break; + case 35: i2cRead (); break; + #endif + + // Implemented in this file. case 90: reset (); break; case 95: setAnalogDivider (); break; case 96: setAnalogResolution (); break; case 97: setAnalogDivider (); break; case 98: setHeartRate (); break; + + // Should send a "feature not implemented" message as default. default: break; } @@ -147,623 +160,67 @@ void Dino::process() { } -// WRITE CALLBACK +// Let the sketch pass a function that it wants us to use to write output. +// Store it as a callback and call that when we need to write. void Dino::setupWrite(void (*writeCallback)(char *str)) { _writeCallback = writeCallback; } + + +// Write the response variable using the callback function from the sketch. +// Always terminate with a newline. void Dino::writeResponse() { _writeCallback(response); - _writeCallback(newline); + _writeCallback("\n"); } -// LISTNENERS -void Dino::updateListeners() { - if (timeSince(lastUpdate) > heartRate || timeSince(lastUpdate) < 0) { - lastUpdate = micros(); - loopCount++; - updateDigitalListeners(); - if (loopCount % registerDivider == 0) updateRegisterListeners(); - if (loopCount % analogDivider == 0) updateAnalogListeners(); - } -} -void Dino::updateDigitalListeners() { - for (int i = 0; i < PIN_COUNT; i++) { - if (digitalListeners[i]) { - dRead(i); - if (rval != digitalListenerValues[i]) { - digitalListenerValues[i] = rval; - writeResponse(); - } - } - } -} -void Dino::updateRegisterListeners() { - for (int i = 0; i < SPI_LISTENER_COUNT; i++) { - if (spiListeners[i].enabled) { - readSPI(spiListeners[i].selectPin, - spiListeners[i].len, - spiListeners[i].spiMode, - spiListeners[i].clockRate); - } - } - for (int i = 0; i < SHIFT_LISTENER_COUNT; i++) { - if (shiftListeners[i].enabled) { - shiftRead(shiftListeners[i].latchPin, - shiftListeners[i].len, - shiftListeners[i].dataPin, - shiftListeners[i].clockPin, - shiftListeners[i].clockHighFirst); - } - } -} -void Dino::updateAnalogListeners() { - for (int i = 0; i < PIN_COUNT; i++) { - if (analogListeners[i]) { - aRead(i); - writeResponse(); - } - } -} + +// Convenience wrapper to keep track of time since last read for listeners. long Dino::timeSince(long event) { long time = micros() - event; return time; } -// API FUNCTIONS - -// CMD = 00 -// Set a pin to output (0), or input (1). -void Dino::setMode() { - if (val == 0) { - removeListener(); - pinMode(pin, OUTPUT); - } - else { - pinMode(pin, INPUT); - } - - #ifdef debug - Serial.print("Called Dino::setMode()\n"); - #endif -} - -// CMD = 01 -// Write a digital output pin. 0 for LOW, 1 or >0 for HIGH. -void Dino::dWrite() { - if (val == 0) { - digitalWrite(pin, LOW); - } - else { - digitalWrite(pin, HIGH); - } - - #ifdef debug - Serial.print("Called Dino::dWrite()\n"); - #endif -} - -// CMD = 02 -// Read a digital input pin. 0 for LOW, 1 for HIGH. -void Dino::dRead(int pin) { - rval = digitalRead(pin); - sprintf(response, "%d:%d", pin, rval); - - #ifdef debug - Serial.print("Called Dino::dRead()\n"); - #endif -} - -// CMD = 03 -// Write an analog output pin. 0 for LOW, up to 255 for HIGH @ 8-bit resolution. -void Dino::aWrite() { - analogWrite(pin,val); - - #ifdef debug - Serial.print("Called Dino::aWrite()\n"); - #endif -} - -// CMD = 04 -// Read an analog input pin. 0 for LOW, up to 1023 for HIGH @ 10-bit resolution. -void Dino::aRead(int pin) { - rval = analogRead(pin); - sprintf(response, "%d:%d", pin, rval); - - #ifdef debug - Serial.print("Called Dino::aRead()\n"); - #endif -} - -// CMD = 05 -// Set a flag to periodically read a digital pin without further requests. -// Runs around other requests in the main loop. Only sends value when changed. -void Dino::addDigitalListener() { - removeListener(); - digitalListeners[pin] = true; - digitalListenerValues[pin] = 2; - - #ifdef debug - Serial.print("Called Dino::addDigitalListener()\n"); - #endif -} - -// CMD = 06 -// Set a flag to periodically read an analog pin without further requests. -// Runs around other requests in the main loop. Sends value on every read. -void Dino::addAnalogListener() { - removeListener(); - analogListeners[pin] = true; - - #ifdef debug - Serial.print("Called Dino::addAnalogListener()\n"); - #endif -} - -// CMD = 07 -// Remove analog and digital listen flags on the pin. -void Dino::removeListener() { - analogListeners[pin] = false; - digitalListeners[pin] = false; - - #ifdef debug - Serial.print("Called Dino::removeListener()\n"); - #endif -} +// Every heartRate microseconds, read the digital listeners and send values if changed. +// Analog listeners use a divider to run at a fraction of that frequency. +// Register listeners (Shift, I2C and SPI) all share a second divider value. +// Analog and register listeners always send values even if not changed. +// See Dino::reset for default timings. +void Dino::updateListeners() { + if (timeSince(lastUpdate) > heartRate || timeSince(lastUpdate) < 0) { + // Digital Listeners + lastUpdate = micros(); + loopCount++; + updateDigitalListeners(); -// CMD = 08 -// Attach the servo object to pin or detach it. -void Dino::servoToggle() { - if (val == 0) { - #ifdef debug - Serial.print("Detaching servo"); Serial.print(" on pin "); Serial.println(pin); + // Register Listeners + #ifdef DINO_SHIFT + if (loopCount % registerDivider == 0) updateShiftListeners(); #endif - servos[pin - SERVO_OFFSET].detach(); - } - else { - #ifdef debug - Serial.print("Attaching servo"); Serial.print(" on pin "); Serial.println(pin); + #ifdef DINO_SPI + if (loopCount % registerDivider == 0) updateSpiListeners(); #endif - servos[pin - SERVO_OFFSET].attach(pin); - } -} - -// CMD = 09 -// Write a value to the servo object. -void Dino::servoWrite() { - #ifdef debug - Serial.print("Servo write "); Serial.print(val); Serial.print(" to pin "); Serial.println(pin); - #endif - servos[pin - SERVO_OFFSET].write(val); -} - -// CMD = 10 -// Write a value to the servo object. -void Dino::handleLCD() { - #ifdef debug - Serial.print("DinoLCD command: "); Serial.print(val); Serial.print(" with data: "); Serial.println(auxMsg); - #endif - dinoLCD.process(val, auxMsg); -} - - -// CMD = 12 -// Control the SoftwareSerial. -void Dino::handleSerial() { - #ifdef debug - Serial.print("DinoSerial command: "); Serial.print(val); Serial.print(" with data: "); Serial.println(auxMsg); - #endif - // SoftwareSerial doesn't work on the Due yet. - #if !defined(__SAM3X8E__) - dinoSerial.process(val, auxMsg); - #endif -} - - -// CMD = 13 -// Read a DHT sensor -void Dino::handleDHT() { - #ifdef debug - Serial.print("DinoDHT command: "); Serial.print(val); Serial.print(" with data: "); Serial.println(auxMsg); - #endif - // dtostrf doesn't work on the Due yet. - #if !defined(__SAM3X8E__) - if (pin != dht.pin) dht.setup(pin); - float reading; - char readingBuff[10]; - char prefix; - if (val == 0) { - reading = dht.getTemperature(); - prefix = 'T'; - } else { - reading = dht.getHumidity(); - prefix = 'H'; - } - if (! isnan(reading)) { - dtostrf(reading, 6, 4, readingBuff); - sprintf(response, "%d:%c%s", pin, prefix, readingBuff); - } - #endif -} - - -// CMD = 15 -void Dino::ds18Read() { - OneWire ds(pin); - - byte data[12]; - byte addr[8]; - - if ( !ds.search(addr)) { - ds.reset_search(); - return; - } - if ( OneWire::crc8( addr, 7) != addr[7]) { - // Serial.println("CRC is not valid!"); - return; - } - - if ( addr[0] != 0x10 && addr[0] != 0x28) { - // Serial.print("Device is not recognized"); - return; - } - - ds.reset(); - ds.select(addr); - ds.write(0x44,1); // start conversion, with parasite power on at the end - - byte present = ds.reset(); - ds.select(addr); - ds.write(0xBE); // Read Scratchpad - - for (int i = 0; i < 9; i++) { // we need 9 bytes - data[i] = ds.read(); - } - - ds.reset_search(); - - byte MSB = data[1]; - byte LSB = data[0]; - - float tempRead = ((MSB << 8) | LSB); //using two's compliment - float reading = tempRead / 16; - char readingBuff[10]; - - if (! isnan(reading)) { - dtostrf(reading, 6, 4, readingBuff); - sprintf(response, "%d:%s", pin, readingBuff); - } -} - - -// CMD = 16 -void Dino::irSend(){ - irsend.sendRaw((uint16_t)&auxMsg[1], (uint8_t)auxMsg[0], val); -} - -// CMD = 20 -void Dino::tone() { - //unsigned int duration = atoi(auxMsg); - //::tone(pin, val, duration); -} - -// CMD = 21 -void Dino::noTone() { - //::noTone(pin); -} - - -// CMD = 22 -// Write to a shift register. -void Dino::shiftWrite(int latchPin, int len, byte dataPin, byte clockPin, byte *data) { - // Set latch pin low to begin serial write. - digitalWrite(latchPin, LOW); - - // Write one byte at a time. - for (uint8_t i = 0; i < len; i++) { - shiftOut(dataPin, clockPin, LSBFIRST, data[i]); - } - - // Set latch pin high so register writes to parallel output. - digitalWrite(latchPin, HIGH); -} - - -// CMD = 23 -// Read from a shift register. -void Dino::shiftRead(int latchPin, int len, byte dataPin, byte clockPin, byte clockHighFirst) { - // Send clock pin high if using a register that clocks on rising edges. - // If not, the MSB will not be read on those registers (always 1), - // and all other bits will be shifted by 1 towards the LSB. - if (clockHighFirst > 0) digitalWrite(clockPin, HIGH); - - // Latch high to read parallel state, then low again to stop. - digitalWrite(latchPin, HIGH); - digitalWrite(latchPin, LOW); - - // Send data as if coming from the latch pin so it's easy to identify. - // Start with just pin number and : for now. - sprintf(response, "%d:", latchPin); - _writeCallback(response); - - for (int i = 1; i <= len; i++) { - // Read a single byte from the register. - byte reading = shiftIn(dataPin, clockPin, LSBFIRST); - - // If we're on the last byte, append \n. If not, append a comma, then write. - if (i == len) { - sprintf(response, "%d\n", reading); - } else { - sprintf(response, "%d,", reading); - } - _writeCallback(response); - } - - // Leave latch pin high and clear response so main loop doesn't send anything. - digitalWrite(latchPin, HIGH); - response[0] = '\0'; -} - - -// CMD = 24 -// Write to an SPI device. -void Dino::writeSPI(int selectPin, int len, byte spiMode, uint32_t clockRate, byte *data) { - // Start the SPI library if it isn't already being used by the main sketch. - SPI.begin(); - - // Set the mode we want. - switch(spiMode) { - case 0: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE0)); break; - case 1: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE1)); break; - case 2: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE2)); break; - case 3: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE3)); break; - } - - // Select the device. - digitalWrite(selectPin, LOW); - - // Write one byte at a time. - for (uint8_t i = 0; i < len; i++) { - SPI.transfer(data[i]); - } - - // End the SPI transaction, and then library if not in use by main sketch. - SPI.endTransaction(); - - // TXRX_SPI is set to false in Dino.h. - // CLI generator will auto set to true for any sketch other than serial. - #if !(TXRX_SPI) - SPI.end(); - #endif - - // Leave select high. - digitalWrite(selectPin, HIGH); -} - - -// CMD = 25 -// Read from an SPI device. -void Dino::readSPI(int selectPin, int len, byte spiMode, uint32_t clockRate) { - // Start the SPI library if it isn't already being used by the main sketch. - SPI.begin(); - - // Set the mode we want. - switch(spiMode) { - case 0: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE0)); break; - case 1: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE1)); break; - case 2: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE2)); break; - case 3: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE3)); break; - } - - // Select the device. - digitalWrite(selectPin, LOW); - - // Send data as if coming from the slave select pin so it's easy to identify. - // Start with just pin number and : for now. - sprintf(response, "%d:", selectPin); - _writeCallback(response); - - for (int i = 1; i <= len; i++) { - // Read a single byte from the register. - byte reading = SPI.transfer(0x00); - - // If we're on the last byte, append \n. If not, append a comma, then write. - if (i == len) { - sprintf(response, "%d\n", reading); - } else { - sprintf(response, "%d,", reading); - } - _writeCallback(response); - } - - // End the SPI transaction, and then library if not in use by main sketch. - SPI.endTransaction(); - - // TXRX_SPI is set to false in Dino.h. - // CLI generator will auto set to true for any sketch other than serial. - #if !(TXRX_SPI) - SPI.end(); - #endif - - // Leave select high and clear response so main loop doesn't send anything. - digitalWrite(selectPin, HIGH); - response[0] = '\0'; -} - -// CMD = 26 -// Start listening to a register using the Arduino shiftIn function. -// Overwrite the first disabled listener in the struct array. -void Dino::addShiftListener(int latchPin, int len, byte dataPin, byte clockPin, byte clockHighFirst) { - for (int i = 0; i < SHIFT_LISTENER_COUNT; i++) { - if (shiftListeners[i].enabled == false) { - shiftListeners[i] = { - latchPin, - len, - dataPin, - clockPin, - clockHighFirst, - true - }; - return; - } else { - // Should send some kind of error if all are in use. - } - } -} - -// CMD = 26 -// Start listening to an SPI register. -// Overwrite the first disabled listener in the struct array. -void Dino::addSPIListener(int selectPin, int len, byte spiMode, uint32_t clockRate) { - for (int i = 0; i < SPI_LISTENER_COUNT; i++) { - if (spiListeners[i].enabled == false) { - spiListeners[i] = { - selectPin, - len, - spiMode, - clockRate, - true - }; - return; - } else { - // Should send some kind of error if all are in use. - } - } -} - - -// CMD = 27 -// Send a number for a select/latch pin to remove either type of register listener. -void Dino::removeRegisterListener() { - for (int i = 0; i < SHIFT_LISTENER_COUNT; i++) { - if (shiftListeners[i].latchPin == pin) { - shiftListeners[i].enabled = false; - } - } - for (int i = 0; i < SPI_LISTENER_COUNT; i++) { - if (spiListeners[i].selectPin == pin) { - spiListeners[i].enabled = false; - } - } -} - - -// CMD = 30 -// Start I2C communication. -void Dino::i2cBegin() { - I2c.begin(); - // I2c.setSpeed(??); - // I2c.pullup(??); - // I2c.timeOut(??); - sprintf(response, "%d:I2C:1", SDA); -} - - -// CMD = 31 -// Stop I2C communication. -void Dino::i2cEnd() { - I2c.end(); - sprintf(response, "%d:I2C:0", SDA); -} - - -// CMD = 32 -// Scan for I2C devices. -// -// WARNING: This takes a long time! Try to record the device addresses -// results and put them into your code. -// -// Returns each found address as if a separate reading from SDA pin, eg. "18:104". -// Returns 128 as if read from SDA pin for search complete, eg. "18:128". -// Returns 255 as if read from SDA pin for I2C errors, eg. "18:255". -// -void Dino::i2cScan() { - uint8_t address = 0; - while (address < 128) { - // Scan for the next device. - address = I2c.scanOne(address); - - // Write whatever we get including address space end or errors. - sprintf(response, "%d:%d", SDA, address); - writeResponse(); - address++; - } - // Clear the response to make sure it doesn't get sent twice. - response[0] = '\0'; -} - - -// CMD = 33 -// Write to an I2C device. -// All parameters need to be sent in binary in the auxMsg. -// -// auxMsg[0] = device address -// auxMsg[1] = register start address -// auxMsg[2] = number of bytes -// auxMsg[3]+ = data -// -// Limited to 255 bytes. Validate on remote end. -// -void Dino::i2cWrite() { - I2c.write(auxMsg[0], auxMsg[1], &auxMsg[3], auxMsg[2]); -} - - -// CMD = 34 -// Read from an I2C device. -// All params need to be sent in binary in the auxMsg. -// -// auxMsg[0] = device address -// auxMsg[1] = register start address -// auxMsg[2] = number of bytes -// -// Streams data back in comma delimited ASCII decimal for now, -// matching shiftRead and readSPI. Limited to 32 bytes by I2C library buffer. -// Validate on remote end. -// -void Dino::i2cRead() { - // Force length to be min 1, max 32. - if (auxMsg[2] > 32) auxMsg[2] = 32; - if (auxMsg[2] == 0) auxMsg[2] = 1; - - // Read all the bytes into the library buffer. - I2c.read(auxMsg[0], auxMsg[1], auxMsg[2]); - - // Send back the SDA pin, the device address, and start register address first. - sprintf(response, "%d:%d:%d:", SDA, auxMsg[0], auxMsg[1]); - _writeCallback(response); - - // Send back the data bytes. - uint8_t currentByte = 0; - while(I2c.available()){ - currentByte++; - // Append comma, but \n for last byte, then write. - if (currentByte == auxMsg[2]){ - sprintf(response, "%d\n", I2c.receive()); - } else { - sprintf(response, "%d,", I2c.receive()); - } - _writeCallback(response); + // Analog Listeners + if (loopCount % analogDivider == 0) updateAnalogListeners(); } - - // Clear the response to make sure it doesn't get sent twice. - response[0] = '\0'; } // CMD = 90 void Dino::reset() { - // Clear the analog and digital pin listeners. - for (int i = 0; i < PIN_COUNT; i++) digitalListeners[i] = false; - for (int i = 0; i < PIN_COUNT; i++) digitalListenerValues[i] = 2; - for (int i = 0; i < PIN_COUNT; i++) analogListeners[i] = false; - - // Disable the register listeners. - for (int i = 0; i < SHIFT_LISTENER_COUNT; i++) shiftListeners[i].enabled = false; - for (int i = 0; i < SPI_LISTENER_COUNT; i++) spiListeners[i].enabled = false; + // Disable all the types of listeners. + clearDigitalListeners(); + clearAnalogListeners(); + #ifdef DINO_SPI + clearSpiListeners(); + #endif + #ifdef DINO_SHIFT + clearShiftListeners(); + #endif - heartRate = 4000; // Update digital listeners every ~4ms. + heartRate = 4000; // Update digital listeners every ~4ms. analogDivider = 4; // Update analog listeners every ~16ms. registerDivider = 2; // Update register listeners every ~8ms. fragmentIndex = 0; @@ -778,12 +235,17 @@ void Dino::reset() { #endif } + // CMD = 95 // Set the register read divider. Powers of 2 up to 128 are valid. void Dino::setRegisterDivider() { registerDivider = val; + #ifdef debug + Serial.print("Called Dino::setRegisterDivider()\n"); + #endif } + // CMD = 96 // Set the analog read and write resolution. void Dino::setAnalogResolution() { @@ -791,16 +253,27 @@ void Dino::setAnalogResolution() { analogReadResolution(val); analogWriteResolution(val); #endif + #ifdef debug + Serial.print("Called Dino::setAnalogResolution()\n"); + #endif } + // CMD = 97 // Set the analog divider. Powers of 2 up to 128 are valid. void Dino::setAnalogDivider() { analogDivider = val; + #ifdef debug + Serial.print("Called Dino::setAnalogDivider()\n"); + #endif } + // CMD = 98 // Set the heart rate in milliseconds. Store it in microseconds. void Dino::setHeartRate() { - heartRate = atoi(auxMsg); + heartRate = atoi((char *)auxMsg); + #ifdef debug + Serial.print("Called Dino::setHeartRate()\n"); + #endif } diff --git a/src/lib/Dino.h b/src/lib/Dino.h index 96b549ab..36ffb57d 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -1,45 +1,38 @@ /* Library for dino ruby gem. */ - #ifndef Dino_h #define Dino_h -#define TXRX_SPI false - -#include "Arduino.h" -#include -#include -#include "DinoLCD.h" -#include "DHT.h" -#include "OneWire.h" -#include "IRremote.h" -#include "I2C.h" - -// SoftwareSerial doesn't work on the Due yet. -#if !defined(__SAM3X8E__) - #include "DinoSerial.h" -#endif +#include +// If using Wi-Fi or Ethernet shield, uncomment this to let the SPI library know. +// #define TXRX_SPI -// Allocate listener storage based on what board we're running. +// Uncomment this line to enable debugging mode. +// #define debug + +// Comment these out to exclude default features. +// Arduino Due cannot use: SERIAL, IR, TONE, I2C +#define DINO_SERVO +#define DINO_LCD +#define DINO_SERIAL +#define DINO_DHT +#define DINO_ONE_WIRE +#define DINO_IR_OUT +// #define DINO_TONE +#define DINO_SHIFT +#define DINO_SPI +#define DINO_I2C + +// Figure out how many pins our hardware has. #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) # define PIN_COUNT 70 -# define SERVO_OFFSET 22 #elif defined(__SAM3X8E__) # define PIN_COUNT 66 -# define SERVO_OFFSET 22 #else # define PIN_COUNT 22 -# define SERVO_OFFSET 2 #endif -// Allocate listener storage for serial registers. -#define SPI_LISTENER_COUNT 4 -#define SHIFT_LISTENER_COUNT 4 - -// Uncomment this line to enable debugging mode. -// #define debug true - class Dino { public: Dino(); @@ -48,45 +41,78 @@ class Dino { void updateListeners(); private: - // API-accessible functions. + // Functions with a cmd value can be called through the remote API. + + // See explanation at top of DinoBugWorkaround.cpp + void bugWorkaround(); + + // Core IO Functions void setMode (); //cmd = 0 void dWrite (); //cmd = 1 void dRead (int pin); //cmd = 2 void aWrite (); //cmd = 3 void aRead (int pin); //cmd = 4 + + // Core IO Listeners void addDigitalListener (); //cmd = 5 void addAnalogListener (); //cmd = 6 void removeListener (); //cmd = 7 + void updateDigitalListeners(); + void updateAnalogListeners (); + void clearDigitalListeners (); + void clearAnalogListeners (); + // Listener State and Storage + // Array indices mapped to board pins. true = listener enabled on that pin. + // Cache last value for digital listeners and only send on change. + boolean analogListeners[PIN_COUNT]; + boolean digitalListeners[PIN_COUNT]; + byte digitalListenerValues[PIN_COUNT]; + + // Included Libraries void servoToggle (); //cmd = 8 void servoWrite (); //cmd = 9 void handleLCD (); //cmd = 10 - void shiftWrite (); //cmd = 11 void handleSerial (); //cmd = 12 - void handleDHT (); //cmd = 13 + void dhtRead (); //cmd = 13 void ds18Read (); //cmd = 15 void irSend (); //cmd = 16 - void tone (); //cmd = 20 - void noTone (); //cmd = 21 - void shiftWrite (int latchPin, int len, byte dataPin, byte clockPin, byte *data); //cmd = 22 - void shiftRead (int latchPin, int len, byte dataPin, byte clockPin, byte clockHighFirst); //cmd = 23 - void writeSPI (int selectPin, int len, byte spiMode, uint32_t clockRate, byte *data); //cmd = 24 - void readSPI (int selectPin, int len, byte spiMode, uint32_t clockRate); //cmd = 25 - void addShiftListener (int latchPin, int len, byte dataPin, byte clockPin, byte clockHighFirst); //cmd = 26 - void addSPIListener (int selectPin, int len, byte spiMode, uint32_t clockRate); //cmd = 27 - void removeRegisterListener(); //cmd = 28 - void i2cBegin (); //cmd = 30 - void i2cEnd (); //cmd = 31 - void i2cScan (); //cmd = 32 - void i2cWrite (); //cmd = 33 - void i2cRead (); //cmd = 34 + void tone (); //cmd = 17 + void noTone (); //cmd = 18 + + // Shift Registers + void shiftWrite (int latchPin, int len, byte dataPin, byte clockPin, byte *data); //cmd = 21 + void shiftRead (int latchPin, int len, byte dataPin, byte clockPin, byte clockHighFirst); //cmd = 22 + void addShiftListener (int latchPin, int len, byte dataPin, byte clockPin, byte clockHighFirst); //cmd = 23 + void removeShiftListener (); //cmd = 24 + void updateShiftListeners (); + void clearShiftListeners (); + + // SPI + void spiBegin (byte spiMode, uint32_t clockRate); + void spiEnd (); + void spiWrite (int selectPin, int len, byte spiMode, uint32_t clockRate, byte *data); //cmd = 26 + void spiRead (int selectPin, int len, byte spiMode, uint32_t clockRate); //cmd = 27 + void addSpiListener (int selectPin, int len, byte spiMode, uint32_t clockRate); //cmd = 28 + void removeSpiListener (); //cmd = 29 + void updateSpiListeners (); + void clearSpiListeners (); + + // I2C + void i2cBegin (); //cmd = 31 + void i2cEnd (); //cmd = 32 + void i2cScan (); //cmd = 33 + void i2cWrite (); //cmd = 34 + void i2cRead (); //cmd = 35 + + // API access to timings, resolutions and reset. void reset (); //cmd = 90 void setRegisterDivider (); //cmd = 97 void setAnalogResolution (); //cmd = 96 void setAnalogDivider (); //cmd = 97 void setHeartRate (); //cmd = 98 - + // Parser state storage and utility functions. - char *messageFragments[4]; + byte *messageFragments[4]; byte fragmentIndex; int charIndex; boolean backslash; @@ -94,23 +120,28 @@ class Dino { void process(); // Parsed message storage. - char cmdStr[5]; int cmd; - char pinStr[5]; int pin; - char valStr[5]; int val; - byte auxMsg[512]; + byte cmdStr[5]; int cmd; + byte pinStr[5]; int pin; + byte valStr[5]; int val; + // Scale aux message allocation based on enabled features. + #if defined(DINO_IR_OUT) + byte auxMsg[528]; + #elif defined(DINO_SHIFT) || defined(DINO_SPI) || defined (DINO_I2C) + byte auxMsg[272]; + #elif defined (DINO_LCD) + byte auxMsg[144]; + #else + byte auxMsg[48]; + #endif // Value and response storage. int rval; char response[16]; - char newline[2]; // Use a write callback from the main sketch to respond. void (*_writeCallback)(char *str); void writeResponse(); - // Arduino native library variables. - Servo servos[12]; - // Internal timing variables and utility functions. long heartRate; long lastUpdate; @@ -118,38 +149,5 @@ class Dino { unsigned int analogDivider; unsigned int registerDivider; long timeSince (long event); - - // Listeners correspond to raw pin number by array index, and store boolean. false == disabled. - boolean analogListeners[PIN_COUNT]; - boolean digitalListeners[PIN_COUNT]; - - // Create listeners for SPI registers. - struct spiListener{ - byte selectPin; - byte len; - byte spiMode; - uint32_t clockRate; - boolean enabled; - }; - spiListener spiListeners[SPI_LISTENER_COUNT]; - - // Create listeners for ShiftIn registers. - struct shiftListener{ - byte latchPin; - byte len; - byte dataPin; - byte clockPin; - byte clockHighFirst; - boolean enabled; - }; - shiftListener shiftListeners[SHIFT_LISTENER_COUNT]; - - // Keep track of the last read values for digital listeners. Only write responses when changed. - byte digitalListenerValues[PIN_COUNT]; - - // Listener update functions. - void updateDigitalListeners (); - void updateRegisterListeners (); - void updateAnalogListeners (); }; #endif diff --git a/src/lib/DinoBugWorkaround.cpp b/src/lib/DinoBugWorkaround.cpp new file mode 100644 index 00000000..cd7dc5e3 --- /dev/null +++ b/src/lib/DinoBugWorkaround.cpp @@ -0,0 +1,24 @@ +// +// After refactoring there's a strange gcc bug referencing the first +// instance of sprintf in Dino.cpp, only when compiling for the 328p/Uno/Nano/Mini. +// +// It only occurs when trying to exlude all the new .cpp files by commenting +// out the #define lines at the top of Dino.h. At least one needed to be left in +// to avoid the error. +// +// Narrowed it down to the following: +// Must declare a function in a separate .cpp file. +// That function must mutate some instance variable in Dino. +// That function must be one of the switch options in the Dino::process case statement. +// +// This file is a stub to meet those requirements. +// Leaving this in until sprintf usage is replaced or bug is fixed. +// +// Similar issues: +// https://github.com/arduino/Arduino/issues/3972 +// https://github.com/CongducPham/LowCostLoRaGw/issues/28 +// +#include "Dino.h" +void Dino::bugWorkaround() { + rval = 0; +} diff --git a/src/lib/DinoCoreIO.cpp b/src/lib/DinoCoreIO.cpp new file mode 100644 index 00000000..a18ecf94 --- /dev/null +++ b/src/lib/DinoCoreIO.cpp @@ -0,0 +1,136 @@ +#include "Dino.h" + +// CMD = 00 +// Set a pin to output (0), or input (1). +void Dino::setMode() { + if (val == 0) { + removeListener(); + pinMode(pin, OUTPUT); + } + else { + pinMode(pin, INPUT); + } + + #ifdef debug + Serial.print("Called Dino::setMode()\n"); + #endif +} + +// CMD = 01 +// Write a digital output pin. 0 for LOW, 1 or >0 for HIGH. +void Dino::dWrite() { + if (val == 0) { + digitalWrite(pin, LOW); + } + else { + digitalWrite(pin, HIGH); + } + + #ifdef debug + Serial.print("Called Dino::dWrite()\n"); + #endif +} + +// CMD = 02 +// Read a digital input pin. 0 for LOW, 1 for HIGH. +void Dino::dRead(int pin) { + rval = digitalRead(pin); + sprintf(response, "%d:%d", pin, rval); + + #ifdef debug + Serial.print("Called Dino::dRead()\n"); + #endif +} + +// CMD = 03 +// Write an analog output pin. 0 for LOW, up to 255 for HIGH @ 8-bit resolution. +void Dino::aWrite() { + analogWrite(pin,val); + + #ifdef debug + Serial.print("Called Dino::aWrite()\n"); + #endif +} + +// CMD = 04 +// Read an analog input pin. 0 for LOW, up to 1023 for HIGH @ 10-bit resolution. +void Dino::aRead(int pin) { + rval = analogRead(pin); + sprintf(response, "%d:%d", pin, rval); + + #ifdef debug + Serial.print("Called Dino::aRead()\n"); + #endif +} + +// CMD = 05 +// Set a flag to periodically read a digital pin without further requests. +// Runs around other requests in the main loop. Only sends value when changed. +void Dino::addDigitalListener() { + removeListener(); + digitalListeners[pin] = true; + digitalListenerValues[pin] = 2; + + #ifdef debug + Serial.print("Called Dino::addDigitalListener()\n"); + #endif +} + +// CMD = 06 +// Set a flag to periodically read an analog pin without further requests. +// Runs around other requests in the main loop. Sends value on every read. +void Dino::addAnalogListener() { + removeListener(); + analogListeners[pin] = true; + + #ifdef debug + Serial.print("Called Dino::addAnalogListener()\n"); + #endif +} + +// CMD = 07 +// Remove analog and digital listen flags on the pin. +void Dino::removeListener() { + analogListeners[pin] = false; + digitalListeners[pin] = false; + + #ifdef debug + Serial.print("Called Dino::removeListener()\n"); + #endif +} + +// Gets called by Dino::updateListeners to run listeners in the main loop. +// Reads each digital listen pin and sends value if changed from last read. +void Dino::updateDigitalListeners() { + for (int i = 0; i < PIN_COUNT; i++) { + if (digitalListeners[i]) { + dRead(i); + if (rval != digitalListenerValues[i]) { + digitalListenerValues[i] = rval; + writeResponse(); + } + } + } +} + +// Gets called by Dino::updateListeners to run listeners in the main loop. +// Reads each analog listen pin and sends value always. Does not store last value. +void Dino::updateAnalogListeners() { + for (int i = 0; i < PIN_COUNT; i++) { + if (analogListeners[i]) { + aRead(i); + writeResponse(); + } + } +} + +// Gets called by Dino::reset to clear all digital listeners. +void Dino::clearDigitalListeners(){ + for (int i = 0; i < PIN_COUNT; i++) digitalListeners[i] = false; + for (int i = 0; i < PIN_COUNT; i++) digitalListenerValues[i] = 2; +} + +// Gets called by Dino::reset to clear all analog listeners. +void Dino::clearAnalogListeners(){ + for (int i = 0; i < PIN_COUNT; i++) analogListeners[i] = false; +} diff --git a/src/lib/DinoDHT.cpp b/src/lib/DinoDHT.cpp new file mode 100644 index 00000000..43b56090 --- /dev/null +++ b/src/lib/DinoDHT.cpp @@ -0,0 +1,41 @@ +// +// This file adds to the Dino class only if DINO_DHT is defined in Dino.h. +// Should not be included in sketch for Arduino Due yet. +// +#include "Dino.h" +#ifdef DINO_DHT + +// Include dtostrf for ARM. +#if defined(__SAM3X8E__) + #include +#endif + +// Include the DHT library and create an instance. +#include "DHT.h" +DHT dht; + +// CMD = 13 +// Read a DHT sensor +void Dino::dhtRead() { + if (pin != dht.pin) dht.setup(pin); + float reading; + char readingBuff[10]; + char prefix; + if (val == 0) { + reading = dht.getTemperature(); + prefix = 'T'; + } else { + reading = dht.getHumidity(); + prefix = 'H'; + } + if (! isnan(reading)) { + dtostrf(reading, 6, 4, readingBuff); + sprintf(response, "%d:%c%s", pin, prefix, readingBuff); + } + + #ifdef debug + Serial.print("Called Dino::dhtRead()\n"); + #endif +} + +#endif diff --git a/src/lib/DinoI2C.cpp b/src/lib/DinoI2C.cpp new file mode 100644 index 00000000..f808f000 --- /dev/null +++ b/src/lib/DinoI2C.cpp @@ -0,0 +1,112 @@ +// +// This file adds to the Dino class only if DINO_I2C is defined in Dino.h. +// +#include "Dino.h" +#ifdef DINO_I2C + +#include "I2C.h" + + +// CMD = 31 +// Start I2C communication. +void Dino::i2cBegin() { + I2c.begin(); + // I2c.setSpeed(??); + // I2c.pullup(??); + // I2c.timeOut(??); + sprintf(response, "%d:I2C:1", SDA); +} + + +// CMD = 32 +// Stop I2C communication. +void Dino::i2cEnd() { + I2c.end(); + sprintf(response, "%d:I2C:0", SDA); +} + + +// CMD = 33 +// Scan for I2C devices. +// +// WARNING: This takes a long time! Try to record the device addresses +// results and put them into your code. +// +// Returns each found address as if a separate reading from SDA pin, eg. "18:104". +// Returns 128 as if read from SDA pin for search complete, eg. "18:128". +// Returns 255 as if read from SDA pin for I2C errors, eg. "18:255". +// +void Dino::i2cScan() { + uint8_t address = 0; + while (address < 128) { + // Scan for the next device. + address = I2c.scanOne(address); + + // Write whatever we get including address space end or errors. + sprintf(response, "%d:%d", SDA, address); + writeResponse(); + address++; + } + // Clear the response to make sure it doesn't get sent twice. + response[0] = '\0'; +} + + +// CMD = 34 +// Write to an I2C device. +// All parameters need to be sent in binary in the auxMsg. +// +// auxMsg[0] = device address +// auxMsg[1] = register start address +// auxMsg[2] = number of bytes +// auxMsg[3]+ = data +// +// Limited to 255 bytes. Validate on remote end. +// +void Dino::i2cWrite() { + I2c.write(auxMsg[0], auxMsg[1], &auxMsg[3], auxMsg[2]); +} + + +// CMD = 35 +// Read from an I2C device. +// All params need to be sent in binary in the auxMsg. +// +// auxMsg[0] = device address +// auxMsg[1] = register start address +// auxMsg[2] = number of bytes +// +// Streams data back in comma delimited ASCII decimal for now, +// matching shiftRead and readSPI. Limited to 32 bytes by I2C library buffer. +// Validate on remote end. +// +void Dino::i2cRead() { + // Force length to be min 1, max 32. + if (auxMsg[2] > 32) auxMsg[2] = 32; + if (auxMsg[2] == 0) auxMsg[2] = 1; + + // Read all the bytes into the library buffer. + I2c.read(auxMsg[0], auxMsg[1], auxMsg[2]); + + // Send back the SDA pin, the device address, and start register address first. + sprintf(response, "%d:%d:%d:", SDA, auxMsg[0], auxMsg[1]); + _writeCallback(response); + + // Send back the data bytes. + uint8_t currentByte = 0; + while(I2c.available()){ + currentByte++; + // Append comma, but \n for last byte, then write. + if (currentByte == auxMsg[2]){ + sprintf(response, "%d\n", I2c.receive()); + } else { + sprintf(response, "%d,", I2c.receive()); + } + _writeCallback(response); + } + + // Clear the response to make sure it doesn't get sent twice. + response[0] = '\0'; +} + +#endif diff --git a/src/lib/DinoIROut.cpp b/src/lib/DinoIROut.cpp new file mode 100644 index 00000000..9ba67395 --- /dev/null +++ b/src/lib/DinoIROut.cpp @@ -0,0 +1,16 @@ +// +// This file adds to the Dino class only if DINO_IR_OUT is defined in Dino.h. +// +#include "Dino.h" +#ifdef DINO_IR_OUT + +#include "IRremote.h" +IRsend infraredOut; + +// CMD = 16 +// Send an infrared signal. +void Dino::irSend(){ + infraredOut.sendRaw((uint16_t)&auxMsg[1], (uint8_t)auxMsg[0], val); +} + +#endif diff --git a/src/lib/DinoIncludes.cpp b/src/lib/DinoIncludes.cpp new file mode 100644 index 00000000..bf36c614 --- /dev/null +++ b/src/lib/DinoIncludes.cpp @@ -0,0 +1,34 @@ +// +// This file should implement features where Dino simply passes input to classes +// that require a static instance to be defined in Dino's scope. +// +#include "Dino.h" + +#ifdef DINO_LCD + #include "DinoLCD.h" + DinoLCD dinoLCD; + + // CMD = 10 + // Pass aux message to the LCD library for processing. + void Dino::handleLCD() { + dinoLCD.process(val, (char *)auxMsg); + #ifdef debug + Serial.print("Called Dino::handleLCD()\n"); + #endif + } +#endif + + +#ifdef DINO_SERIAL + #include "DinoSerial.h" + DinoSerial dinoSerial; + + // CMD = 12 + // Pass aux message to the software serial library for processing. + void Dino::handleSerial() { + dinoSerial.process(val, auxMsg); + #ifdef debug + Serial.print("Called Dino::handleSerial()\n"); + #endif + } +#endif diff --git a/src/lib/DinoOneWire.cpp b/src/lib/DinoOneWire.cpp new file mode 100644 index 00000000..fa1e6155 --- /dev/null +++ b/src/lib/DinoOneWire.cpp @@ -0,0 +1,62 @@ +// +// This file adds to the Dino class only if DINO_ONE_WIRE is defined in Dino.h. +// +#include "Dino.h" +#ifdef DINO_ONE_WIRE + +// Include dtostrf for ARM. +#if defined(__SAM3X8E__) + #include +#endif +#include "OneWire.h" + +// CMD = 15 +void Dino::ds18Read() { + OneWire ds(pin); + + byte data[12]; + byte addr[8]; + + if ( !ds.search(addr)) { + ds.reset_search(); + return; + } + + if ( OneWire::crc8( addr, 7) != addr[7]) { + // Serial.println("CRC is not valid!"); + return; + } + + if ( addr[0] != 0x10 && addr[0] != 0x28) { + // Serial.print("Device is not recognized"); + return; + } + + ds.reset(); + ds.select(addr); + ds.write(0x44,1); // start conversion, with parasite power on at the end + + byte present = ds.reset(); + ds.select(addr); + ds.write(0xBE); // Read Scratchpad + + for (int i = 0; i < 9; i++) { // we need 9 bytes + data[i] = ds.read(); + } + + ds.reset_search(); + + byte MSB = data[1]; + byte LSB = data[0]; + + float tempRead = ((MSB << 8) | LSB); //using two's compliment + float reading = tempRead / 16; + char readingBuff[10]; + + if (! isnan(reading)) { + dtostrf(reading, 6, 4, readingBuff); + sprintf(response, "%d:%s", pin, readingBuff); + } +} + +#endif diff --git a/src/lib/DinoSPI.cpp b/src/lib/DinoSPI.cpp new file mode 100644 index 00000000..35e05c5d --- /dev/null +++ b/src/lib/DinoSPI.cpp @@ -0,0 +1,153 @@ +// +// This file adds to the Dino class only if DINO_SPI is defined in Dino.h. +// +#include "Dino.h" +#ifdef DINO_SPI + +#include + +// Define listeners for SPI registers. +#define SPI_LISTENER_COUNT 4 +struct SpiListener{ + byte selectPin; + byte len; + byte spiMode; + uint32_t clockRate; + boolean enabled; +}; +SpiListener spiListeners[SPI_LISTENER_COUNT]; + + +// Convenience wrapper for SPI.begin +void Dino::spiBegin(byte spiMode, uint32_t clockRate){ + SPI.begin(); + // Should make LSB/MSB optional. + switch(spiMode) { + case 0: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE0)); break; + case 1: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE1)); break; + case 2: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE2)); break; + case 3: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE3)); break; + } +} + + +// Convenience wrapper for SPI.end +void Dino::spiEnd(){ + SPI.endTransaction(); + // If the sketch is using SPI for TxRx (Wi-Fi/Ethernet) we don't want to end. + // CLI generator will define TXRX_SPI in Dino.h for those cases. + #ifndef TXRX_SPI + SPI.end(); + #endif +} + + +// +// Request format for single direction SPI API functions. +// pin = slave select pin (int) +// val = length (int) +// auxMsg[0] = SPI mode (byte) +// auxMsg[1-4] = clock frequency (uint32_t as 4 bytes) +// auxMsg[5]+ = data (bytes) (write func only) +// +// CMD = 26 +// Write to an SPI device. +void Dino::spiWrite(int selectPin, int len, byte spiMode, uint32_t clockRate, byte *data) { + spiBegin(spiMode, clockRate); + + // Select the device. + digitalWrite(selectPin, LOW); + + // Write one byte at a time. + for (uint8_t i = 0; i < len; i++) { + SPI.transfer(data[i]); + } + spiEnd(); + + // Leave select high. + digitalWrite(selectPin, HIGH); +} + + +// CMD = 27 +// Read from an SPI device. +void Dino::spiRead(int selectPin, int len, byte spiMode, uint32_t clockRate) { + spiBegin(spiMode, clockRate); + + // Select the device. + digitalWrite(selectPin, LOW); + + // Send data as if coming from the slave select pin so it's easy to identify. + // Start with just pin number and : for now. + sprintf(response, "%d:", selectPin); + _writeCallback(response); + + for (int i = 1; i <= len; i++) { + // Read a single byte from the register. + byte reading = SPI.transfer(0x00); + + // If we're on the last byte, append \n. If not, append a comma, then write. + if (i == len) { + sprintf(response, "%d\n", reading); + } else { + sprintf(response, "%d,", reading); + } + _writeCallback(response); + } + spiEnd(); + + // Leave select high and clear response so main loop doesn't send anything. + digitalWrite(selectPin, HIGH); + response[0] = '\0'; +} + +// CMD = 28 +// Start listening to an SPI register. +// Overwrite the first disabled listener in the struct array. +void Dino::addSpiListener(int selectPin, int len, byte spiMode, uint32_t clockRate) { + for (int i = 0; i < SPI_LISTENER_COUNT; i++) { + if (spiListeners[i].enabled == false) { + spiListeners[i] = { + selectPin, + len, + spiMode, + clockRate, + true + }; + return; + } else { + // Should send some kind of error if all are in use. + } + } +} + +// CMD = 29 +// Send a number for a select pin to remove an SPI register listener. +void Dino::removeSpiListener(){ + for (int i = 0; i < SPI_LISTENER_COUNT; i++) { + if (spiListeners[i].selectPin == pin) { + spiListeners[i].enabled = false; + } + } +} + + +// Gets called by Dino::updateListeners to run listeners in the main loop. +void Dino::updateSpiListeners(){ + for (int i = 0; i < SPI_LISTENER_COUNT; i++) { + if (spiListeners[i].enabled) { + spiRead(spiListeners[i].selectPin, + spiListeners[i].len, + spiListeners[i].spiMode, + spiListeners[i].clockRate); + } + } +} + + +// Gets called by Dino::reset to clear all listeners. +void Dino::clearSpiListeners(){ + for (int i = 0; i < SPI_LISTENER_COUNT; i++) spiListeners[i].enabled = false; +} + +#endif diff --git a/src/lib/DinoSerial.cpp b/src/lib/DinoSerial.cpp index 068344b7..8e1dfec4 100644 --- a/src/lib/DinoSerial.cpp +++ b/src/lib/DinoSerial.cpp @@ -1,6 +1,3 @@ -// SoftwareSerial doesn't work on the Due yet. -#if !defined(__SAM3X8E__) - #include "Arduino.h" #include "DinoSerial.h" @@ -41,5 +38,3 @@ void DinoSerial::begin(char *aux) { int baud = atoi(aux); softSerial.begin(baud); } - -#endif diff --git a/src/lib/DinoSerial.h b/src/lib/DinoSerial.h index 5160bc72..a4427767 100644 --- a/src/lib/DinoSerial.h +++ b/src/lib/DinoSerial.h @@ -1,6 +1,3 @@ -// SoftwareSerial doesn't work on the Due yet. -#if !defined(__SAM3X8E__) - #ifndef DinoSerial_h #define DinoSerial_h @@ -19,5 +16,3 @@ class DinoSerial { }; #endif - -#endif diff --git a/src/lib/DinoServo.cpp b/src/lib/DinoServo.cpp new file mode 100644 index 00000000..e3735af4 --- /dev/null +++ b/src/lib/DinoServo.cpp @@ -0,0 +1,73 @@ +// +// This file adds to the Dino class only if DINO_SERVO is defined in Dino.h. +// +#include "Dino.h" +#ifdef DINO_SERVO + +#include + +// Maximum 12 servos on most boards. Could be up to 48 on Arduino Mega. +#define SERVO_COUNT 12 + +// Create an array of wrapper structs that link pins to servo objects. +struct ServoWrapper{ + byte pin; + boolean active; + Servo servo; +}; +ServoWrapper servos[SERVO_COUNT]; + +// CMD = 08 +// Attach a free servo object to the specified pin. +void Dino::servoToggle() { + // Search by pin for in use servo object, detatch and set inactive. + if (val == 0) { + for (int i = 0; i < SERVO_COUNT; i++) { + if (servos[i].pin == pin) { + servos[i].servo.detach(); + servos[i].active = false; + } + } + } + // Search by pin for in use servo object, attach and set active. + else { + boolean found = false; + for (int i = 0; i < SERVO_COUNT; i++) { + if (servos[i].pin == pin) { + found = true; + servos[i].servo.attach(pin); + servos[i].active = true; + } + } + // If it doesn't exist, use the first inactive object. + if (found == false) { + for (int i = 0; i < SERVO_COUNT; i++) { + if (servos[i].active == false) { + servos[i].servo.attach(pin); + servos[i].active = true; + } + } + } + } + + #ifdef debug + Serial.print("Called Dino::servoToggle()\n"); + #endif +} + +// CMD = 09 +// Write a value to the servo object. +void Dino::servoWrite() { + // Find servo by pin and write value to it. + for (int i = 0; i < SERVO_COUNT; i++) { + if (servos[i].pin == pin) { + servos[i].servo.write(val); + } + } + + #ifdef debug + Serial.print("Called Dino::servoWrite()\n"); + #endif +} + +#endif diff --git a/src/lib/DinoShift.cpp b/src/lib/DinoShift.cpp new file mode 100644 index 00000000..f5788dae --- /dev/null +++ b/src/lib/DinoShift.cpp @@ -0,0 +1,133 @@ +// +// This file adds to the Dino class only if DINO_SHIFT is defined in Dino.h. +// +#include "Dino.h" +#ifdef DINO_SHIFT + +// Define listeners for ShiftIn registers. +#define SHIFT_LISTENER_COUNT 4 +struct shiftListener{ + byte latchPin; + byte len; + byte dataPin; + byte clockPin; + byte clockHighFirst; + boolean enabled; +}; +shiftListener shiftListeners[SHIFT_LISTENER_COUNT]; + + +// +// Request format for shift register API functions. +// pin = latch pin (int) +// val = length (int) +// auxMsg[0] = data pin (byte) +// auxMsg[1] = clock pin (byte) +// auxMsg[2] = send clock high before reading (byte) (0/1) (read func only) +// auxMsg[3]+ = data (bytes) (write func only) +// +// CMD = 21 +// Write to a shift register. +void Dino::shiftWrite(int latchPin, int len, byte dataPin, byte clockPin, byte *data) { + // Set latch pin low to begin serial write. + digitalWrite(latchPin, LOW); + + // Write one byte at a time. + for (uint8_t i = 0; i < len; i++) { + shiftOut(dataPin, clockPin, LSBFIRST, data[i]); + } + + // Set latch pin high so register writes to parallel output. + digitalWrite(latchPin, HIGH); +} + + +// CMD = 22 +// Read from a shift register. +void Dino::shiftRead(int latchPin, int len, byte dataPin, byte clockPin, byte clockHighFirst) { + // Send clock pin high if using a register that clocks on rising edges. + // If not, the MSB will not be read on those registers (always 1), + // and all other bits will be shifted by 1 towards the LSB. + if (clockHighFirst > 0) digitalWrite(clockPin, HIGH); + + // Latch high to read parallel state, then low again to stop. + digitalWrite(latchPin, HIGH); + digitalWrite(latchPin, LOW); + + // Send data as if coming from the latch pin so it's easy to identify. + // Start with just pin number and : for now. + sprintf(response, "%d:", latchPin); + _writeCallback(response); + + for (int i = 1; i <= len; i++) { + // Read a single byte from the register. + byte reading = shiftIn(dataPin, clockPin, LSBFIRST); + + // If we're on the last byte, append \n. If not, append a comma, then write. + if (i == len) { + sprintf(response, "%d\n", reading); + } else { + sprintf(response, "%d,", reading); + } + _writeCallback(response); + } + + // Leave latch pin high and clear response so main loop doesn't send anything. + digitalWrite(latchPin, HIGH); + response[0] = '\0'; +} + + +// CMD = 23 +// Start listening to a register using the Arduino shiftIn function. +// Overwrite the first disabled listener in the struct array. +void Dino::addShiftListener(int latchPin, int len, byte dataPin, byte clockPin, byte clockHighFirst) { + for (int i = 0; i < SHIFT_LISTENER_COUNT; i++) { + if (shiftListeners[i].enabled == false) { + shiftListeners[i] = { + latchPin, + len, + dataPin, + clockPin, + clockHighFirst, + true + }; + return; + } else { + // Should send some kind of error if all are in use. + } + } +} + + +// CMD = 24 +// Send a number for a latch pin to remove a shift register listener. +void Dino::removeShiftListener() { + for (int i = 0; i < SHIFT_LISTENER_COUNT; i++) { + if (shiftListeners[i].latchPin == pin) { + shiftListeners[i].enabled = false; + } + } +} + + +// Gets called by Dino::updateListeners to run listeners in the main loop. +void Dino::updateShiftListeners() { + for (int i = 0; i < SHIFT_LISTENER_COUNT; i++) { + if (shiftListeners[i].enabled) { + shiftRead(shiftListeners[i].latchPin, + shiftListeners[i].len, + shiftListeners[i].dataPin, + shiftListeners[i].clockPin, + shiftListeners[i].clockHighFirst); + } + } +} + + +// Gets called by Dino::reset to clear all listeners. +void Dino::clearShiftListeners() { + for (int i = 0; i < SHIFT_LISTENER_COUNT; i++) shiftListeners[i].enabled = false; +} + +#endif diff --git a/src/lib/DinoTone.cpp b/src/lib/DinoTone.cpp new file mode 100644 index 00000000..e15caf3f --- /dev/null +++ b/src/lib/DinoTone.cpp @@ -0,0 +1,18 @@ +// +// This file adds to the Dino class only if DINO_TONE is defined in Dino.h. +// +#include "Dino.h" +#ifdef DINO_TONE + +// CMD = 20 +void Dino::tone() { + unsigned int duration = atoi((char*)auxMsg); + ::tone(pin, val, duration); +} + +// CMD = 21 +void Dino::noTone() { + ::noTone(pin); +} + +#endif From 88d9deb71d5ef21cba8a98a596a25cdd8e287165 Mon Sep 17 00:00:00 2001 From: vickash Date: Fri, 5 Jan 2018 19:44:17 -0400 Subject: [PATCH 125/296] Map serial properly on the ATtiny85. --- src/dino_serial.ino | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dino_serial.ino b/src/dino_serial.ino index 70cf652e..9869158f 100644 --- a/src/dino_serial.ino +++ b/src/dino_serial.ino @@ -8,6 +8,8 @@ Dino dino; Serial_ &serial = SerialUSB; #elif defined(__AVR_ATmega32U4__) Serial_ &serial = Serial; +#elif defined(__AVR_ATtiny85__) + TinyDebugSerial &serial = Serial; #else HardwareSerial &serial = Serial; #endif From ab8ec6b1884deab6e61b1e276583f7005e4431f8 Mon Sep 17 00:00:00 2001 From: vickash Date: Fri, 5 Jan 2018 21:12:09 -0400 Subject: [PATCH 126/296] preclock_high only matters for shift, not SPI. --- lib/dino/components/register/input.rb | 8 -------- lib/dino/components/register/shift_in.rb | 9 +++++++++ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/dino/components/register/input.rb b/lib/dino/components/register/input.rb index 48dc4dac..58e22496 100644 --- a/lib/dino/components/register/input.rb +++ b/lib/dino/components/register/input.rb @@ -27,14 +27,6 @@ def after_initialize(options={}) @reading_pins = Array.new(@bytes*8) {|i| false} @listening_pins = Array.new(@bytes*8) {|i| false} - # - # Certain registers which use rising edges for clock signals produce - # errors with the native Arduino shiftIn function unless you set the clock - # pin high before reading. Setting this instance var to 1 will include - # that instruction on every call to read or listen. - # - @preclock_high = options[:preclock_high] ? 1 : 0 - enable_proxy end diff --git a/lib/dino/components/register/shift_in.rb b/lib/dino/components/register/shift_in.rb index 5e1aace0..a9344a63 100644 --- a/lib/dino/components/register/shift_in.rb +++ b/lib/dino/components/register/shift_in.rb @@ -20,6 +20,15 @@ class ShiftIn # def after_initialize(options={}) super(options) if defined?(super) + + # + # Certain registers which use rising edges for clock signals produce + # errors with the native Arduino shiftIn function unless you set the clock + # pin high before reading. Setting this instance var to 1 will include + # that instruction on every call to read or listen. + # + @preclock_high = options[:preclock_high] ? 1 : 0 + bubble_callbacks end From be48d68ca7a0565f1db9bb1901172c934619cb3c Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 6 Jan 2018 17:24:29 -0400 Subject: [PATCH 127/296] More useful info and errors during connect/handshake --- lib/dino/tx_rx/base.rb | 71 ++++++++++++++++++++--------------- lib/dino/tx_rx/serial.rb | 27 +++++++++++-- spec/lib/tx_rx/serial_spec.rb | 8 ++-- spec/lib/tx_rx/tcp_spec.rb | 4 +- 4 files changed, 69 insertions(+), 41 deletions(-) diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index b38486d8..0ea2c49e 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -3,11 +3,16 @@ module Dino module TxRx - class BoardNotFound < StandardError; end + class SerialConnectError < StandardError; end + class TCPConnectError < StandardError; end + class HandshakeError < StandardError; end + class RxFlushTimeout < StandardError; end class Base include Observable BOARD_BUFFER = 60 + HANDSHAKE_TRIES = 3 + HANDSHAKE_TIMEOUT = 2 def io @io ||= connect @@ -30,29 +35,38 @@ def write(message) def handshake initialize_flow_control flush_read - 10.times do |retries| - begin - Timeout.timeout(1) do - write Dino::Message.encode(command: 90) - loop do - line = gets - if line && line.match(/ACK:/) - flush_read - ignore_retry_bytes(retries) - puts "Connected to board..." - return line.split(/:/)[1] - end + HANDSHAKE_TRIES.times do |retries| + Timeout.timeout(HANDSHAKE_TIMEOUT) do + print "Sending handshake to: #{self.to_s}... " + write Dino::Message.encode(command: 90) + loop do + line = gets + if line && line.match(/\AACK:/) + flush_read + ignore_retry_bytes(retries) + puts "Acknowledged. Hardware ready...\n\n" + return line.split(":", 2)[1] end end - rescue Timeout::Error - puts "Could not find board. Retrying..." end + rescue Timeout::Error + print "No response, " + puts (retries + 1 < HANDSHAKE_TRIES ? "retrying..." : "exiting...") + next end - raise BoardNotFound + raise HandshakeError, "Connected to wrong device, or device not running dino" end private + def flush_read + Timeout.timeout(5) do + gets until gets == nil + end + rescue Timeout::Error + raise RxFlushTimeout "Cannot read from device, or device not running dino" + end + def synced_write(message) message = message.split("") loop do @@ -71,7 +85,9 @@ def synced_write(message) end end - def io_write(message); raise "#io_write should be defined in TxRx subclasses"; end + def connect(message); raise "#connect should be defined in TxRx subclasses"; end + def gets(message); raise "#gets should be defined in TxRx subclasses"; end + def _write(message); raise "#_write should be defined in TxRx subclasses"; end def _read line = gets @@ -90,6 +106,13 @@ def process_line(line) end end + # Subtract bytes for failed handshakes from the total in transit bytes. + # The board likely reset, dropping these bytes, and causing the retries. + def ignore_retry_bytes(retries) + retry_bytes = Dino::Message.encode(command: 90).length * retries + remove_transit_bytes(retry_bytes) + end + def initialize_flow_control @flow_control ||= Mutex.new @write_mutex ||= Mutex.new @@ -107,20 +130,6 @@ def add_transit_bytes(value) def remove_transit_bytes(value) @flow_control.synchronize { @transit_bytes = @transit_bytes - value } end - - # Subtract bytes for failed handshakes from the total in transit bytes. - # The board will never acknowledge these. - def ignore_retry_bytes(retries) - retry_bytes = Dino::Message.encode(command: 90).length * retries - remove_transit_bytes(retry_bytes) - end - - def connect(message); raise "#connect should be defined in TxRx subclasses"; end - def gets(message); raise "#gets should be defined in TxRx subclasses"; end - - def flush_read - gets until gets == nil - end end end end diff --git a/lib/dino/tx_rx/serial.rb b/lib/dino/tx_rx/serial.rb index f61a6363..6ddd4acb 100644 --- a/lib/dino/tx_rx/serial.rb +++ b/lib/dino/tx_rx/serial.rb @@ -10,12 +10,31 @@ def initialize(options={}) @baud = options[:baud] || BAUD end + def to_s + "#{@device} @ #{@baud} baud" + end + private def connect - # ::Serial calls the rubyserial gem here. - tty_devices.each { |device| return ::Serial.new(device, @baud) rescue nil } - raise BoardNotFound + tty_devices.each do |device| + @device = device + print "Trying serial device: #{self.to_s}... " + connection = ::Serial.new(@device, @baud) + puts "Connected" + return connection + rescue RubySerial::Exception => error + handle_error(error); next + end + raise SerialConnectError, "Could not connect to a serial device." + end + + def handle_error(error) + if error.message == "EBUSY" + puts "Device Busy! (EBUSY)" + else + puts "RubySerial Error (#{error.message})" + end end def tty_devices @@ -28,7 +47,7 @@ def on_windows? RUBY_PLATFORM.match /mswin|mingw/i end - def io_write(message) + def _write(message) io.write(message) end diff --git a/spec/lib/tx_rx/serial_spec.rb b/spec/lib/tx_rx/serial_spec.rb index bdb03cb0..ed772379 100644 --- a/spec/lib/tx_rx/serial_spec.rb +++ b/spec/lib/tx_rx/serial_spec.rb @@ -17,7 +17,7 @@ module Dino # Simulate 3 COM ports with COM2 connected. expect(subject).to receive(:tty_devices).and_return(["COM1", "COM2", "COM3"]) - expect(::Serial).to receive(:new).with("COM1", TxRx::Serial::BAUD).and_raise + expect(::Serial).to receive(:new).with("COM1", TxRx::Serial::BAUD).and_raise(RubySerial::Exception) expect(::Serial).to receive(:new).with("COM2", TxRx::Serial::BAUD).and_return(mock_serial = double) expect(::Serial).to_not receive(:new).with("COM3", TxRx::Serial::BAUD) expect(subject.io).to equal(mock_serial) @@ -45,9 +45,9 @@ module Dino expect(subject.io).to equal(mock_serial) end - it 'should raise a BoardNotFound exception if there is no board connected' do - allow(::Serial).to receive(:new).and_raise - expect { subject.io }.to raise_exception Dino::TxRx::BoardNotFound + it 'should raise SerialConnectError if it cannot connect to a board' do + allow(::Serial).to receive(:new).and_raise(RubySerial::Exception) + expect { subject.io }.to raise_exception Dino::TxRx::SerialConnectError end end diff --git a/spec/lib/tx_rx/tcp_spec.rb b/spec/lib/tx_rx/tcp_spec.rb index 79be3998..c610bf3a 100644 --- a/spec/lib/tx_rx/tcp_spec.rb +++ b/spec/lib/tx_rx/tcp_spec.rb @@ -9,8 +9,8 @@ module Dino end describe "#connect" do - it 'should raise a BoardNotFound exception if it cannot connect to the server' do - expect { @instance.io }.to raise_exception Dino::TxRx::BoardNotFound + it 'should raise a TCPConnectError exception if it cannot connect to the server' do + expect { @instance.io }.to raise_exception Dino::TxRx::TCPConnectError end it 'should return the TCPSocket if connected' do From baa98fade4bd70934cb38a613a84e7c584533361 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 6 Jan 2018 17:25:48 -0400 Subject: [PATCH 128/296] Aux msg always 40 bytes if targeting ATmega168 --- src/lib/Dino.h | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/lib/Dino.h b/src/lib/Dino.h index 36ffb57d..5363ec03 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -84,8 +84,8 @@ class Dino { void shiftRead (int latchPin, int len, byte dataPin, byte clockPin, byte clockHighFirst); //cmd = 22 void addShiftListener (int latchPin, int len, byte dataPin, byte clockPin, byte clockHighFirst); //cmd = 23 void removeShiftListener (); //cmd = 24 - void updateShiftListeners (); - void clearShiftListeners (); + void updateShiftListeners (); + void clearShiftListeners (); // SPI void spiBegin (byte spiMode, uint32_t clockRate); @@ -123,15 +123,19 @@ class Dino { byte cmdStr[5]; int cmd; byte pinStr[5]; int pin; byte valStr[5]; int val; - // Scale aux message allocation based on enabled features. - #if defined(DINO_IR_OUT) - byte auxMsg[528]; - #elif defined(DINO_SHIFT) || defined(DINO_SPI) || defined (DINO_I2C) - byte auxMsg[272]; - #elif defined (DINO_LCD) - byte auxMsg[144]; + // Scale aux message allocation based on enabled features and chip. + #if !defined (__AVR_ATmega168__) + #if defined(DINO_IR_OUT) + byte auxMsg[528]; + #elif defined(DINO_SHIFT) || defined(DINO_SPI) || defined (DINO_I2C) + byte auxMsg[272]; + #elif defined (DINO_LCD) + byte auxMsg[144]; + #else + byte auxMsg[48]; + #endif #else - byte auxMsg[48]; + byte auxMsg[40]; #endif // Value and response storage. From d9bd9db04d704f1acd6c74c09bffaec181b64aa5 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 6 Jan 2018 17:36:04 -0400 Subject: [PATCH 129/296] =?UTF-8?q?Need=20to=20wrap=20Timeout=20in=20begin?= =?UTF-8?q?=20apparently=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dino/tx_rx/base.rb | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index 0ea2c49e..4e5970a0 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -36,23 +36,25 @@ def handshake initialize_flow_control flush_read HANDSHAKE_TRIES.times do |retries| - Timeout.timeout(HANDSHAKE_TIMEOUT) do - print "Sending handshake to: #{self.to_s}... " - write Dino::Message.encode(command: 90) - loop do - line = gets - if line && line.match(/\AACK:/) - flush_read - ignore_retry_bytes(retries) - puts "Acknowledged. Hardware ready...\n\n" - return line.split(":", 2)[1] + begin + Timeout.timeout(HANDSHAKE_TIMEOUT) do + print "Sending handshake to: #{self.to_s}... " + write Dino::Message.encode(command: 90) + loop do + line = gets + if line && line.match(/\AACK:/) + flush_read + ignore_retry_bytes(retries) + puts "Acknowledged. Hardware ready...\n\n" + return line.split(":", 2)[1] + end end end + rescue Timeout::Error + print "No response, " + puts (retries + 1 < HANDSHAKE_TRIES ? "retrying..." : "exiting...") + next end - rescue Timeout::Error - print "No response, " - puts (retries + 1 < HANDSHAKE_TRIES ? "retrying..." : "exiting...") - next end raise HandshakeError, "Connected to wrong device, or device not running dino" end From bb4e0fb4fc0a11a6bd99d2821b8f477e81aa40a5 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 6 Jan 2018 17:37:11 -0400 Subject: [PATCH 130/296] Forgot TCP file in earlier commits --- lib/dino/tx_rx/tcp.rb | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/dino/tx_rx/tcp.rb b/lib/dino/tx_rx/tcp.rb index d9610775..f9da020a 100644 --- a/lib/dino/tx_rx/tcp.rb +++ b/lib/dino/tx_rx/tcp.rb @@ -7,15 +7,21 @@ def initialize(host, port=3466) @host, @port = host, port end - private + def to_s + "#{@host}:#{@port}" + end + private def connect - Timeout::timeout(10) { TCPSocket.open(@host, @port) } - rescue - raise BoardNotFound + print "Connecting to TCP at: #{self.to_s}... " + connection = Timeout::timeout(10) { TCPSocket.open @host, @port } + puts "Connected" + connection + rescue => error + raise TCPConnectError, error.message end - def io_write(message) + def _write(message) loop do if IO.select(nil, [io], nil) io.syswrite(message) From cd675bbd82323a44549de5f033cb5cc626c89a55 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 6 Jan 2018 18:07:04 -0400 Subject: [PATCH 131/296] Fix tests for ruby < 2.5.0 and require rubyserial ~> 0.5.0. --- dino.gemspec | 3 +-- lib/dino/tx_rx/serial.rb | 17 ++++++++++------- spec/lib/tx_rx/serial_spec.rb | 4 ++-- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/dino.gemspec b/dino.gemspec index 96d54cb8..23d14b9d 100644 --- a/dino.gemspec +++ b/dino.gemspec @@ -16,8 +16,7 @@ Gem::Specification.new do |gem| gem.version = Dino::VERSION gem.executables = ["dino"] - gem.add_dependency 'rubyserial' - gem.add_dependency 'trollop' + gem.add_dependency 'rubyserial', '~> 0.5.0' gem.add_development_dependency 'rake' gem.add_development_dependency 'rspec' diff --git a/lib/dino/tx_rx/serial.rb b/lib/dino/tx_rx/serial.rb index 6ddd4acb..7a2adca1 100644 --- a/lib/dino/tx_rx/serial.rb +++ b/lib/dino/tx_rx/serial.rb @@ -18,13 +18,16 @@ def to_s def connect tty_devices.each do |device| - @device = device - print "Trying serial device: #{self.to_s}... " - connection = ::Serial.new(@device, @baud) - puts "Connected" - return connection - rescue RubySerial::Exception => error - handle_error(error); next + begin + @device = device + print "Trying serial device: #{self.to_s}... " + connection = ::Serial.new(@device, @baud) + puts "Connected" + return connection + rescue RubySerial::Error => error + puts error + handle_error(error); next + end end raise SerialConnectError, "Could not connect to a serial device." end diff --git a/spec/lib/tx_rx/serial_spec.rb b/spec/lib/tx_rx/serial_spec.rb index ed772379..1bfb8b3b 100644 --- a/spec/lib/tx_rx/serial_spec.rb +++ b/spec/lib/tx_rx/serial_spec.rb @@ -17,7 +17,7 @@ module Dino # Simulate 3 COM ports with COM2 connected. expect(subject).to receive(:tty_devices).and_return(["COM1", "COM2", "COM3"]) - expect(::Serial).to receive(:new).with("COM1", TxRx::Serial::BAUD).and_raise(RubySerial::Exception) + expect(::Serial).to receive(:new).with("COM1", TxRx::Serial::BAUD).and_raise(RubySerial::Error) expect(::Serial).to receive(:new).with("COM2", TxRx::Serial::BAUD).and_return(mock_serial = double) expect(::Serial).to_not receive(:new).with("COM3", TxRx::Serial::BAUD) expect(subject.io).to equal(mock_serial) @@ -46,7 +46,7 @@ module Dino end it 'should raise SerialConnectError if it cannot connect to a board' do - allow(::Serial).to receive(:new).and_raise(RubySerial::Exception) + allow(::Serial).to receive(:new).and_raise(RubySerial::Error) expect { subject.io }.to raise_exception Dino::TxRx::SerialConnectError end end From a69be546883e8277a66ad672d52511428075f935 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 6 Jan 2018 21:31:13 -0400 Subject: [PATCH 132/296] Update CLI to target different boards --- bin/dino | 17 ++++-- lib/dino_cli.rb | 4 +- lib/dino_cli/generator.rb | 114 +++++++++++++++++++++++--------------- lib/dino_cli/helper.rb | 39 ++++--------- lib/dino_cli/packages.rb | 100 +++++++++++++++++++++++++++++++++ lib/dino_cli/parser.rb | 13 ++++- lib/dino_cli/targets.rb | 16 ++++++ lib/dino_cli/targets.txt | 60 ++++++++++++++++++++ lib/dino_cli/usage.txt | 34 ++++++++++++ src/lib/Dino.h | 20 +------ src/lib/DinoDefines.h | 17 ++++++ src/lib/DinoSerial.cpp | 2 + src/lib/DinoSerial.h | 2 + 13 files changed, 336 insertions(+), 102 deletions(-) create mode 100644 lib/dino_cli/packages.rb create mode 100644 lib/dino_cli/targets.rb create mode 100644 lib/dino_cli/targets.txt create mode 100644 lib/dino_cli/usage.txt create mode 100644 src/lib/DinoDefines.h diff --git a/bin/dino b/bin/dino index 63d5b20d..dbe3caae 100755 --- a/bin/dino +++ b/bin/dino @@ -5,13 +5,18 @@ require "pathname" bin_file = Pathname.new(__FILE__).realpath -# Make sure dino is in the load path -$:.unshift File.expand_path("../../lib", bin_file) - -# Work out paths for the CLI -working_dir = Dir.pwd +# Find everything else src_dir = bin_file.to_s.chomp("/bin/dino") + "/src" +lib_dir = bin_file.to_s.chomp("/bin/dino") + "/lib" +cli_dir = bin_file.to_s.chomp("/bin/dino") + "/lib/dino_cli" +working_dir = Dir.pwd + +# Make sure dino is in the load path +$:.unshift lib_dir # Start the CLI require "dino_cli" -DinoCLI.start(working_dir: working_dir, src_dir: src_dir, args: ARGV) +DinoCLI.start working_dir: working_dir, + src_dir: src_dir, + cli_dir: cli_dir, + args: ARGV diff --git a/lib/dino_cli.rb b/lib/dino_cli.rb index 30d7a37c..03e4621a 100644 --- a/lib/dino_cli.rb +++ b/lib/dino_cli.rb @@ -12,7 +12,7 @@ def self.start(options={}) end def self.sketch(options) - options = DinoCLI::Generator.run!(options) - $stdout.puts options[:sketch_file] + result = DinoCLI::Generator.run!(options) + $stdout.puts result[:sketch_file] end end diff --git a/lib/dino_cli/generator.rb b/lib/dino_cli/generator.rb index 87bd7e78..26853d4d 100644 --- a/lib/dino_cli/generator.rb +++ b/lib/dino_cli/generator.rb @@ -1,62 +1,56 @@ class DinoCLI::Generator require "fileutils" - LIB_FILENAMES = [ - "lib/Dino.h", - "lib/Dino.cpp", - "lib/DinoCoreIO.cpp", - "lib/DinoIncludes.cpp", - "lib/DinoLCD.cpp", - "lib/DinoLCD.h", - "lib/DinoSerial.cpp", - "lib/DinoSerial.h", - "lib/DinoServo.cpp", - "lib/DinoDHT.cpp", - "lib/DinoOneWire.cpp", - "lib/DinoIROut.cpp", - "lib/DinoTone.cpp", - "lib/DinoShift.cpp", - "lib/DinoSPI.cpp", - "lib/DinoI2C.cpp", - - # See explanation at top of src/lib/DinoBugWorkaround.cpp - "lib/DinoBugWorkaround.cpp", - - "vendor/DHT/DHT.cpp", - "vendor/DHT/DHT.h", - "vendor/OneWire/OneWire.cpp", - "vendor/OneWire/OneWire.h", - "vendor/Arduino-IRremote/boarddefs.h", - "vendor/Arduino-IRremote/IRremote.cpp", - "vendor/Arduino-IRremote/IRremote.h", - "vendor/Arduino-IRremote/IRremoteInt.h", - "vendor/Arduino-IRremote/irSend.cpp", - - "vendor/I2C-Master-Library/I2C.h", - "vendor/I2C-Master-Library/I2C.cpp" - ] - + require "dino_cli/packages" + require "dino_cli/targets" attr_accessor :options def initialize(options={}) @options = options + append_target + end + + def append_target + # Default to generating the mega sketch. + options[:target] = :mega unless options[:target] + # Preserve the source sketch name, since we need to copy that file. + options[:src_sketch_name] = options[:sketch_name].dup + # Append the target to the output sketch name/folder when not using :mega. + options[:sketch_name] << "_#{options[:target]}" unless options[:target] == :mega end def self.run!(options={}) instance = self.new(options) instance.read - instance.modify + instance.target_config + instance.user_config instance.write end def read - @libs = LIB_FILENAMES.map do |f| - File.read(File.join(options[:src_dir], f)) + # Start by just copying the PACKAGES hash. + @packages = PACKAGES + + # Now replace each filepath with a hash containing the filepath and contents. + @packages.each_key do |k| + @packages[k][:files].map! do |f| + contents = File.read(File.join(options[:src_dir], f)) + # If the file is in src/vendor, it gets wrapped in an #ifdef + # corresponding to the package directive. The entire package can now + # be toggled in DinoDefines.h. Without this, IDE would try to compile anyway. + if f.match /\Avendor/ + directive = @packages[k][:directive] + contents = "#include \"DinoDefines.h\"\n#ifdef #{directive}\n" << contents << "\n#endif\n" + end + { path: f, contents: contents } + end end - @sketch = File.read File.join(options[:src_dir], sketch_filename) + + # Read in the sketch itself. + @sketch = File.read File.join(options[:src_dir], "#{options[:src_sketch_name]}.ino") end - def modify + def user_config if options[:baud] && serial? @sketch.gsub! "115200", options[:baud] end @@ -78,22 +72,50 @@ def modify @sketch.gsub! "int port = 3466", "int port = #{options[:port]}" end if options[:debug] - @libs[0].gsub! "// #define debug", "#define debug" + define("debug") end unless serial? - @libs[0].gsub! "// #define TXRX_SPI", "#define TXRX_SPI" + define("TXRX_SPI") + end + end + + def target_config + target_packages = TARGETS[options[:target]] + target_packages.each do |t| + directive = PACKAGES[t][:directive] + define(directive) if directive + end + end + + def define(directive) + gsub_defines "// #define #{directive}", "#define #{directive}" + end + + # Run gsub! on contents of src/lib/Dino.h specifically. + def gsub_defines(from, to) + @packages[:core][:files].each do |f| + if f[:path] == "lib/DinoDefines.h" + f[:contents].gsub!(from, to) + end end end def write + # Write the sketch itself first. sketch = File.join(output_dir, sketch_filename) File.open(sketch, 'w') { |f| f.write @sketch } - libs = LIB_FILENAMES.map { |f| File.join(output_dir, f.split('/')[-1])} - libs.each_with_index do |file, index| - File.open(file, 'w') { |f| f.write @libs[index]} + # Go through the @packages hash and copy all the source files regardless of target. + # Append source file basename to the output dir to get output file path. + # Then write the file contents to the destination path. + @packages.each_key do |k| + @packages[k][:files].each do |file| + dest_path = File.join(output_dir, file[:path].split('/')[-1]) + File.open(dest_path, 'w') { |f| f.write file[:contents] } + end end + # Return the location of the sketch file. options[:sketch_file] = sketch options end @@ -115,6 +137,6 @@ def make_output_dir end def sketch_filename - options[:sketch_filename] ||= "#{options[:sketch_name]}.ino" + "#{options[:sketch_name]}.ino" end end diff --git a/lib/dino_cli/helper.rb b/lib/dino_cli/helper.rb index 2d30a1b2..3ea1b46f 100644 --- a/lib/dino_cli/helper.rb +++ b/lib/dino_cli/helper.rb @@ -1,39 +1,22 @@ module DinoCLI::Helper - def error(message) + def error(message, help=:usage) $stderr.puts $stderr.puts "Error: #{message}" $stderr.puts - usage + usage if help == :usage + targets if help == :targets end def usage - $stderr.puts "Usage:" - $stderr.puts " dino task [options]" + text = File.read(File.join(@options[:cli_dir], "usage.txt")) + $stderr.print text $stderr.puts - $stderr.puts "Tasks:" - $stderr.puts " sketch SKETCH [options]" - $stderr.puts - $stderr.puts "Available sketches and options for each sketch:" - $stderr.puts - $stderr.puts " serial" - $stderr.puts " -baud BAUD" - $stderr.puts " -debug" - $stderr.puts - $stderr.puts " ethernet" - $stderr.puts " -mac XX:XX:XX:XX:XX:XX" - $stderr.puts " -ip XXX.XXX.XXX.XXX" - $stderr.puts " -port PORT" - $stderr.puts " -debug" - $stderr.puts - $stderr.puts " wifi" - $stderr.puts " -ssid SSID" - $stderr.puts " -password PASSWORD" - $stderr.puts " -port PORT" - $stderr.puts " -debug" - $stderr.puts - $stderr.puts "Example:" - $stderr.puts - $stderr.puts " dino sketch serial -baud 9600" + exit(2) + end + + def targets + text = File.read(File.join(@options[:cli_dir], "targets.txt")) + $stderr.print text $stderr.puts exit(2) end diff --git a/lib/dino_cli/packages.rb b/lib/dino_cli/packages.rb new file mode 100644 index 00000000..15ea1d98 --- /dev/null +++ b/lib/dino_cli/packages.rb @@ -0,0 +1,100 @@ +class DinoCLI::Generator +PACKAGES = { + # These files are always included when building a sketch. + core: { + description: "Core Dino Library", + directive: nil, + files: [ + "lib/Dino.h", + "lib/DinoDefines.h", + "lib/Dino.cpp", + "lib/DinoCoreIO.cpp", + "lib/DinoIncludes.cpp", + "lib/DinoBugWorkaround.cpp", # See explanation at top of file. + ] + }, + servo: { + description: "Servo support", + directive: "DINO_LCD", + files: [ + "lib/DinoServo.cpp", + ] + }, + tone: { + description: "Tone support", + directive: "DINO_TONE", + files: [ + "lib/DinoTone.cpp", + ] + }, + shift: { + description: "Shift Register support", + directive: "DINO_SHIFT", + files: [ + "lib/DinoShift.cpp", + ] + }, + spi: { + description: "SPI support", + directive: "DINO_SPI", + files: [ + "lib/DinoSPI.cpp", + ] + }, + lcd: { + description: "LCD based on Arduino LiquidCrystal", + directive: "DINO_LCD", + files: [ + "lib/DinoLCD.cpp", + "lib/DinoLCD.h", + ] + }, + dht: { + description: "Read DHT temp/humidity sensors", + directive: "DINO_DHT", + files: [ + "lib/DinoDHT.cpp", + "vendor/DHT/DHT.cpp", + "vendor/DHT/DHT.h", + ] + }, + serial: { + description: "Software serial output", + directive: "DINO_SERIAL", + files: [ + "lib/DinoSerial.cpp", + "lib/DinoSerial.h", + ] + }, + ir_out: { + description: "Transmit infrared signals", + directive: "DINO_IR_OUT", + files: [ + "lib/DinoIROut.cpp", + "vendor/Arduino-IRremote/boarddefs.h", + "vendor/Arduino-IRremote/IRremote.cpp", + "vendor/Arduino-IRremote/IRremote.h", + "vendor/Arduino-IRremote/IRremoteInt.h", + "vendor/Arduino-IRremote/irSend.cpp", + ] + }, + one_wire: { + description: "OneWire bus support (Just DS18B20 for now)", + directive: "DINO_ONE_WIRE", + files: [ + "lib/DinoOneWire.cpp", + "vendor/OneWire/OneWire.cpp", + "vendor/OneWire/OneWire.h", + ] + }, + i2c: { + description: "I2C device support", + directive: "DINO_I2C", + files: [ + "lib/DinoI2C.cpp", + "vendor/I2C-Master-Library/I2C.h", + "vendor/I2C-Master-Library/I2C.cpp", + ] + } +} +end diff --git a/lib/dino_cli/parser.rb b/lib/dino_cli/parser.rb index 7e9217c9..a2c578f4 100644 --- a/lib/dino_cli/parser.rb +++ b/lib/dino_cli/parser.rb @@ -15,9 +15,12 @@ def parse args = @options[:args].dup error("No arguments given") if args.empty? - # Ensure task is the first argument + # Ensure task is the first argument. @options[:task] = args.shift + + # If the task is a help command, just run it, don't return. usage if @options[:task].match /help|usage/ + targets if @options[:task].match /targets/ # Ensure there's only one task error "Invalid task '#{@options[:task]}'" unless DinoCLI::TASKS.include? @options[:task] @@ -33,6 +36,8 @@ def parse args.shift; set_sketch("wifi") when '-baud' args.shift; @options[:baud] = args.shift + when '-target' + args.shift; set_target(args.shift) when '-mac' args.shift; @options[:mac] = args.shift when '-ip' @@ -59,4 +64,10 @@ def set_sketch(name) error "More than one sketch specified" unless @options[:sketch_name].nil? @options[:sketch_name] = "dino_#{name}" end + + def set_target(target) + targets = DinoCLI::Generator::TARGETS.each_key.map{|a| a.to_s} + error "Invalid target '#{target}'", :targets unless targets.include?(target) + @options[:target] = target.to_sym + end end diff --git a/lib/dino_cli/targets.rb b/lib/dino_cli/targets.rb new file mode 100644 index 00000000..92d029f8 --- /dev/null +++ b/lib/dino_cli/targets.rb @@ -0,0 +1,16 @@ +class DinoCLI::Generator + TARGETS = { + # Default target always includes all packages. + # Tone temporarily disabled due to timer conflict. + mega: PACKAGES.each_key.map {|k| k} - [:tone], + + # Core is core. + core: [:core], + + # Specific features for the old mega168 chips. + mega168: [:core, :servo, :lcd, :spi, :i2c], + + # ARM includes everytyhing except specific incompatibilities. + arm: PACKAGES.each_key.map {|k| k} - [:serial, :tone, :ir_out, :i2c], + } +end diff --git a/lib/dino_cli/targets.txt b/lib/dino_cli/targets.txt new file mode 100644 index 00000000..b5082cda --- /dev/null +++ b/lib/dino_cli/targets.txt @@ -0,0 +1,60 @@ + + Valid targets: + + mega (default) + + Covers the popular Atmel chips on Arduino boards listed below, and many more. + This is the primary target and all features will be included. Default setting. + + Chips: ATmega328p, ATmega32u4, ATmega1280, ATmega2560 + Boards: UNO, Nano, Mini, Ethernet + Leonardo, Micro, Esplora, Leonardo ETH + Mega 1280, Mega 2560, Mega ADK + Most Lilypads (excluding ones with an ATmega168) + + core + + This includes only the core feature set of dino: + Set Pin Mode + Digital Read/Write/Listen + Analog Read/Write/Listen + + This is useful if you're getting errors when trying to upload, want to + include specific features by hand, trying to squeeze onto something with + low memory, or testing a new chip. This is the smallest the dino sketch + can get for now, and should be universally compatible. + + mega168 + + This specifically targets the older ATmega168 chip used in early Arduinos. + They have half the RAM and flash available, so we need to cut the sketch down. + Beyond core IO functionality, it ONLY includes the following features: + Servo, LCD, SPI, I2C + + Chips: ATmega168 + Boards: Duemilanove, Diecimila, Pro, Pro Mini + (Later versions of these may carry an ATmega328) + + Note: While you can generate this sketch using any communication type, + it will only fit on the board when generated for serial communication. + There is not enough memory available for Wi-Fi or Ethernet. You can + try the core target to tradeoff the features for networking. + + Note: Aux message length is always limited to 40 bytes on this chip. + + arm + + This is the same as mega, but omits libraries specifically known to be + incompatible with the Atmel SAMD (ARM Cortex M0) chips. Use this when + you need to load a sketch on the Arduino Due or similar boards. + This option excludes the following features: + Serial, Tone, IR Out, I2C + + Chips: AT91SAM3X8E + Boards: Due + + NOTE: While different targets include different libraries, you can manually + control which libraries are included at compile time by editing DinoDefines.h. + All the library files (even the unused ones) are copied to the destination sketch + folder to enable this. Uncomment a corresponding #define in DinoDefines.h to + include a library in the sketch. Comment it out to exclude that library. diff --git a/lib/dino_cli/usage.txt b/lib/dino_cli/usage.txt new file mode 100644 index 00000000..5e4202a7 --- /dev/null +++ b/lib/dino_cli/usage.txt @@ -0,0 +1,34 @@ + + Usage: + dino task [options] + + Tasks: + sketch SKETCH [options] + help + targets + + Global options: + + -target TARGET (default: mega. Run 'dino targets' for more info) + -debug (default: off) + + Available sketches and sketch for each sketch: + + serial + -baud BAUD (default: 115200) + + ethernet + -mac XX:XX:XX:XX:XX:XX (required) + -ip XXX.XXX.XXX.XXX (required, no DHCP) + -port PORT (default: 3466) + + wifi + -ssid SSID (required) + -password PASSWORD (required) + -port PORT (default: 3466) + + Examples: + + dino sketch ethernet -mac 12:34:56:78:90:12 - ip 192.168.1.2 + dino sketch serial -baud 57600 + dino sketch serial -baud 9600 -target core diff --git a/src/lib/Dino.h b/src/lib/Dino.h index 5363ec03..e4c92168 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -4,25 +4,7 @@ #ifndef Dino_h #define Dino_h #include - -// If using Wi-Fi or Ethernet shield, uncomment this to let the SPI library know. -// #define TXRX_SPI - -// Uncomment this line to enable debugging mode. -// #define debug - -// Comment these out to exclude default features. -// Arduino Due cannot use: SERIAL, IR, TONE, I2C -#define DINO_SERVO -#define DINO_LCD -#define DINO_SERIAL -#define DINO_DHT -#define DINO_ONE_WIRE -#define DINO_IR_OUT -// #define DINO_TONE -#define DINO_SHIFT -#define DINO_SPI -#define DINO_I2C +#include "DinoDefines.h" // Figure out how many pins our hardware has. #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) diff --git a/src/lib/DinoDefines.h b/src/lib/DinoDefines.h new file mode 100644 index 00000000..8e630eed --- /dev/null +++ b/src/lib/DinoDefines.h @@ -0,0 +1,17 @@ +// If using Wi-Fi or Ethernet shield, uncomment this to let the SPI library know. +// #define TXRX_SPI + +// Uncomment this line to enable debugging mode. +// #define debug + +// Uncomment these to include features beyond core features. +// #define DINO_SERVO +// #define DINO_LCD +// #define DINO_SERIAL +// #define DINO_DHT +// #define DINO_ONE_WIRE +// #define DINO_IR_OUT +// #define DINO_TONE +// #define DINO_SHIFT +// #define DINO_SPI +// #define DINO_I2C diff --git a/src/lib/DinoSerial.cpp b/src/lib/DinoSerial.cpp index 8e1dfec4..9efb7995 100644 --- a/src/lib/DinoSerial.cpp +++ b/src/lib/DinoSerial.cpp @@ -1,3 +1,4 @@ +#ifdef DINO_SERIAL #include "Arduino.h" #include "DinoSerial.h" @@ -38,3 +39,4 @@ void DinoSerial::begin(char *aux) { int baud = atoi(aux); softSerial.begin(baud); } +#endif diff --git a/src/lib/DinoSerial.h b/src/lib/DinoSerial.h index a4427767..f74c0058 100644 --- a/src/lib/DinoSerial.h +++ b/src/lib/DinoSerial.h @@ -1,3 +1,4 @@ +#ifdef DINO_SERIAL #ifndef DinoSerial_h #define DinoSerial_h @@ -16,3 +17,4 @@ class DinoSerial { }; #endif +#endif From b908bcfcc57c730a88709fb07d94f59e63ce2fa4 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 6 Jan 2018 22:44:31 -0400 Subject: [PATCH 133/296] Make all external libraries submodules. Fix a couple #ifdefs. --- .gitmodules | 6 + lib/dino_cli/packages.rb | 4 +- src/lib/DinoDHT.cpp | 3 +- src/lib/DinoLCD.cpp | 3 + src/lib/DinoLCD.h | 3 + src/lib/DinoSerial.cpp | 1 + src/lib/DinoSerial.h | 1 + src/vendor/DHT/DHT.cpp | 207 --------------------------------- src/vendor/DHT/DHT.h | 96 --------------- src/vendor/OneWire/OneWire.cpp | 38 ++++-- src/vendor/OneWire/OneWire.h | 196 +++++++++++++++++++++++++++++-- src/vendor/arduino-DHT | 1 + 12 files changed, 233 insertions(+), 326 deletions(-) delete mode 100644 src/vendor/DHT/DHT.cpp delete mode 100644 src/vendor/DHT/DHT.h mode change 100755 => 100644 src/vendor/OneWire/OneWire.cpp mode change 100755 => 100644 src/vendor/OneWire/OneWire.h create mode 160000 src/vendor/arduino-DHT diff --git a/.gitmodules b/.gitmodules index 2d2f614d..47bcd249 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,9 @@ [submodule "src/vendor/I2C-Master-Library"] path = src/vendor/I2C-Master-Library url = https://github.com/vickash/I2C-Master-Library.git +[submodule "src/vendor/arduino-DHT"] + path = src/vendor/arduino-DHT + url = https://github.com/markruys/arduino-DHT.git +[submodule "src/vendor/OneWire"] + path = src/vendor/OneWire + url = https://github.com/PaulStoffregen/OneWire.git diff --git a/lib/dino_cli/packages.rb b/lib/dino_cli/packages.rb index 15ea1d98..5bb654fe 100644 --- a/lib/dino_cli/packages.rb +++ b/lib/dino_cli/packages.rb @@ -54,8 +54,8 @@ class DinoCLI::Generator directive: "DINO_DHT", files: [ "lib/DinoDHT.cpp", - "vendor/DHT/DHT.cpp", - "vendor/DHT/DHT.h", + "vendor/arduino-DHT/DHT.cpp", + "vendor/arduino-DHT/DHT.h", ] }, serial: { diff --git a/src/lib/DinoDHT.cpp b/src/lib/DinoDHT.cpp index 43b56090..7a29e1c8 100644 --- a/src/lib/DinoDHT.cpp +++ b/src/lib/DinoDHT.cpp @@ -17,7 +17,8 @@ DHT dht; // CMD = 13 // Read a DHT sensor void Dino::dhtRead() { - if (pin != dht.pin) dht.setup(pin); + // if (pin != dht.pin) + dht.setup(pin); float reading; char readingBuff[10]; char prefix; diff --git a/src/lib/DinoLCD.cpp b/src/lib/DinoLCD.cpp index 0c0bbabb..d705565c 100644 --- a/src/lib/DinoLCD.cpp +++ b/src/lib/DinoLCD.cpp @@ -1,3 +1,5 @@ +#include "Dino.h" +#ifdef DINO_LCD #include "Arduino.h" #include "DinoLCD.h" @@ -66,3 +68,4 @@ void DinoLCD::setLCDCursor(char *aux) { int *values = parse(aux); lcd.setCursor(values[0], values[1]); } +#endif diff --git a/src/lib/DinoLCD.h b/src/lib/DinoLCD.h index a90629e2..49657dba 100644 --- a/src/lib/DinoLCD.h +++ b/src/lib/DinoLCD.h @@ -1,3 +1,5 @@ +#include "Dino.h" +#ifdef DINO_LCD #ifndef DinoLCD_h #define DinoLCD_h @@ -18,3 +20,4 @@ class DinoLCD { }; #endif +#endif diff --git a/src/lib/DinoSerial.cpp b/src/lib/DinoSerial.cpp index 9efb7995..f39ddde0 100644 --- a/src/lib/DinoSerial.cpp +++ b/src/lib/DinoSerial.cpp @@ -1,3 +1,4 @@ +#include "Dino.h" #ifdef DINO_SERIAL #include "Arduino.h" #include "DinoSerial.h" diff --git a/src/lib/DinoSerial.h b/src/lib/DinoSerial.h index f74c0058..9cc3a85a 100644 --- a/src/lib/DinoSerial.h +++ b/src/lib/DinoSerial.h @@ -1,3 +1,4 @@ +#include "Dino.h" #ifdef DINO_SERIAL #ifndef DinoSerial_h #define DinoSerial_h diff --git a/src/vendor/DHT/DHT.cpp b/src/vendor/DHT/DHT.cpp deleted file mode 100644 index 90727246..00000000 --- a/src/vendor/DHT/DHT.cpp +++ /dev/null @@ -1,207 +0,0 @@ -/****************************************************************** - DHT Temperature & Humidity Sensor library for Arduino. - - Features: - - Support for DHT11 and DHT22/AM2302/RHT03 - - Auto detect sensor model - - Very low memory footprint - - Very small code - - http://www.github.com/markruys/arduino-DHT - - Written by Mark Ruys, mark@paracas.nl. - - BSD license, check license.txt for more information. - All text above must be included in any redistribution. - - Datasheets: - - http://www.micro4you.com/files/sensor/DHT11.pdf - - http://www.adafruit.com/datasheets/DHT22.pdf - - http://dlnmh9ip6v2uc.cloudfront.net/datasheets/Sensors/Weather/RHT03.pdf - - http://meteobox.tk/files/AM2302.pdf - - Changelog: - 2013-06-10: Initial version - 2013-06-12: Refactored code - 2013-07-01: Add a resetTimer method - ******************************************************************/ - -#include "DHT.h" - -void DHT::setup(uint8_t pin, DHT_MODEL_t model) -{ - DHT::pin = pin; - DHT::model = model; - DHT::resetTimer(); // Make sure we do read the sensor in the next readSensor() - - if ( model == AUTO_DETECT) { - DHT::model = DHT22; - readSensor(); - if ( error == ERROR_TIMEOUT ) { - DHT::model = DHT11; - delay(1000); - // Warning: in case we auto detect a DHT11, you should wait at least 1000 msec - // before your first read request. Otherwise you will get a time out error. - } - } -} - -void DHT::resetTimer() -{ - DHT::lastReadTime = millis() - 3000; -} - -float DHT::getHumidity() -{ - readSensor(); - return humidity; -} - -float DHT::getTemperature() -{ - readSensor(); - return temperature; -} - -#ifndef OPTIMIZE_SRAM_SIZE - -const char* DHT::getStatusString() -{ - switch ( error ) { - case DHT::ERROR_TIMEOUT: - return "TIMEOUT"; - - case DHT::ERROR_CHECKSUM: - return "CHECKSUM"; - - default: - return "OK"; - } -} - -#else - -// At the expense of 26 bytes of extra PROGMEM, we save 11 bytes of -// SRAM by using the following method: - -prog_char P_OK[] PROGMEM = "OK"; -prog_char P_TIMEOUT[] PROGMEM = "TIMEOUT"; -prog_char P_CHECKSUM[] PROGMEM = "CHECKSUM"; - -const char *DHT::getStatusString() { - prog_char *c; - switch ( error ) { - case DHT::ERROR_CHECKSUM: - c = P_CHECKSUM; break; - - case DHT::ERROR_TIMEOUT: - c = P_TIMEOUT; break; - - default: - c = P_OK; break; - } - - static char buffer[9]; - strcpy_P(buffer, c); - - return buffer; -} - -#endif - -void DHT::readSensor() -{ - // Make sure we don't poll the sensor too often - // - Max sample rate DHT11 is 1 Hz (duty cicle 1000 ms) - // - Max sample rate DHT22 is 0.5 Hz (duty cicle 2000 ms) - unsigned long startTime = millis(); - if ( (unsigned long)(startTime - lastReadTime) < (model == DHT11 ? 999L : 1999L) ) { - return; - } - lastReadTime = startTime; - - temperature = NAN; - humidity = NAN; - - // Request sample - - digitalWrite(pin, LOW); // Send start signal - pinMode(pin, OUTPUT); - if ( model == DHT11 ) { - delay(18); - } - else { - // This will fail for a DHT11 - that's how we can detect such a device - delayMicroseconds(800); - } - - pinMode(pin, INPUT); - digitalWrite(pin, HIGH); // Switch bus to receive data - - // We're going to read 83 edges: - // - First a FALLING, RISING, and FALLING edge for the start bit - // - Then 40 bits: RISING and then a FALLING edge per bit - // To keep our code simple, we accept any HIGH or LOW reading if it's max 85 usecs long - - word rawHumidity; - word rawTemperature; - word data; - - for ( int8_t i = -3 ; i < 2 * 40; i++ ) { - byte age; - startTime = micros(); - - do { - age = (unsigned long)(micros() - startTime); - if ( age > 90 ) { - error = ERROR_TIMEOUT; - return; - } - } - while ( digitalRead(pin) == (i & 1) ? HIGH : LOW ); - - if ( i >= 0 && (i & 1) ) { - // Now we are being fed our 40 bits - data <<= 1; - - // A zero max 30 usecs, a one at least 68 usecs. - if ( age > 30 ) { - data |= 1; // we got a one - } - } - - switch ( i ) { - case 31: - rawHumidity = data; - break; - case 63: - rawTemperature = data; - data = 0; - break; - } - } - - // Verify checksum - - if ( (byte)(((byte)rawHumidity) + (rawHumidity >> 8) + ((byte)rawTemperature) + (rawTemperature >> 8)) != data ) { - error = ERROR_CHECKSUM; - return; - } - - // Store readings - - if ( model == DHT11 ) { - humidity = rawHumidity >> 8; - temperature = rawTemperature >> 8; - } - else { - humidity = rawHumidity * 0.1; - - if ( rawTemperature & 0x8000 ) { - rawTemperature = -(int16_t)(rawTemperature & 0x7FFF); - } - temperature = ((int16_t)rawTemperature) * 0.1; - } - - error = ERROR_NONE; -} \ No newline at end of file diff --git a/src/vendor/DHT/DHT.h b/src/vendor/DHT/DHT.h deleted file mode 100644 index bf6113a0..00000000 --- a/src/vendor/DHT/DHT.h +++ /dev/null @@ -1,96 +0,0 @@ -/****************************************************************** - DHT Temperature & Humidity Sensor library for Arduino. - - Features: - - Support for DHT11 and DHT22/AM2302/RHT03 - - Auto detect sensor model - - Very low memory footprint - - Very small code - - http://www.github.com/markruys/arduino-DHT - - Written by Mark Ruys, mark@paracas.nl. - - BSD license, check license.txt for more information. - All text above must be included in any redistribution. - - Datasheets: - - http://www.micro4you.com/files/sensor/DHT11.pdf - - http://www.adafruit.com/datasheets/DHT22.pdf - - http://dlnmh9ip6v2uc.cloudfront.net/datasheets/Sensors/Weather/RHT03.pdf - - http://meteobox.tk/files/AM2302.pdf - - Changelog: - 2013-06-10: Initial version - 2013-06-12: Refactored code - 2013-07-01: Add a resetTimer method - ******************************************************************/ - -#ifndef dht_h -#define dht_h - -#if ARDUINO < 100 - #include -#else - #include -#endif - -class DHT -{ -public: - - typedef enum { - AUTO_DETECT, - DHT11, - DHT22, - AM2302, // Packaged DHT22 - RHT03 // Equivalent to DHT22 - } - DHT_MODEL_t; - - typedef enum { - ERROR_NONE = 0, - ERROR_TIMEOUT, - ERROR_CHECKSUM - } - DHT_ERROR_t; - - void setup(uint8_t pin, DHT_MODEL_t model=AUTO_DETECT); - void resetTimer(); - - float getTemperature(); - float getHumidity(); - - DHT_ERROR_t getStatus() { return error; }; - const char* getStatusString(); - - DHT_MODEL_t getModel() { return model; } - - int getMinimumSamplingPeriod() { return model == DHT11 ? 1000 : 2000; } - - int8_t getNumberOfDecimalsTemperature() { return model == DHT11 ? 0 : 1; }; - int8_t getLowerBoundTemperature() { return model == DHT11 ? 0 : -40; }; - int8_t getUpperBoundTemperature() { return model == DHT11 ? 50 : 125; }; - - int8_t getNumberOfDecimalsHumidity() { return 0; }; - int8_t getLowerBoundHumidity() { return model == DHT11 ? 20 : 0; }; - int8_t getUpperBoundHumidity() { return model == DHT11 ? 90 : 100; }; - - static float toFahrenheit(float fromCelcius) { return 1.8 * fromCelcius + 32.0; }; - static float toCelsius(float fromFahrenheit) { return (fromFahrenheit - 32.0) / 1.8; }; - - uint8_t pin; - -protected: - void readSensor(); - - float temperature; - float humidity; - -private: - DHT_MODEL_t model; - DHT_ERROR_t error; - unsigned long lastReadTime; -}; - -#endif /*dht_h*/ \ No newline at end of file diff --git a/src/vendor/OneWire/OneWire.cpp b/src/vendor/OneWire/OneWire.cpp old mode 100755 new mode 100644 index efd69491..5c9945d5 --- a/src/vendor/OneWire/OneWire.cpp +++ b/src/vendor/OneWire/OneWire.cpp @@ -5,15 +5,35 @@ The latest version of this library may be found at: http://www.pjrc.com/teensy/td_libs_OneWire.html OneWire has been maintained by Paul Stoffregen (paul@pjrc.com) since -January 2010. At the time, it was in need of many bug fixes, but had +January 2010. + +DO NOT EMAIL for technical support, especially not for ESP chips! +All project support questions must be posted on public forums +relevant to the board or chips used. If using Arduino, post on +Arduino's forum. If using ESP, post on the ESP community forums. +There is ABSOLUTELY NO TECH SUPPORT BY PRIVATE EMAIL! + +Github's issue tracker for OneWire should be used only to report +specific bugs. DO NOT request project support via Github. All +project and tech support questions must be posted on forums, not +github issues. If you experience a problem and you are not +absolutely sure it's an issue with the library, ask on a forum +first. Only use github to report issues after experts have +confirmed the issue is with OneWire rather than your project. + +Back in 2010, OneWire was in need of many bug fixes, but had been abandoned the original author (Jim Studt). None of the known contributors were interested in maintaining OneWire. Paul typically works on OneWire every 6 to 12 months. Patches usually wait that long. If anyone is interested in more actively maintaining OneWire, -please contact Paul. +please contact Paul (this is pretty much the only reason to use +private email about OneWire). + +OneWire is now very mature code. No changes other than adding +definitions for newer hardware support are anticipated. Version 2.3: - Unknonw chip fallback mode, Roger Clark + Unknown chip fallback mode, Roger Clark Teensy-LC compatibility, Paul Stoffregen Search bug fix, Love Nystrom @@ -141,8 +161,8 @@ OneWire::OneWire(uint8_t pin) // uint8_t OneWire::reset(void) { - IO_REG_TYPE mask = bitmask; - volatile IO_REG_TYPE *reg IO_REG_ASM = baseReg; + IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask; + volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg; uint8_t r; uint8_t retries = 125; @@ -175,8 +195,8 @@ uint8_t OneWire::reset(void) // void OneWire::write_bit(uint8_t v) { - IO_REG_TYPE mask=bitmask; - volatile IO_REG_TYPE *reg IO_REG_ASM = baseReg; + IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask; + volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg; if (v & 1) { noInterrupts(); @@ -203,8 +223,8 @@ void OneWire::write_bit(uint8_t v) // uint8_t OneWire::read_bit(void) { - IO_REG_TYPE mask=bitmask; - volatile IO_REG_TYPE *reg IO_REG_ASM = baseReg; + IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask; + volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg; uint8_t r; noInterrupts(); diff --git a/src/vendor/OneWire/OneWire.h b/src/vendor/OneWire/OneWire.h old mode 100755 new mode 100644 index b66b7eba..c394f1d0 --- a/src/vendor/OneWire/OneWire.h +++ b/src/vendor/OneWire/OneWire.h @@ -62,7 +62,8 @@ #define PIN_TO_BASEREG(pin) (portInputRegister(digitalPinToPort(pin))) #define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) #define IO_REG_TYPE uint8_t -#define IO_REG_ASM asm("r30") +#define IO_REG_BASE_ATTR asm("r30") +#define IO_REG_MASK_ATTR #define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0) #define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) &= ~(mask)) #define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+1)) |= (mask)) @@ -73,7 +74,8 @@ #define PIN_TO_BASEREG(pin) (portOutputRegister(pin)) #define PIN_TO_BITMASK(pin) (1) #define IO_REG_TYPE uint8_t -#define IO_REG_ASM +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR __attribute__ ((unused)) #define DIRECT_READ(base, mask) (*((base)+512)) #define DIRECT_MODE_INPUT(base, mask) (*((base)+640) = 0) #define DIRECT_MODE_OUTPUT(base, mask) (*((base)+640) = 1) @@ -84,14 +86,15 @@ #define PIN_TO_BASEREG(pin) (portOutputRegister(pin)) #define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) #define IO_REG_TYPE uint8_t -#define IO_REG_ASM +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR #define DIRECT_READ(base, mask) ((*((base)+16) & (mask)) ? 1 : 0) #define DIRECT_MODE_INPUT(base, mask) (*((base)+20) &= ~(mask)) #define DIRECT_MODE_OUTPUT(base, mask) (*((base)+20) |= (mask)) #define DIRECT_WRITE_LOW(base, mask) (*((base)+8) = (mask)) #define DIRECT_WRITE_HIGH(base, mask) (*((base)+4) = (mask)) -#elif defined(__SAM3X8E__) +#elif defined(__SAM3X8E__) || defined(__SAM3A8C__) || defined(__SAM3A4C__) // Arduino 1.5.1 may have a bug in delayMicroseconds() on Arduino Due. // http://arduino.cc/forum/index.php/topic,141030.msg1076268.html#msg1076268 // If you have trouble with OneWire on Arduino Due, please check the @@ -99,7 +102,8 @@ #define PIN_TO_BASEREG(pin) (&(digitalPinToPort(pin)->PIO_PER)) #define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) #define IO_REG_TYPE uint32_t -#define IO_REG_ASM +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR #define DIRECT_READ(base, mask) (((*((base)+15)) & (mask)) ? 1 : 0) #define DIRECT_MODE_INPUT(base, mask) ((*((base)+5)) = (mask)) #define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+4)) = (mask)) @@ -116,7 +120,8 @@ #define PIN_TO_BASEREG(pin) (portModeRegister(digitalPinToPort(pin))) #define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) #define IO_REG_TYPE uint32_t -#define IO_REG_ASM +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR #define DIRECT_READ(base, mask) (((*(base+4)) & (mask)) ? 1 : 0) //PORTX + 0x10 #define DIRECT_MODE_INPUT(base, mask) ((*(base+2)) = (mask)) //TRISXSET + 0x08 #define DIRECT_MODE_OUTPUT(base, mask) ((*(base+1)) = (mask)) //TRISXCLR + 0x04 @@ -124,21 +129,128 @@ #define DIRECT_WRITE_HIGH(base, mask) ((*(base+8+2)) = (mask)) //LATXSET + 0x28 #elif defined(ARDUINO_ARCH_ESP8266) +// Special note: I depend on the ESP community to maintain these definitions and +// submit good pull requests. I can not answer any ESP questions or help you +// resolve any problems related to ESP chips. Please do not contact me and please +// DO NOT CREATE GITHUB ISSUES for ESP support. All ESP questions must be asked +// on ESP community forums. #define PIN_TO_BASEREG(pin) ((volatile uint32_t*) GPO) #define PIN_TO_BITMASK(pin) (1 << pin) #define IO_REG_TYPE uint32_t -#define IO_REG_ASM +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR #define DIRECT_READ(base, mask) ((GPI & (mask)) ? 1 : 0) //GPIO_IN_ADDRESS #define DIRECT_MODE_INPUT(base, mask) (GPE &= ~(mask)) //GPIO_ENABLE_W1TC_ADDRESS #define DIRECT_MODE_OUTPUT(base, mask) (GPE |= (mask)) //GPIO_ENABLE_W1TS_ADDRESS #define DIRECT_WRITE_LOW(base, mask) (GPOC = (mask)) //GPIO_OUT_W1TC_ADDRESS #define DIRECT_WRITE_HIGH(base, mask) (GPOS = (mask)) //GPIO_OUT_W1TS_ADDRESS +#elif defined(ARDUINO_ARCH_ESP32) +#include +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) (pin) +#define IO_REG_TYPE uint32_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR + +static inline __attribute__((always_inline)) +IO_REG_TYPE directRead(IO_REG_TYPE pin) +{ + if ( pin < 32 ) + return (GPIO.in >> pin) & 0x1; + else if ( pin < 40 ) + return (GPIO.in1.val >> (pin - 32)) & 0x1; + + return 0; +} + +static inline __attribute__((always_inline)) +void directWriteLow(IO_REG_TYPE pin) +{ + if ( pin < 32 ) + GPIO.out_w1tc = ((uint32_t)1 << pin); + else if ( pin < 34 ) + GPIO.out1_w1tc.val = ((uint32_t)1 << (pin - 32)); +} + +static inline __attribute__((always_inline)) +void directWriteHigh(IO_REG_TYPE pin) +{ + if ( pin < 32 ) + GPIO.out_w1ts = ((uint32_t)1 << pin); + else if ( pin < 34 ) + GPIO.out1_w1ts.val = ((uint32_t)1 << (pin - 32)); +} + +static inline __attribute__((always_inline)) +void directModeInput(IO_REG_TYPE pin) +{ + if ( digitalPinIsValid(pin) ) + { + uint32_t rtc_reg(rtc_gpio_desc[pin].reg); + + if ( rtc_reg ) // RTC pins PULL settings + { + ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].mux); + ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].pullup | rtc_gpio_desc[pin].pulldown); + } + + if ( pin < 32 ) + GPIO.enable_w1tc = ((uint32_t)1 << pin); + else + GPIO.enable1_w1tc.val = ((uint32_t)1 << (pin - 32)); + + uint32_t pinFunction((uint32_t)2 << FUN_DRV_S); // what are the drivers? + pinFunction |= FUN_IE; // input enable but required for output as well? + pinFunction |= ((uint32_t)2 << MCU_SEL_S); + + ESP_REG(DR_REG_IO_MUX_BASE + esp32_gpioMux[pin].reg) = pinFunction; + + GPIO.pin[pin].val = 0; + } +} + +static inline __attribute__((always_inline)) +void directModeOutput(IO_REG_TYPE pin) +{ + if ( digitalPinIsValid(pin) && pin <= 33 ) // pins above 33 can be only inputs + { + uint32_t rtc_reg(rtc_gpio_desc[pin].reg); + + if ( rtc_reg ) // RTC pins PULL settings + { + ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].mux); + ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].pullup | rtc_gpio_desc[pin].pulldown); + } + + if ( pin < 32 ) + GPIO.enable_w1ts = ((uint32_t)1 << pin); + else // already validated to pins <= 33 + GPIO.enable1_w1ts.val = ((uint32_t)1 << (pin - 32)); + + uint32_t pinFunction((uint32_t)2 << FUN_DRV_S); // what are the drivers? + pinFunction |= FUN_IE; // input enable but required for output as well? + pinFunction |= ((uint32_t)2 << MCU_SEL_S); + + ESP_REG(DR_REG_IO_MUX_BASE + esp32_gpioMux[pin].reg) = pinFunction; + + GPIO.pin[pin].val = 0; + } +} + +#define DIRECT_READ(base, pin) directRead(pin) +#define DIRECT_WRITE_LOW(base, pin) directWriteLow(pin) +#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(pin) +#define DIRECT_MODE_INPUT(base, pin) directModeInput(pin) +#define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(pin) +#warning "ESP32 OneWire testing" + #elif defined(__SAMD21G18A__) #define PIN_TO_BASEREG(pin) portModeRegister(digitalPinToPort(pin)) #define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) #define IO_REG_TYPE uint32_t -#define IO_REG_ASM +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR #define DIRECT_READ(base, mask) (((*((base)+8)) & (mask)) ? 1 : 0) #define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) = (mask)) #define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+2)) = (mask)) @@ -149,7 +261,8 @@ #define PIN_TO_BASEREG(pin) (0) #define PIN_TO_BITMASK(pin) (pin) #define IO_REG_TYPE uint32_t -#define IO_REG_ASM +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR #define DIRECT_READ(base, pin) nrf_gpio_pin_read(pin) #define DIRECT_WRITE_LOW(base, pin) nrf_gpio_pin_clear(pin) #define DIRECT_WRITE_HIGH(base, pin) nrf_gpio_pin_set(pin) @@ -174,7 +287,8 @@ #define PIN_TO_BASEREG(pin) ((volatile uint32_t *)g_APinDescription[pin].ulGPIOBase) #define PIN_TO_BITMASK(pin) pin #define IO_REG_TYPE uint32_t -#define IO_REG_ASM +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR static inline __attribute__((always_inline)) IO_REG_TYPE directRead(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) @@ -236,11 +350,71 @@ void directWriteHigh(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) #define DIRECT_WRITE_LOW(base, pin) directWriteLow(base, pin) #define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(base, pin) +#elif defined(__riscv) + +/* + * Tested on highfive1 + * + * Stable results are achieved operating in the + * two high speed modes of the highfive1. It + * seems to be less reliable in slow mode. + */ +#define PIN_TO_BASEREG(pin) (0) +#define PIN_TO_BITMASK(pin) digitalPinToBitMask(pin) +#define IO_REG_TYPE uint32_t +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR + +static inline __attribute__((always_inline)) +IO_REG_TYPE directRead(IO_REG_TYPE mask) +{ + return ((GPIO_REG(GPIO_INPUT_VAL) & mask) != 0) ? 1 : 0; +} + +static inline __attribute__((always_inline)) +void directModeInput(IO_REG_TYPE mask) +{ + GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask; + GPIO_REG(GPIO_IOF_EN) &= ~mask; + + GPIO_REG(GPIO_INPUT_EN) |= mask; + GPIO_REG(GPIO_OUTPUT_EN) &= ~mask; +} + +static inline __attribute__((always_inline)) +void directModeOutput(IO_REG_TYPE mask) +{ + GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask; + GPIO_REG(GPIO_IOF_EN) &= ~mask; + + GPIO_REG(GPIO_INPUT_EN) &= ~mask; + GPIO_REG(GPIO_OUTPUT_EN) |= mask; +} + +static inline __attribute__((always_inline)) +void directWriteLow(IO_REG_TYPE mask) +{ + GPIO_REG(GPIO_OUTPUT_VAL) &= ~mask; +} + +static inline __attribute__((always_inline)) +void directWriteHigh(IO_REG_TYPE mask) +{ + GPIO_REG(GPIO_OUTPUT_VAL) |= mask; +} + +#define DIRECT_READ(base, mask) directRead(mask) +#define DIRECT_WRITE_LOW(base, mask) directWriteLow(mask) +#define DIRECT_WRITE_HIGH(base, mask) directWriteHigh(mask) +#define DIRECT_MODE_INPUT(base, mask) directModeInput(mask) +#define DIRECT_MODE_OUTPUT(base, mask) directModeOutput(mask) + #else #define PIN_TO_BASEREG(pin) (0) #define PIN_TO_BITMASK(pin) (pin) #define IO_REG_TYPE unsigned int -#define IO_REG_ASM +#define IO_REG_BASE_ATTR +#define IO_REG_MASK_ATTR #define DIRECT_READ(base, pin) digitalRead(pin) #define DIRECT_WRITE_LOW(base, pin) digitalWrite(pin, LOW) #define DIRECT_WRITE_HIGH(base, pin) digitalWrite(pin, HIGH) diff --git a/src/vendor/arduino-DHT b/src/vendor/arduino-DHT new file mode 160000 index 00000000..cd24ce3c --- /dev/null +++ b/src/vendor/arduino-DHT @@ -0,0 +1 @@ +Subproject commit cd24ce3ca32fe0caf05d6c21565c8d5605860a08 From e5de289fc6f598a1d81e6420d97cb3575e8b6984 Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 7 Jan 2018 10:53:16 -0400 Subject: [PATCH 134/296] Remove OneWire submodule --- .gitmodules | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index 47bcd249..e0acf3cd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,6 +7,3 @@ [submodule "src/vendor/arduino-DHT"] path = src/vendor/arduino-DHT url = https://github.com/markruys/arduino-DHT.git -[submodule "src/vendor/OneWire"] - path = src/vendor/OneWire - url = https://github.com/PaulStoffregen/OneWire.git From dc71fc9dcb8c890dbdce13b558a27c08b87ccf8b Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 7 Jan 2018 10:54:00 -0400 Subject: [PATCH 135/296] Remove OneWire submodule files --- src/vendor/OneWire/OneWire.cpp | 597 --------------------------------- src/vendor/OneWire/OneWire.h | 545 ------------------------------ 2 files changed, 1142 deletions(-) delete mode 100644 src/vendor/OneWire/OneWire.cpp delete mode 100644 src/vendor/OneWire/OneWire.h diff --git a/src/vendor/OneWire/OneWire.cpp b/src/vendor/OneWire/OneWire.cpp deleted file mode 100644 index 5c9945d5..00000000 --- a/src/vendor/OneWire/OneWire.cpp +++ /dev/null @@ -1,597 +0,0 @@ -/* -Copyright (c) 2007, Jim Studt (original old version - many contributors since) - -The latest version of this library may be found at: - http://www.pjrc.com/teensy/td_libs_OneWire.html - -OneWire has been maintained by Paul Stoffregen (paul@pjrc.com) since -January 2010. - -DO NOT EMAIL for technical support, especially not for ESP chips! -All project support questions must be posted on public forums -relevant to the board or chips used. If using Arduino, post on -Arduino's forum. If using ESP, post on the ESP community forums. -There is ABSOLUTELY NO TECH SUPPORT BY PRIVATE EMAIL! - -Github's issue tracker for OneWire should be used only to report -specific bugs. DO NOT request project support via Github. All -project and tech support questions must be posted on forums, not -github issues. If you experience a problem and you are not -absolutely sure it's an issue with the library, ask on a forum -first. Only use github to report issues after experts have -confirmed the issue is with OneWire rather than your project. - -Back in 2010, OneWire was in need of many bug fixes, but had -been abandoned the original author (Jim Studt). None of the known -contributors were interested in maintaining OneWire. Paul typically -works on OneWire every 6 to 12 months. Patches usually wait that -long. If anyone is interested in more actively maintaining OneWire, -please contact Paul (this is pretty much the only reason to use -private email about OneWire). - -OneWire is now very mature code. No changes other than adding -definitions for newer hardware support are anticipated. - -Version 2.3: - Unknown chip fallback mode, Roger Clark - Teensy-LC compatibility, Paul Stoffregen - Search bug fix, Love Nystrom - -Version 2.2: - Teensy 3.0 compatibility, Paul Stoffregen, paul@pjrc.com - Arduino Due compatibility, http://arduino.cc/forum/index.php?topic=141030 - Fix DS18B20 example negative temperature - Fix DS18B20 example's low res modes, Ken Butcher - Improve reset timing, Mark Tillotson - Add const qualifiers, Bertrik Sikken - Add initial value input to crc16, Bertrik Sikken - Add target_search() function, Scott Roberts - -Version 2.1: - Arduino 1.0 compatibility, Paul Stoffregen - Improve temperature example, Paul Stoffregen - DS250x_PROM example, Guillermo Lovato - PIC32 (chipKit) compatibility, Jason Dangel, dangel.jason AT gmail.com - Improvements from Glenn Trewitt: - - crc16() now works - - check_crc16() does all of calculation/checking work. - - Added read_bytes() and write_bytes(), to reduce tedious loops. - - Added ds2408 example. - Delete very old, out-of-date readme file (info is here) - -Version 2.0: Modifications by Paul Stoffregen, January 2010: -http://www.pjrc.com/teensy/td_libs_OneWire.html - Search fix from Robin James - http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27 - Use direct optimized I/O in all cases - Disable interrupts during timing critical sections - (this solves many random communication errors) - Disable interrupts during read-modify-write I/O - Reduce RAM consumption by eliminating unnecessary - variables and trimming many to 8 bits - Optimize both crc8 - table version moved to flash - -Modified to work with larger numbers of devices - avoids loop. -Tested in Arduino 11 alpha with 12 sensors. -26 Sept 2008 -- Robin James -http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27 - -Updated to work with arduino-0008 and to include skip() as of -2007/07/06. --RJL20 - -Modified to calculate the 8-bit CRC directly, avoiding the need for -the 256-byte lookup table to be loaded in RAM. Tested in arduino-0010 --- Tom Pollard, Jan 23, 2008 - -Jim Studt's original library was modified by Josh Larios. - -Tom Pollard, pollard@alum.mit.edu, contributed around May 20, 2008 - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -Much of the code was inspired by Derek Yerger's code, though I don't -think much of that remains. In any event that was.. - (copyleft) 2006 by Derek Yerger - Free to distribute freely. - -The CRC code was excerpted and inspired by the Dallas Semiconductor -sample code bearing this copyright. -//--------------------------------------------------------------------------- -// Copyright (C) 2000 Dallas Semiconductor Corporation, All Rights Reserved. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES -// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// Except as contained in this notice, the name of Dallas Semiconductor -// shall not be used except as stated in the Dallas Semiconductor -// Branding Policy. -//-------------------------------------------------------------------------- -*/ - -#include "OneWire.h" - - -OneWire::OneWire(uint8_t pin) -{ - pinMode(pin, INPUT); - bitmask = PIN_TO_BITMASK(pin); - baseReg = PIN_TO_BASEREG(pin); -#if ONEWIRE_SEARCH - reset_search(); -#endif -} - - -// Perform the onewire reset function. We will wait up to 250uS for -// the bus to come high, if it doesn't then it is broken or shorted -// and we return a 0; -// -// Returns 1 if a device asserted a presence pulse, 0 otherwise. -// -uint8_t OneWire::reset(void) -{ - IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask; - volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg; - uint8_t r; - uint8_t retries = 125; - - noInterrupts(); - DIRECT_MODE_INPUT(reg, mask); - interrupts(); - // wait until the wire is high... just in case - do { - if (--retries == 0) return 0; - delayMicroseconds(2); - } while ( !DIRECT_READ(reg, mask)); - - noInterrupts(); - DIRECT_WRITE_LOW(reg, mask); - DIRECT_MODE_OUTPUT(reg, mask); // drive output low - interrupts(); - delayMicroseconds(480); - noInterrupts(); - DIRECT_MODE_INPUT(reg, mask); // allow it to float - delayMicroseconds(70); - r = !DIRECT_READ(reg, mask); - interrupts(); - delayMicroseconds(410); - return r; -} - -// -// Write a bit. Port and bit is used to cut lookup time and provide -// more certain timing. -// -void OneWire::write_bit(uint8_t v) -{ - IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask; - volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg; - - if (v & 1) { - noInterrupts(); - DIRECT_WRITE_LOW(reg, mask); - DIRECT_MODE_OUTPUT(reg, mask); // drive output low - delayMicroseconds(10); - DIRECT_WRITE_HIGH(reg, mask); // drive output high - interrupts(); - delayMicroseconds(55); - } else { - noInterrupts(); - DIRECT_WRITE_LOW(reg, mask); - DIRECT_MODE_OUTPUT(reg, mask); // drive output low - delayMicroseconds(65); - DIRECT_WRITE_HIGH(reg, mask); // drive output high - interrupts(); - delayMicroseconds(5); - } -} - -// -// Read a bit. Port and bit is used to cut lookup time and provide -// more certain timing. -// -uint8_t OneWire::read_bit(void) -{ - IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask; - volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg; - uint8_t r; - - noInterrupts(); - DIRECT_MODE_OUTPUT(reg, mask); - DIRECT_WRITE_LOW(reg, mask); - delayMicroseconds(3); - DIRECT_MODE_INPUT(reg, mask); // let pin float, pull up will raise - delayMicroseconds(10); - r = DIRECT_READ(reg, mask); - interrupts(); - delayMicroseconds(53); - return r; -} - -// -// Write a byte. The writing code uses the active drivers to raise the -// pin high, if you need power after the write (e.g. DS18S20 in -// parasite power mode) then set 'power' to 1, otherwise the pin will -// go tri-state at the end of the write to avoid heating in a short or -// other mishap. -// -void OneWire::write(uint8_t v, uint8_t power /* = 0 */) { - uint8_t bitMask; - - for (bitMask = 0x01; bitMask; bitMask <<= 1) { - OneWire::write_bit( (bitMask & v)?1:0); - } - if ( !power) { - noInterrupts(); - DIRECT_MODE_INPUT(baseReg, bitmask); - DIRECT_WRITE_LOW(baseReg, bitmask); - interrupts(); - } -} - -void OneWire::write_bytes(const uint8_t *buf, uint16_t count, bool power /* = 0 */) { - for (uint16_t i = 0 ; i < count ; i++) - write(buf[i]); - if (!power) { - noInterrupts(); - DIRECT_MODE_INPUT(baseReg, bitmask); - DIRECT_WRITE_LOW(baseReg, bitmask); - interrupts(); - } -} - -// -// Read a byte -// -uint8_t OneWire::read() { - uint8_t bitMask; - uint8_t r = 0; - - for (bitMask = 0x01; bitMask; bitMask <<= 1) { - if ( OneWire::read_bit()) r |= bitMask; - } - return r; -} - -void OneWire::read_bytes(uint8_t *buf, uint16_t count) { - for (uint16_t i = 0 ; i < count ; i++) - buf[i] = read(); -} - -// -// Do a ROM select -// -void OneWire::select(const uint8_t rom[8]) -{ - uint8_t i; - - write(0x55); // Choose ROM - - for (i = 0; i < 8; i++) write(rom[i]); -} - -// -// Do a ROM skip -// -void OneWire::skip() -{ - write(0xCC); // Skip ROM -} - -void OneWire::depower() -{ - noInterrupts(); - DIRECT_MODE_INPUT(baseReg, bitmask); - interrupts(); -} - -#if ONEWIRE_SEARCH - -// -// You need to use this function to start a search again from the beginning. -// You do not need to do it for the first search, though you could. -// -void OneWire::reset_search() -{ - // reset the search state - LastDiscrepancy = 0; - LastDeviceFlag = FALSE; - LastFamilyDiscrepancy = 0; - for(int i = 7; ; i--) { - ROM_NO[i] = 0; - if ( i == 0) break; - } -} - -// Setup the search to find the device type 'family_code' on the next call -// to search(*newAddr) if it is present. -// -void OneWire::target_search(uint8_t family_code) -{ - // set the search state to find SearchFamily type devices - ROM_NO[0] = family_code; - for (uint8_t i = 1; i < 8; i++) - ROM_NO[i] = 0; - LastDiscrepancy = 64; - LastFamilyDiscrepancy = 0; - LastDeviceFlag = FALSE; -} - -// -// Perform a search. If this function returns a '1' then it has -// enumerated the next device and you may retrieve the ROM from the -// OneWire::address variable. If there are no devices, no further -// devices, or something horrible happens in the middle of the -// enumeration then a 0 is returned. If a new device is found then -// its address is copied to newAddr. Use OneWire::reset_search() to -// start over. -// -// --- Replaced by the one from the Dallas Semiconductor web site --- -//-------------------------------------------------------------------------- -// Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing -// search state. -// Return TRUE : device found, ROM number in ROM_NO buffer -// FALSE : device not found, end of search -// -uint8_t OneWire::search(uint8_t *newAddr, bool search_mode /* = true */) -{ - uint8_t id_bit_number; - uint8_t last_zero, rom_byte_number, search_result; - uint8_t id_bit, cmp_id_bit; - - unsigned char rom_byte_mask, search_direction; - - // initialize for search - id_bit_number = 1; - last_zero = 0; - rom_byte_number = 0; - rom_byte_mask = 1; - search_result = 0; - - // if the last call was not the last one - if (!LastDeviceFlag) - { - // 1-Wire reset - if (!reset()) - { - // reset the search - LastDiscrepancy = 0; - LastDeviceFlag = FALSE; - LastFamilyDiscrepancy = 0; - return FALSE; - } - - // issue the search command - if (search_mode == true) { - write(0xF0); // NORMAL SEARCH - } else { - write(0xEC); // CONDITIONAL SEARCH - } - - // loop to do the search - do - { - // read a bit and its complement - id_bit = read_bit(); - cmp_id_bit = read_bit(); - - // check for no devices on 1-wire - if ((id_bit == 1) && (cmp_id_bit == 1)) - break; - else - { - // all devices coupled have 0 or 1 - if (id_bit != cmp_id_bit) - search_direction = id_bit; // bit write value for search - else - { - // if this discrepancy if before the Last Discrepancy - // on a previous next then pick the same as last time - if (id_bit_number < LastDiscrepancy) - search_direction = ((ROM_NO[rom_byte_number] & rom_byte_mask) > 0); - else - // if equal to last pick 1, if not then pick 0 - search_direction = (id_bit_number == LastDiscrepancy); - - // if 0 was picked then record its position in LastZero - if (search_direction == 0) - { - last_zero = id_bit_number; - - // check for Last discrepancy in family - if (last_zero < 9) - LastFamilyDiscrepancy = last_zero; - } - } - - // set or clear the bit in the ROM byte rom_byte_number - // with mask rom_byte_mask - if (search_direction == 1) - ROM_NO[rom_byte_number] |= rom_byte_mask; - else - ROM_NO[rom_byte_number] &= ~rom_byte_mask; - - // serial number search direction write bit - write_bit(search_direction); - - // increment the byte counter id_bit_number - // and shift the mask rom_byte_mask - id_bit_number++; - rom_byte_mask <<= 1; - - // if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask - if (rom_byte_mask == 0) - { - rom_byte_number++; - rom_byte_mask = 1; - } - } - } - while(rom_byte_number < 8); // loop until through all ROM bytes 0-7 - - // if the search was successful then - if (!(id_bit_number < 65)) - { - // search successful so set LastDiscrepancy,LastDeviceFlag,search_result - LastDiscrepancy = last_zero; - - // check for last device - if (LastDiscrepancy == 0) - LastDeviceFlag = TRUE; - - search_result = TRUE; - } - } - - // if no device found then reset counters so next 'search' will be like a first - if (!search_result || !ROM_NO[0]) - { - LastDiscrepancy = 0; - LastDeviceFlag = FALSE; - LastFamilyDiscrepancy = 0; - search_result = FALSE; - } else { - for (int i = 0; i < 8; i++) newAddr[i] = ROM_NO[i]; - } - return search_result; - } - -#endif - -#if ONEWIRE_CRC -// The 1-Wire CRC scheme is described in Maxim Application Note 27: -// "Understanding and Using Cyclic Redundancy Checks with Maxim iButton Products" -// - -#if ONEWIRE_CRC8_TABLE -// This table comes from Dallas sample code where it is freely reusable, -// though Copyright (C) 2000 Dallas Semiconductor Corporation -static const uint8_t PROGMEM dscrc_table[] = { - 0, 94,188,226, 97, 63,221,131,194,156,126, 32,163,253, 31, 65, - 157,195, 33,127,252,162, 64, 30, 95, 1,227,189, 62, 96,130,220, - 35,125,159,193, 66, 28,254,160,225,191, 93, 3,128,222, 60, 98, - 190,224, 2, 92,223,129, 99, 61,124, 34,192,158, 29, 67,161,255, - 70, 24,250,164, 39,121,155,197,132,218, 56,102,229,187, 89, 7, - 219,133,103, 57,186,228, 6, 88, 25, 71,165,251,120, 38,196,154, - 101, 59,217,135, 4, 90,184,230,167,249, 27, 69,198,152,122, 36, - 248,166, 68, 26,153,199, 37,123, 58,100,134,216, 91, 5,231,185, - 140,210, 48,110,237,179, 81, 15, 78, 16,242,172, 47,113,147,205, - 17, 79,173,243,112, 46,204,146,211,141,111, 49,178,236, 14, 80, - 175,241, 19, 77,206,144,114, 44,109, 51,209,143, 12, 82,176,238, - 50,108,142,208, 83, 13,239,177,240,174, 76, 18,145,207, 45,115, - 202,148,118, 40,171,245, 23, 73, 8, 86,180,234,105, 55,213,139, - 87, 9,235,181, 54,104,138,212,149,203, 41,119,244,170, 72, 22, - 233,183, 85, 11,136,214, 52,106, 43,117,151,201, 74, 20,246,168, - 116, 42,200,150, 21, 75,169,247,182,232, 10, 84,215,137,107, 53}; - -// -// Compute a Dallas Semiconductor 8 bit CRC. These show up in the ROM -// and the registers. (note: this might better be done without to -// table, it would probably be smaller and certainly fast enough -// compared to all those delayMicrosecond() calls. But I got -// confused, so I use this table from the examples.) -// -uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len) -{ - uint8_t crc = 0; - - while (len--) { - crc = pgm_read_byte(dscrc_table + (crc ^ *addr++)); - } - return crc; -} -#else -// -// Compute a Dallas Semiconductor 8 bit CRC directly. -// this is much slower, but much smaller, than the lookup table. -// -uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len) -{ - uint8_t crc = 0; - - while (len--) { -#if defined(__AVR__) - crc = _crc_ibutton_update(crc, *addr++); -#else - uint8_t inbyte = *addr++; - for (uint8_t i = 8; i; i--) { - uint8_t mix = (crc ^ inbyte) & 0x01; - crc >>= 1; - if (mix) crc ^= 0x8C; - inbyte >>= 1; - } -#endif - } - return crc; -} -#endif - -#if ONEWIRE_CRC16 -bool OneWire::check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc) -{ - crc = ~crc16(input, len, crc); - return (crc & 0xFF) == inverted_crc[0] && (crc >> 8) == inverted_crc[1]; -} - -uint16_t OneWire::crc16(const uint8_t* input, uint16_t len, uint16_t crc) -{ -#if defined(__AVR__) - for (uint16_t i = 0 ; i < len ; i++) { - crc = _crc16_update(crc, input[i]); - } -#else - static const uint8_t oddparity[16] = - { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 }; - - for (uint16_t i = 0 ; i < len ; i++) { - // Even though we're just copying a byte from the input, - // we'll be doing 16-bit computation with it. - uint16_t cdata = input[i]; - cdata = (cdata ^ crc) & 0xff; - crc >>= 8; - - if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4]) - crc ^= 0xC001; - - cdata <<= 6; - crc ^= cdata; - cdata <<= 1; - crc ^= cdata; - } -#endif - return crc; -} -#endif - -#endif diff --git a/src/vendor/OneWire/OneWire.h b/src/vendor/OneWire/OneWire.h deleted file mode 100644 index c394f1d0..00000000 --- a/src/vendor/OneWire/OneWire.h +++ /dev/null @@ -1,545 +0,0 @@ -#ifndef OneWire_h -#define OneWire_h - -#include - -#if defined(__AVR__) -#include -#endif - -#if ARDUINO >= 100 -#include "Arduino.h" // for delayMicroseconds, digitalPinToBitMask, etc -#else -#include "WProgram.h" // for delayMicroseconds -#include "pins_arduino.h" // for digitalPinToBitMask, etc -#endif - -// You can exclude certain features from OneWire. In theory, this -// might save some space. In practice, the compiler automatically -// removes unused code (technically, the linker, using -fdata-sections -// and -ffunction-sections when compiling, and Wl,--gc-sections -// when linking), so most of these will not result in any code size -// reduction. Well, unless you try to use the missing features -// and redesign your program to not need them! ONEWIRE_CRC8_TABLE -// is the exception, because it selects a fast but large algorithm -// or a small but slow algorithm. - -// you can exclude onewire_search by defining that to 0 -#ifndef ONEWIRE_SEARCH -#define ONEWIRE_SEARCH 1 -#endif - -// You can exclude CRC checks altogether by defining this to 0 -#ifndef ONEWIRE_CRC -#define ONEWIRE_CRC 1 -#endif - -// Select the table-lookup method of computing the 8-bit CRC -// by setting this to 1. The lookup table enlarges code size by -// about 250 bytes. It does NOT consume RAM (but did in very -// old versions of OneWire). If you disable this, a slower -// but very compact algorithm is used. -#ifndef ONEWIRE_CRC8_TABLE -#define ONEWIRE_CRC8_TABLE 1 -#endif - -// You can allow 16-bit CRC checks by defining this to 1 -// (Note that ONEWIRE_CRC must also be 1.) -#ifndef ONEWIRE_CRC16 -#define ONEWIRE_CRC16 1 -#endif - -#ifndef FALSE -#define FALSE 0 -#endif -#ifndef TRUE -#define TRUE 1 -#endif - -// Platform specific I/O definitions - -#if defined(__AVR__) -#define PIN_TO_BASEREG(pin) (portInputRegister(digitalPinToPort(pin))) -#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) -#define IO_REG_TYPE uint8_t -#define IO_REG_BASE_ATTR asm("r30") -#define IO_REG_MASK_ATTR -#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0) -#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) &= ~(mask)) -#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+1)) |= (mask)) -#define DIRECT_WRITE_LOW(base, mask) ((*((base)+2)) &= ~(mask)) -#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+2)) |= (mask)) - -#elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__MK64FX512__) -#define PIN_TO_BASEREG(pin) (portOutputRegister(pin)) -#define PIN_TO_BITMASK(pin) (1) -#define IO_REG_TYPE uint8_t -#define IO_REG_BASE_ATTR -#define IO_REG_MASK_ATTR __attribute__ ((unused)) -#define DIRECT_READ(base, mask) (*((base)+512)) -#define DIRECT_MODE_INPUT(base, mask) (*((base)+640) = 0) -#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+640) = 1) -#define DIRECT_WRITE_LOW(base, mask) (*((base)+256) = 1) -#define DIRECT_WRITE_HIGH(base, mask) (*((base)+128) = 1) - -#elif defined(__MKL26Z64__) -#define PIN_TO_BASEREG(pin) (portOutputRegister(pin)) -#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) -#define IO_REG_TYPE uint8_t -#define IO_REG_BASE_ATTR -#define IO_REG_MASK_ATTR -#define DIRECT_READ(base, mask) ((*((base)+16) & (mask)) ? 1 : 0) -#define DIRECT_MODE_INPUT(base, mask) (*((base)+20) &= ~(mask)) -#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+20) |= (mask)) -#define DIRECT_WRITE_LOW(base, mask) (*((base)+8) = (mask)) -#define DIRECT_WRITE_HIGH(base, mask) (*((base)+4) = (mask)) - -#elif defined(__SAM3X8E__) || defined(__SAM3A8C__) || defined(__SAM3A4C__) -// Arduino 1.5.1 may have a bug in delayMicroseconds() on Arduino Due. -// http://arduino.cc/forum/index.php/topic,141030.msg1076268.html#msg1076268 -// If you have trouble with OneWire on Arduino Due, please check the -// status of delayMicroseconds() before reporting a bug in OneWire! -#define PIN_TO_BASEREG(pin) (&(digitalPinToPort(pin)->PIO_PER)) -#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) -#define IO_REG_TYPE uint32_t -#define IO_REG_BASE_ATTR -#define IO_REG_MASK_ATTR -#define DIRECT_READ(base, mask) (((*((base)+15)) & (mask)) ? 1 : 0) -#define DIRECT_MODE_INPUT(base, mask) ((*((base)+5)) = (mask)) -#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+4)) = (mask)) -#define DIRECT_WRITE_LOW(base, mask) ((*((base)+13)) = (mask)) -#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+12)) = (mask)) -#ifndef PROGMEM -#define PROGMEM -#endif -#ifndef pgm_read_byte -#define pgm_read_byte(addr) (*(const uint8_t *)(addr)) -#endif - -#elif defined(__PIC32MX__) -#define PIN_TO_BASEREG(pin) (portModeRegister(digitalPinToPort(pin))) -#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) -#define IO_REG_TYPE uint32_t -#define IO_REG_BASE_ATTR -#define IO_REG_MASK_ATTR -#define DIRECT_READ(base, mask) (((*(base+4)) & (mask)) ? 1 : 0) //PORTX + 0x10 -#define DIRECT_MODE_INPUT(base, mask) ((*(base+2)) = (mask)) //TRISXSET + 0x08 -#define DIRECT_MODE_OUTPUT(base, mask) ((*(base+1)) = (mask)) //TRISXCLR + 0x04 -#define DIRECT_WRITE_LOW(base, mask) ((*(base+8+1)) = (mask)) //LATXCLR + 0x24 -#define DIRECT_WRITE_HIGH(base, mask) ((*(base+8+2)) = (mask)) //LATXSET + 0x28 - -#elif defined(ARDUINO_ARCH_ESP8266) -// Special note: I depend on the ESP community to maintain these definitions and -// submit good pull requests. I can not answer any ESP questions or help you -// resolve any problems related to ESP chips. Please do not contact me and please -// DO NOT CREATE GITHUB ISSUES for ESP support. All ESP questions must be asked -// on ESP community forums. -#define PIN_TO_BASEREG(pin) ((volatile uint32_t*) GPO) -#define PIN_TO_BITMASK(pin) (1 << pin) -#define IO_REG_TYPE uint32_t -#define IO_REG_BASE_ATTR -#define IO_REG_MASK_ATTR -#define DIRECT_READ(base, mask) ((GPI & (mask)) ? 1 : 0) //GPIO_IN_ADDRESS -#define DIRECT_MODE_INPUT(base, mask) (GPE &= ~(mask)) //GPIO_ENABLE_W1TC_ADDRESS -#define DIRECT_MODE_OUTPUT(base, mask) (GPE |= (mask)) //GPIO_ENABLE_W1TS_ADDRESS -#define DIRECT_WRITE_LOW(base, mask) (GPOC = (mask)) //GPIO_OUT_W1TC_ADDRESS -#define DIRECT_WRITE_HIGH(base, mask) (GPOS = (mask)) //GPIO_OUT_W1TS_ADDRESS - -#elif defined(ARDUINO_ARCH_ESP32) -#include -#define PIN_TO_BASEREG(pin) (0) -#define PIN_TO_BITMASK(pin) (pin) -#define IO_REG_TYPE uint32_t -#define IO_REG_BASE_ATTR -#define IO_REG_MASK_ATTR - -static inline __attribute__((always_inline)) -IO_REG_TYPE directRead(IO_REG_TYPE pin) -{ - if ( pin < 32 ) - return (GPIO.in >> pin) & 0x1; - else if ( pin < 40 ) - return (GPIO.in1.val >> (pin - 32)) & 0x1; - - return 0; -} - -static inline __attribute__((always_inline)) -void directWriteLow(IO_REG_TYPE pin) -{ - if ( pin < 32 ) - GPIO.out_w1tc = ((uint32_t)1 << pin); - else if ( pin < 34 ) - GPIO.out1_w1tc.val = ((uint32_t)1 << (pin - 32)); -} - -static inline __attribute__((always_inline)) -void directWriteHigh(IO_REG_TYPE pin) -{ - if ( pin < 32 ) - GPIO.out_w1ts = ((uint32_t)1 << pin); - else if ( pin < 34 ) - GPIO.out1_w1ts.val = ((uint32_t)1 << (pin - 32)); -} - -static inline __attribute__((always_inline)) -void directModeInput(IO_REG_TYPE pin) -{ - if ( digitalPinIsValid(pin) ) - { - uint32_t rtc_reg(rtc_gpio_desc[pin].reg); - - if ( rtc_reg ) // RTC pins PULL settings - { - ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].mux); - ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].pullup | rtc_gpio_desc[pin].pulldown); - } - - if ( pin < 32 ) - GPIO.enable_w1tc = ((uint32_t)1 << pin); - else - GPIO.enable1_w1tc.val = ((uint32_t)1 << (pin - 32)); - - uint32_t pinFunction((uint32_t)2 << FUN_DRV_S); // what are the drivers? - pinFunction |= FUN_IE; // input enable but required for output as well? - pinFunction |= ((uint32_t)2 << MCU_SEL_S); - - ESP_REG(DR_REG_IO_MUX_BASE + esp32_gpioMux[pin].reg) = pinFunction; - - GPIO.pin[pin].val = 0; - } -} - -static inline __attribute__((always_inline)) -void directModeOutput(IO_REG_TYPE pin) -{ - if ( digitalPinIsValid(pin) && pin <= 33 ) // pins above 33 can be only inputs - { - uint32_t rtc_reg(rtc_gpio_desc[pin].reg); - - if ( rtc_reg ) // RTC pins PULL settings - { - ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].mux); - ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].pullup | rtc_gpio_desc[pin].pulldown); - } - - if ( pin < 32 ) - GPIO.enable_w1ts = ((uint32_t)1 << pin); - else // already validated to pins <= 33 - GPIO.enable1_w1ts.val = ((uint32_t)1 << (pin - 32)); - - uint32_t pinFunction((uint32_t)2 << FUN_DRV_S); // what are the drivers? - pinFunction |= FUN_IE; // input enable but required for output as well? - pinFunction |= ((uint32_t)2 << MCU_SEL_S); - - ESP_REG(DR_REG_IO_MUX_BASE + esp32_gpioMux[pin].reg) = pinFunction; - - GPIO.pin[pin].val = 0; - } -} - -#define DIRECT_READ(base, pin) directRead(pin) -#define DIRECT_WRITE_LOW(base, pin) directWriteLow(pin) -#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(pin) -#define DIRECT_MODE_INPUT(base, pin) directModeInput(pin) -#define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(pin) -#warning "ESP32 OneWire testing" - -#elif defined(__SAMD21G18A__) -#define PIN_TO_BASEREG(pin) portModeRegister(digitalPinToPort(pin)) -#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin)) -#define IO_REG_TYPE uint32_t -#define IO_REG_BASE_ATTR -#define IO_REG_MASK_ATTR -#define DIRECT_READ(base, mask) (((*((base)+8)) & (mask)) ? 1 : 0) -#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) = (mask)) -#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+2)) = (mask)) -#define DIRECT_WRITE_LOW(base, mask) ((*((base)+5)) = (mask)) -#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+6)) = (mask)) - -#elif defined(RBL_NRF51822) -#define PIN_TO_BASEREG(pin) (0) -#define PIN_TO_BITMASK(pin) (pin) -#define IO_REG_TYPE uint32_t -#define IO_REG_BASE_ATTR -#define IO_REG_MASK_ATTR -#define DIRECT_READ(base, pin) nrf_gpio_pin_read(pin) -#define DIRECT_WRITE_LOW(base, pin) nrf_gpio_pin_clear(pin) -#define DIRECT_WRITE_HIGH(base, pin) nrf_gpio_pin_set(pin) -#define DIRECT_MODE_INPUT(base, pin) nrf_gpio_cfg_input(pin, NRF_GPIO_PIN_NOPULL) -#define DIRECT_MODE_OUTPUT(base, pin) nrf_gpio_cfg_output(pin) - -#elif defined(__arc__) /* Arduino101/Genuino101 specifics */ - -#include "scss_registers.h" -#include "portable.h" -#include "avr/pgmspace.h" - -#define GPIO_ID(pin) (g_APinDescription[pin].ulGPIOId) -#define GPIO_TYPE(pin) (g_APinDescription[pin].ulGPIOType) -#define GPIO_BASE(pin) (g_APinDescription[pin].ulGPIOBase) -#define DIR_OFFSET_SS 0x01 -#define DIR_OFFSET_SOC 0x04 -#define EXT_PORT_OFFSET_SS 0x0A -#define EXT_PORT_OFFSET_SOC 0x50 - -/* GPIO registers base address */ -#define PIN_TO_BASEREG(pin) ((volatile uint32_t *)g_APinDescription[pin].ulGPIOBase) -#define PIN_TO_BITMASK(pin) pin -#define IO_REG_TYPE uint32_t -#define IO_REG_BASE_ATTR -#define IO_REG_MASK_ATTR - -static inline __attribute__((always_inline)) -IO_REG_TYPE directRead(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) -{ - IO_REG_TYPE ret; - if (SS_GPIO == GPIO_TYPE(pin)) { - ret = READ_ARC_REG(((IO_REG_TYPE)base + EXT_PORT_OFFSET_SS)); - } else { - ret = MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, EXT_PORT_OFFSET_SOC); - } - return ((ret >> GPIO_ID(pin)) & 0x01); -} - -static inline __attribute__((always_inline)) -void directModeInput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) -{ - if (SS_GPIO == GPIO_TYPE(pin)) { - WRITE_ARC_REG(READ_ARC_REG((((IO_REG_TYPE)base) + DIR_OFFSET_SS)) & ~(0x01 << GPIO_ID(pin)), - ((IO_REG_TYPE)(base) + DIR_OFFSET_SS)); - } else { - MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) &= ~(0x01 << GPIO_ID(pin)); - } -} - -static inline __attribute__((always_inline)) -void directModeOutput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) -{ - if (SS_GPIO == GPIO_TYPE(pin)) { - WRITE_ARC_REG(READ_ARC_REG(((IO_REG_TYPE)(base) + DIR_OFFSET_SS)) | (0x01 << GPIO_ID(pin)), - ((IO_REG_TYPE)(base) + DIR_OFFSET_SS)); - } else { - MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) |= (0x01 << GPIO_ID(pin)); - } -} - -static inline __attribute__((always_inline)) -void directWriteLow(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) -{ - if (SS_GPIO == GPIO_TYPE(pin)) { - WRITE_ARC_REG(READ_ARC_REG(base) & ~(0x01 << GPIO_ID(pin)), base); - } else { - MMIO_REG_VAL(base) &= ~(0x01 << GPIO_ID(pin)); - } -} - -static inline __attribute__((always_inline)) -void directWriteHigh(volatile IO_REG_TYPE *base, IO_REG_TYPE pin) -{ - if (SS_GPIO == GPIO_TYPE(pin)) { - WRITE_ARC_REG(READ_ARC_REG(base) | (0x01 << GPIO_ID(pin)), base); - } else { - MMIO_REG_VAL(base) |= (0x01 << GPIO_ID(pin)); - } -} - -#define DIRECT_READ(base, pin) directRead(base, pin) -#define DIRECT_MODE_INPUT(base, pin) directModeInput(base, pin) -#define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(base, pin) -#define DIRECT_WRITE_LOW(base, pin) directWriteLow(base, pin) -#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(base, pin) - -#elif defined(__riscv) - -/* - * Tested on highfive1 - * - * Stable results are achieved operating in the - * two high speed modes of the highfive1. It - * seems to be less reliable in slow mode. - */ -#define PIN_TO_BASEREG(pin) (0) -#define PIN_TO_BITMASK(pin) digitalPinToBitMask(pin) -#define IO_REG_TYPE uint32_t -#define IO_REG_BASE_ATTR -#define IO_REG_MASK_ATTR - -static inline __attribute__((always_inline)) -IO_REG_TYPE directRead(IO_REG_TYPE mask) -{ - return ((GPIO_REG(GPIO_INPUT_VAL) & mask) != 0) ? 1 : 0; -} - -static inline __attribute__((always_inline)) -void directModeInput(IO_REG_TYPE mask) -{ - GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask; - GPIO_REG(GPIO_IOF_EN) &= ~mask; - - GPIO_REG(GPIO_INPUT_EN) |= mask; - GPIO_REG(GPIO_OUTPUT_EN) &= ~mask; -} - -static inline __attribute__((always_inline)) -void directModeOutput(IO_REG_TYPE mask) -{ - GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask; - GPIO_REG(GPIO_IOF_EN) &= ~mask; - - GPIO_REG(GPIO_INPUT_EN) &= ~mask; - GPIO_REG(GPIO_OUTPUT_EN) |= mask; -} - -static inline __attribute__((always_inline)) -void directWriteLow(IO_REG_TYPE mask) -{ - GPIO_REG(GPIO_OUTPUT_VAL) &= ~mask; -} - -static inline __attribute__((always_inline)) -void directWriteHigh(IO_REG_TYPE mask) -{ - GPIO_REG(GPIO_OUTPUT_VAL) |= mask; -} - -#define DIRECT_READ(base, mask) directRead(mask) -#define DIRECT_WRITE_LOW(base, mask) directWriteLow(mask) -#define DIRECT_WRITE_HIGH(base, mask) directWriteHigh(mask) -#define DIRECT_MODE_INPUT(base, mask) directModeInput(mask) -#define DIRECT_MODE_OUTPUT(base, mask) directModeOutput(mask) - -#else -#define PIN_TO_BASEREG(pin) (0) -#define PIN_TO_BITMASK(pin) (pin) -#define IO_REG_TYPE unsigned int -#define IO_REG_BASE_ATTR -#define IO_REG_MASK_ATTR -#define DIRECT_READ(base, pin) digitalRead(pin) -#define DIRECT_WRITE_LOW(base, pin) digitalWrite(pin, LOW) -#define DIRECT_WRITE_HIGH(base, pin) digitalWrite(pin, HIGH) -#define DIRECT_MODE_INPUT(base, pin) pinMode(pin,INPUT) -#define DIRECT_MODE_OUTPUT(base, pin) pinMode(pin,OUTPUT) -#warning "OneWire. Fallback mode. Using API calls for pinMode,digitalRead and digitalWrite. Operation of this library is not guaranteed on this architecture." - -#endif - - -class OneWire -{ - private: - IO_REG_TYPE bitmask; - volatile IO_REG_TYPE *baseReg; - -#if ONEWIRE_SEARCH - // global search state - unsigned char ROM_NO[8]; - uint8_t LastDiscrepancy; - uint8_t LastFamilyDiscrepancy; - uint8_t LastDeviceFlag; -#endif - - public: - OneWire( uint8_t pin); - - // Perform a 1-Wire reset cycle. Returns 1 if a device responds - // with a presence pulse. Returns 0 if there is no device or the - // bus is shorted or otherwise held low for more than 250uS - uint8_t reset(void); - - // Issue a 1-Wire rom select command, you do the reset first. - void select(const uint8_t rom[8]); - - // Issue a 1-Wire rom skip command, to address all on bus. - void skip(void); - - // Write a byte. If 'power' is one then the wire is held high at - // the end for parasitically powered devices. You are responsible - // for eventually depowering it by calling depower() or doing - // another read or write. - void write(uint8_t v, uint8_t power = 0); - - void write_bytes(const uint8_t *buf, uint16_t count, bool power = 0); - - // Read a byte. - uint8_t read(void); - - void read_bytes(uint8_t *buf, uint16_t count); - - // Write a bit. The bus is always left powered at the end, see - // note in write() about that. - void write_bit(uint8_t v); - - // Read a bit. - uint8_t read_bit(void); - - // Stop forcing power onto the bus. You only need to do this if - // you used the 'power' flag to write() or used a write_bit() call - // and aren't about to do another read or write. You would rather - // not leave this powered if you don't have to, just in case - // someone shorts your bus. - void depower(void); - -#if ONEWIRE_SEARCH - // Clear the search state so that if will start from the beginning again. - void reset_search(); - - // Setup the search to find the device type 'family_code' on the next call - // to search(*newAddr) if it is present. - void target_search(uint8_t family_code); - - // Look for the next device. Returns 1 if a new address has been - // returned. A zero might mean that the bus is shorted, there are - // no devices, or you have already retrieved all of them. It - // might be a good idea to check the CRC to make sure you didn't - // get garbage. The order is deterministic. You will always get - // the same devices in the same order. - uint8_t search(uint8_t *newAddr, bool search_mode = true); -#endif - -#if ONEWIRE_CRC - // Compute a Dallas Semiconductor 8 bit CRC, these are used in the - // ROM and scratchpad registers. - static uint8_t crc8(const uint8_t *addr, uint8_t len); - -#if ONEWIRE_CRC16 - // Compute the 1-Wire CRC16 and compare it against the received CRC. - // Example usage (reading a DS2408): - // // Put everything in a buffer so we can compute the CRC easily. - // uint8_t buf[13]; - // buf[0] = 0xF0; // Read PIO Registers - // buf[1] = 0x88; // LSB address - // buf[2] = 0x00; // MSB address - // WriteBytes(net, buf, 3); // Write 3 cmd bytes - // ReadBytes(net, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16 - // if (!CheckCRC16(buf, 11, &buf[11])) { - // // Handle error. - // } - // - // @param input - Array of bytes to checksum. - // @param len - How many bytes to use. - // @param inverted_crc - The two CRC16 bytes in the received data. - // This should just point into the received data, - // *not* at a 16-bit integer. - // @param crc - The crc starting value (optional) - // @return True, iff the CRC matches. - static bool check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc = 0); - - // Compute a Dallas Semiconductor 16 bit CRC. This is required to check - // the integrity of data received from many 1-Wire devices. Note that the - // CRC computed here is *not* what you'll get from the 1-Wire network, - // for two reasons: - // 1) The CRC is transmitted bitwise inverted. - // 2) Depending on the endian-ness of your processor, the binary - // representation of the two-byte return value may have a different - // byte order than the two bytes you get from 1-Wire. - // @param input - Array of bytes to checksum. - // @param len - How many bytes to use. - // @param crc - The crc starting value (optional) - // @return The CRC16, as defined by Dallas Semiconductor. - static uint16_t crc16(const uint8_t* input, uint16_t len, uint16_t crc = 0); -#endif -#endif -}; - -#endif From 99c4100c6da15b2e82d460b3357d8f17fb153289 Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 7 Jan 2018 10:54:37 -0400 Subject: [PATCH 136/296] Readd OneWire submodule cleanly --- .gitmodules | 3 +++ src/vendor/OneWire | 1 + 2 files changed, 4 insertions(+) create mode 160000 src/vendor/OneWire diff --git a/.gitmodules b/.gitmodules index e0acf3cd..47bcd249 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "src/vendor/arduino-DHT"] path = src/vendor/arduino-DHT url = https://github.com/markruys/arduino-DHT.git +[submodule "src/vendor/OneWire"] + path = src/vendor/OneWire + url = https://github.com/PaulStoffregen/OneWire.git diff --git a/src/vendor/OneWire b/src/vendor/OneWire new file mode 160000 index 00000000..ebc5f7f5 --- /dev/null +++ b/src/vendor/OneWire @@ -0,0 +1 @@ +Subproject commit ebc5f7f503b7be9c3772c3c131d86d7080cc77cf From 60c03f7a8a6d783ba1f6346ecdf6d65c7e3c556f Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 7 Jan 2018 14:17:17 -0400 Subject: [PATCH 137/296] Ignore backslashes that don't escape anything --- lib/dino/tx_rx/serial.rb | 1 + src/lib/Dino.cpp | 12 ++++++++---- src/lib/Dino.h | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/dino/tx_rx/serial.rb b/lib/dino/tx_rx/serial.rb index 7a2adca1..98968075 100644 --- a/lib/dino/tx_rx/serial.rb +++ b/lib/dino/tx_rx/serial.rb @@ -68,6 +68,7 @@ def gets(timeout=0) escaped = true end else + escaped = false buff << char end return nil if (buff.empty? && !escaped) diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 9bbb92ef..e5cccba6 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -14,9 +14,9 @@ Dino::Dino(){ void Dino::parse(byte c) { if ((c == '\n') || (c == '\\')) { // If last char was a \, this \ or \n is escaped. - if(backslash){ + if(escaping){ append(c); - backslash = false; + escaping = false; } // If EOL, process and reset. @@ -28,13 +28,14 @@ void Dino::parse(byte c) { } // Backslash is the escape character. - else if (c == '\\') backslash = true; + else if (c == '\\') escaping = true; } // If fragment delimiter, terminate current fragment and move to next. // Unless we're in the auxillary message fragment, then just append. else if (c == '.') { if (fragmentIndex < 3) { + escaping = false; append('\0'); fragmentIndex++; charIndex = 0; @@ -44,7 +45,10 @@ void Dino::parse(byte c) { } // Else just append the character. - else append(c); + else { + escaping = false; + append(c); + } } void Dino::append(byte c) { diff --git a/src/lib/Dino.h b/src/lib/Dino.h index e4c92168..0a620590 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -97,7 +97,7 @@ class Dino { byte *messageFragments[4]; byte fragmentIndex; int charIndex; - boolean backslash; + boolean escaping; void append(byte c); void process(); From e3ca5f0b7e6479b5a3f8921860f13ef4f02ab7c1 Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 7 Jan 2018 15:28:32 -0400 Subject: [PATCH 138/296] No more sprintf,dtostrf. Pass a Stream class to dino. --- examples/dht/dht.rb | 19 +++++++-------- lib/dino/components/dht.rb | 43 +++++++++++++--------------------- lib/dino/components/ds18b20.rb | 2 +- src/dino_serial.ino | 29 ++++++++++------------- src/lib/Dino.cpp | 43 +++++++++++++++------------------- src/lib/Dino.h | 17 ++++++++------ src/lib/DinoCoreIO.cpp | 20 ++++++++++++---- src/lib/DinoDHT.cpp | 31 ++++++++++-------------- src/lib/DinoI2C.cpp | 39 ++++++++++++++++-------------- src/lib/DinoOneWire.cpp | 10 ++++---- src/lib/DinoSPI.cpp | 17 +++++--------- src/lib/DinoShift.cpp | 17 +++++--------- 12 files changed, 133 insertions(+), 154 deletions(-) diff --git a/examples/dht/dht.rb b/examples/dht/dht.rb index dc556e03..e08d0d17 100644 --- a/examples/dht/dht.rb +++ b/examples/dht/dht.rb @@ -5,16 +5,15 @@ require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) +dht = Dino::Components::DHT.new(pin: 7, board: board) -# The temperature and humidity functions of the DHT sensors are -# modelled separately, but both isntances can be set up on the same pin. -temp = Dino::Components::DHT::Temperature.new(pin: 4, board: board) -humidity = Dino::Components::DHT::Humidity.new(pin: 4, board: board) - -temp.read do |temperature| - puts "The temperature is #{temperature} degrees C" +# The DHT class pre-processes raw data from the board. When it reaches callbacks +# it's already hash of :temperature and :humidity keys, both with Float values. +dht.add_callback do |data| + puts "The temperature is #{data[:temperature]} degrees Celsius" + puts "The relative humidity is #{data[:humidity]}%" end -humidity.read do |humidity| - puts "The relative humidity is #{humidity}%" -end +# Read it every 10 seconds. +dht.poll(10) +sleep diff --git a/lib/dino/components/dht.rb b/lib/dino/components/dht.rb index 872e54b1..72a22d2d 100644 --- a/lib/dino/components/dht.rb +++ b/lib/dino/components/dht.rb @@ -1,36 +1,25 @@ module Dino module Components - module DHT - class Temperature - include Setup::SinglePin - include Setup::Input - include Mixins::Poller + class DHT + include Setup::SinglePin + include Setup::Input + include Mixins::Poller - def _read - board.dht_read(self.pin, 0) - end - - def update(data) - return unless data.match /T/ - data.gsub!('T', '') - super(data.to_f) - end + def after_initialize(options={}) + super(options) if defined?(super) + @state = {temperature: nil, humidity: nil} end - class Humidity - include Setup::SinglePin - include Setup::Input - include Mixins::Poller - - def _read - board.dht_read(self.pin, 1) - end + def _read + board.dht_read(self.pin) + end - def update(data) - return unless data.match /H/ - data.gsub!('H', '') - super(data.to_f) - end + # Process raw data from the board before running Callbacks#update. + # super will write to @state after running callbacks. + def update(data) + t, h = data.split(",") + reading = { temperature: t.to_f, humidity: h.to_f } + super(reading) end end end diff --git a/lib/dino/components/ds18b20.rb b/lib/dino/components/ds18b20.rb index 0ee1d385..1651b74e 100644 --- a/lib/dino/components/ds18b20.rb +++ b/lib/dino/components/ds18b20.rb @@ -6,7 +6,7 @@ class DS18B20 include Mixins::Poller def _read - board.ds18b20_read(self.pin, 0) + board.ds18b20_read(self.pin) end end end diff --git a/src/dino_serial.ino b/src/dino_serial.ino index 9869158f..c7741ae6 100644 --- a/src/dino_serial.ino +++ b/src/dino_serial.ino @@ -2,32 +2,29 @@ Dino dino; -// Use 'serial' to reference the right interface depending on the device. +// Use 'serial' as a pointer to the serial interface depending on device. // Uses native USB connection on the Due by default. #if defined(__SAM3X8E__) - Serial_ &serial = SerialUSB; + Serial_* serial = &SerialUSB; #elif defined(__AVR_ATmega32U4__) - Serial_ &serial = Serial; + Serial_* serial = &Serial; #elif defined(__AVR_ATtiny85__) - TinyDebugSerial &serial = Serial; + TinyDebugSerial* serial = &Serial; #else - HardwareSerial &serial = Serial; + HardwareSerial* serial = &Serial; #endif -// Dino.h doesn't handle TXRX. Create a callback so it can write to serial. -void writeResponse(char *response) { serial.print(response); } -void (*writeCallback)(char *str) = writeResponse; - void setup() { - serial.begin(115200); + serial->begin(115200); // Wait for Leonardo serial port to connect. #if defined(__AVR_ATmega32U4__) while(!serial); #endif - dino.setupWrite(writeCallback); + // Pass the serial pointer to dino, so it can write too. + dino.setOutputStream(serial); } @@ -38,16 +35,16 @@ long lastRcv = micros(); long rcvWindow = 1000000; void acknowledge() { - serial.print("RCV:"); - serial.print(rcvBytes); - serial.print("\n"); + serial->print("RCV:"); + serial->print(rcvBytes); + serial->print("\n"); rcvBytes = 0; } void loop() { - while(serial.available() > 0) { - dino.parse(serial.read()); + while(serial->available() > 0) { + dino.parse(serial->read()); // Acknowledge when we've received as many bytes as the serial input buffer. lastRcv = micros(); diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index e5cccba6..3f7b6ab8 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -8,9 +8,10 @@ Dino::Dino(){ messageFragments[1] = pinStr; messageFragments[2] = valStr; messageFragments[3] = auxMsg; - reset(); + resetState(); } + void Dino::parse(byte c) { if ((c == '\n') || (c == '\\')) { // If last char was a \, this \ or \n is escaped. @@ -59,7 +60,6 @@ void Dino::process() { cmd = atoi((char *)cmdStr); pin = atoi((char *)pinStr); val = atoi((char *)valStr); - response[0] = '\0'; #ifdef debug Serial.print("Received - Command: "); Serial.print(cmdStr); @@ -155,27 +155,16 @@ void Dino::process() { default: break; } - // Write the response. - if (response[0] != '\0') writeResponse(); - #ifdef debug Serial.print("Responded with - "); Serial.print(response); Serial.print("\n\n"); #endif } -// Let the sketch pass a function that it wants us to use to write output. -// Store it as a callback and call that when we need to write. -void Dino::setupWrite(void (*writeCallback)(char *str)) { - _writeCallback = writeCallback; -} - - -// Write the response variable using the callback function from the sketch. -// Always terminate with a newline. -void Dino::writeResponse() { - _writeCallback(response); - _writeCallback("\n"); +// Expect the sketch to pass a pointer to something that inherits from Stream. +// Store it and call ->print, ->write, etc on it to send data. +void Dino::setOutputStream(Stream* callback){ + stream = callback; } @@ -214,6 +203,19 @@ void Dino::updateListeners() { // CMD = 90 void Dino::reset() { + resetState(); + + stream->print("ACK:"); + stream->print(A0); + #if defined(__SAM3X8E__) + stream->print(','); + stream->print(DAC0); + #endif + stream->print('\n'); +} + + +void Dino::resetState() { // Disable all the types of listeners. clearDigitalListeners(); clearAnalogListeners(); @@ -223,7 +225,6 @@ void Dino::reset() { #ifdef DINO_SHIFT clearShiftListeners(); #endif - heartRate = 4000; // Update digital listeners every ~4ms. analogDivider = 4; // Update analog listeners every ~16ms. registerDivider = 2; // Update register listeners every ~8ms. @@ -231,12 +232,6 @@ void Dino::reset() { charIndex = 0; loopCount = 0; lastUpdate = micros(); - - #if defined(__SAM3X8E__) - sprintf(response, "ACK:%d,%d", A0, DAC0); - #else - sprintf(response, "ACK:%d", A0); - #endif } diff --git a/src/lib/Dino.h b/src/lib/Dino.h index 0a620590..ba3473f8 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -18,7 +18,7 @@ class Dino { public: Dino(); - void setupWrite(void (*writeCallback)(char *str)); + void setOutputStream(Stream* callback); void parse(byte c); void updateListeners(); @@ -88,6 +88,7 @@ class Dino { // API access to timings, resolutions and reset. void reset (); //cmd = 90 + void resetState (); void setRegisterDivider (); //cmd = 97 void setAnalogResolution (); //cmd = 96 void setAnalogDivider (); //cmd = 97 @@ -99,12 +100,15 @@ class Dino { int charIndex; boolean escaping; void append(byte c); + + // Mostly a switch statement that decides what to run. void process(); // Parsed message storage. byte cmdStr[5]; int cmd; byte pinStr[5]; int pin; byte valStr[5]; int val; + // Scale aux message allocation based on enabled features and chip. #if !defined (__AVR_ATmega168__) #if defined(DINO_IR_OUT) @@ -120,13 +124,12 @@ class Dino { byte auxMsg[40]; #endif - // Value and response storage. - int rval; - char response[16]; + // Save a pointer to any stream so we can call ->print and ->write on it. + Stream* stream; - // Use a write callback from the main sketch to respond. - void (*_writeCallback)(char *str); - void writeResponse(); + // Storage and response func for features following the pin:rval pattern. + int rval; + void coreResponse(); // Internal timing variables and utility functions. long heartRate; diff --git a/src/lib/DinoCoreIO.cpp b/src/lib/DinoCoreIO.cpp index a18ecf94..6d01bd82 100644 --- a/src/lib/DinoCoreIO.cpp +++ b/src/lib/DinoCoreIO.cpp @@ -35,7 +35,7 @@ void Dino::dWrite() { // Read a digital input pin. 0 for LOW, 1 for HIGH. void Dino::dRead(int pin) { rval = digitalRead(pin); - sprintf(response, "%d:%d", pin, rval); + coreResponse(); #ifdef debug Serial.print("Called Dino::dRead()\n"); @@ -56,13 +56,25 @@ void Dino::aWrite() { // Read an analog input pin. 0 for LOW, up to 1023 for HIGH @ 10-bit resolution. void Dino::aRead(int pin) { rval = analogRead(pin); - sprintf(response, "%d:%d", pin, rval); + coreResponse(); #ifdef debug Serial.print("Called Dino::aRead()\n"); #endif } + +// Send current value of pin and reading value (rval), following the protocol. +// This is for core reads and listeners, but can be used for any function +// following the pin:rval pattern. Set those variables correctly before calling. +void Dino::coreResponse(){ + stream->print(pin); + stream->print(':'); + stream->print(rval); + stream->print('\n'); +} + + // CMD = 05 // Set a flag to periodically read a digital pin without further requests. // Runs around other requests in the main loop. Only sends value when changed. @@ -107,7 +119,7 @@ void Dino::updateDigitalListeners() { dRead(i); if (rval != digitalListenerValues[i]) { digitalListenerValues[i] = rval; - writeResponse(); + coreResponse(); } } } @@ -119,7 +131,7 @@ void Dino::updateAnalogListeners() { for (int i = 0; i < PIN_COUNT; i++) { if (analogListeners[i]) { aRead(i); - writeResponse(); + coreResponse(); } } } diff --git a/src/lib/DinoDHT.cpp b/src/lib/DinoDHT.cpp index 7a29e1c8..6f9e38f6 100644 --- a/src/lib/DinoDHT.cpp +++ b/src/lib/DinoDHT.cpp @@ -5,11 +5,6 @@ #include "Dino.h" #ifdef DINO_DHT -// Include dtostrf for ARM. -#if defined(__SAM3X8E__) - #include -#endif - // Include the DHT library and create an instance. #include "DHT.h" DHT dht; @@ -17,22 +12,20 @@ DHT dht; // CMD = 13 // Read a DHT sensor void Dino::dhtRead() { + // Can't access pin to check this in latest DHT library. + // Assuming safe to repeatedly call setup for now. // if (pin != dht.pin) dht.setup(pin); - float reading; - char readingBuff[10]; - char prefix; - if (val == 0) { - reading = dht.getTemperature(); - prefix = 'T'; - } else { - reading = dht.getHumidity(); - prefix = 'H'; - } - if (! isnan(reading)) { - dtostrf(reading, 6, 4, readingBuff); - sprintf(response, "%d:%c%s", pin, prefix, readingBuff); - } + + // Always read both values + float temperature = dht.getTemperature(); + float humidity = dht.getHumidity(); + + stream->print(pin); stream->print(':'); + // Send the values as a comma delimited printed decimals with 1dp precision. + stream->print(temperature, 1); stream->print(','); + stream->print(humidity, 1); + stream->print('\n'); #ifdef debug Serial.print("Called Dino::dhtRead()\n"); diff --git a/src/lib/DinoI2C.cpp b/src/lib/DinoI2C.cpp index f808f000..6052deba 100644 --- a/src/lib/DinoI2C.cpp +++ b/src/lib/DinoI2C.cpp @@ -14,7 +14,11 @@ void Dino::i2cBegin() { // I2c.setSpeed(??); // I2c.pullup(??); // I2c.timeOut(??); - sprintf(response, "%d:I2C:1", SDA); + + // This format could be better. + stream->print(SDA); stream->print(':'); + stream->print("I2C"); stream->print(':'); + stream->print('1'); stream->print('\n'); } @@ -22,7 +26,11 @@ void Dino::i2cBegin() { // Stop I2C communication. void Dino::i2cEnd() { I2c.end(); - sprintf(response, "%d:I2C:0", SDA); + + // This format could be better. + stream->print(SDA); stream->print(':'); + stream->print("I2C"); stream->print(':'); + stream->print('0'); stream->print('\n'); } @@ -43,12 +51,12 @@ void Dino::i2cScan() { address = I2c.scanOne(address); // Write whatever we get including address space end or errors. - sprintf(response, "%d:%d", SDA, address); - writeResponse(); + stream->print(SDA); stream->print(':'); + stream->print(address); stream->print('\n'); + + // Increment address before scanning again. address++; } - // Clear the response to make sure it doesn't get sent twice. - response[0] = '\0'; } @@ -89,24 +97,19 @@ void Dino::i2cRead() { I2c.read(auxMsg[0], auxMsg[1], auxMsg[2]); // Send back the SDA pin, the device address, and start register address first. - sprintf(response, "%d:%d:%d:", SDA, auxMsg[0], auxMsg[1]); - _writeCallback(response); + stream->print(SDA); stream->print(':'); + stream->print(auxMsg[0]); stream->print(':'); + stream->print(auxMsg[1]); stream->print(':'); // Send back the data bytes. uint8_t currentByte = 0; while(I2c.available()){ currentByte++; - // Append comma, but \n for last byte, then write. - if (currentByte == auxMsg[2]){ - sprintf(response, "%d\n", I2c.receive()); - } else { - sprintf(response, "%d,", I2c.receive()); - } - _writeCallback(response); - } - // Clear the response to make sure it doesn't get sent twice. - response[0] = '\0'; + // Get a byte from the I2C buffer, print it, then comma or \n if last byte. + stream->print(I2c.receive()); + stream->print((currentByte == auxMsg[2]) ? '\n' : ','); + } } #endif diff --git a/src/lib/DinoOneWire.cpp b/src/lib/DinoOneWire.cpp index fa1e6155..d157a88a 100644 --- a/src/lib/DinoOneWire.cpp +++ b/src/lib/DinoOneWire.cpp @@ -4,10 +4,6 @@ #include "Dino.h" #ifdef DINO_ONE_WIRE -// Include dtostrf for ARM. -#if defined(__SAM3X8E__) - #include -#endif #include "OneWire.h" // CMD = 15 @@ -54,8 +50,10 @@ void Dino::ds18Read() { char readingBuff[10]; if (! isnan(reading)) { - dtostrf(reading, 6, 4, readingBuff); - sprintf(response, "%d:%s", pin, readingBuff); + stream->print(pin); + stream->print(':'); + stream->print(reading, 4); + stream->print('\n'); } } diff --git a/src/lib/DinoSPI.cpp b/src/lib/DinoSPI.cpp index 35e05c5d..a5669ac2 100644 --- a/src/lib/DinoSPI.cpp +++ b/src/lib/DinoSPI.cpp @@ -79,26 +79,21 @@ void Dino::spiRead(int selectPin, int len, byte spiMode, uint32_t clockRate) { // Send data as if coming from the slave select pin so it's easy to identify. // Start with just pin number and : for now. - sprintf(response, "%d:", selectPin); - _writeCallback(response); + stream->print(selectPin); + stream->print(':'); for (int i = 1; i <= len; i++) { // Read a single byte from the register. byte reading = SPI.transfer(0x00); - // If we're on the last byte, append \n. If not, append a comma, then write. - if (i == len) { - sprintf(response, "%d\n", reading); - } else { - sprintf(response, "%d,", reading); - } - _writeCallback(response); + // Print it, then a comma or \n if it's the last byte. + stream->print(reading); + stream->print((i==len) ? '\n' : ','); } spiEnd(); - // Leave select high and clear response so main loop doesn't send anything. + // Leave select high. digitalWrite(selectPin, HIGH); - response[0] = '\0'; } // CMD = 28 diff --git a/src/lib/DinoShift.cpp b/src/lib/DinoShift.cpp index f5788dae..a3945dc0 100644 --- a/src/lib/DinoShift.cpp +++ b/src/lib/DinoShift.cpp @@ -56,25 +56,20 @@ void Dino::shiftRead(int latchPin, int len, byte dataPin, byte clockPin, byte cl // Send data as if coming from the latch pin so it's easy to identify. // Start with just pin number and : for now. - sprintf(response, "%d:", latchPin); - _writeCallback(response); + stream->print(latchPin); + stream->print(':'); for (int i = 1; i <= len; i++) { // Read a single byte from the register. byte reading = shiftIn(dataPin, clockPin, LSBFIRST); - // If we're on the last byte, append \n. If not, append a comma, then write. - if (i == len) { - sprintf(response, "%d\n", reading); - } else { - sprintf(response, "%d,", reading); - } - _writeCallback(response); + // Print it, then a comma or \n if it's the last byte. + stream->print(reading); + stream->print((i==len) ? '\n' : ','); } - // Leave latch pin high and clear response so main loop doesn't send anything. + // Leave latch pin high. digitalWrite(latchPin, HIGH); - response[0] = '\0'; } From 359adc448a581f29f06ce1d69f3c45dc2ae5ee36 Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 7 Jan 2018 15:40:41 -0400 Subject: [PATCH 139/296] Fix how coreResponse works with listeners --- src/lib/Dino.h | 7 +++---- src/lib/DinoCoreIO.cpp | 12 +++++------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/lib/Dino.h b/src/lib/Dino.h index ba3473f8..09a25326 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -49,6 +49,9 @@ class Dino { boolean analogListeners[PIN_COUNT]; boolean digitalListeners[PIN_COUNT]; byte digitalListenerValues[PIN_COUNT]; + // Storage and response func for features following the pin:rval pattern. + int rval; + void coreResponse(int p, int v); // Included Libraries void servoToggle (); //cmd = 8 @@ -127,10 +130,6 @@ class Dino { // Save a pointer to any stream so we can call ->print and ->write on it. Stream* stream; - // Storage and response func for features following the pin:rval pattern. - int rval; - void coreResponse(); - // Internal timing variables and utility functions. long heartRate; long lastUpdate; diff --git a/src/lib/DinoCoreIO.cpp b/src/lib/DinoCoreIO.cpp index 6d01bd82..5e363397 100644 --- a/src/lib/DinoCoreIO.cpp +++ b/src/lib/DinoCoreIO.cpp @@ -35,7 +35,7 @@ void Dino::dWrite() { // Read a digital input pin. 0 for LOW, 1 for HIGH. void Dino::dRead(int pin) { rval = digitalRead(pin); - coreResponse(); + coreResponse(pin, rval); #ifdef debug Serial.print("Called Dino::dRead()\n"); @@ -56,7 +56,7 @@ void Dino::aWrite() { // Read an analog input pin. 0 for LOW, up to 1023 for HIGH @ 10-bit resolution. void Dino::aRead(int pin) { rval = analogRead(pin); - coreResponse(); + coreResponse(pin, rval); #ifdef debug Serial.print("Called Dino::aRead()\n"); @@ -67,10 +67,10 @@ void Dino::aRead(int pin) { // Send current value of pin and reading value (rval), following the protocol. // This is for core reads and listeners, but can be used for any function // following the pin:rval pattern. Set those variables correctly before calling. -void Dino::coreResponse(){ - stream->print(pin); +void Dino::coreResponse(int p, int v){ + stream->print(p); stream->print(':'); - stream->print(rval); + stream->print(v); stream->print('\n'); } @@ -119,7 +119,6 @@ void Dino::updateDigitalListeners() { dRead(i); if (rval != digitalListenerValues[i]) { digitalListenerValues[i] = rval; - coreResponse(); } } } @@ -131,7 +130,6 @@ void Dino::updateAnalogListeners() { for (int i = 0; i < PIN_COUNT; i++) { if (analogListeners[i]) { aRead(i); - coreResponse(); } } } From 2384b141f02743bc8d6bec247e4b6772ebcb1223 Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 7 Jan 2018 17:17:57 -0400 Subject: [PATCH 140/296] Get ethernet and wifi? working with new Stream write method. --- src/dino_ethernet.ino | 29 ++++++----------------------- src/dino_wifi.ino | 27 +++++---------------------- 2 files changed, 11 insertions(+), 45 deletions(-) diff --git a/src/dino_ethernet.ino b/src/dino_ethernet.ino index d5de3e36..0915d5fe 100644 --- a/src/dino_ethernet.ino +++ b/src/dino_ethernet.ino @@ -10,24 +10,8 @@ int port = 3466; Dino dino; EthernetServer server(port); EthernetClient client; -char responseBuffer[65]; -// Dino.h doesn't handle TXRX. -// Setup a callback to buffer responses for writing. -void bufferResponse(char *response) { - if (strlen(responseBuffer) > 56 ) writeResponses(); - strcpy(responseBuffer, response); -} -void (*writeCallback)(char *str) = bufferResponse; - -// Write the buffered responses to the client. -void writeResponses() { - if (responseBuffer[0] != '\0') - client.write(responseBuffer); - responseBuffer[0] = '\0'; -} - void printEthernetStatus() { // Print ethernet status. Serial.print("IP Address: "); @@ -50,8 +34,8 @@ void setup() { server.begin(); printEthernetStatus(); - // Attach the write callback. - dino.setupWrite(writeCallback); + // Pass a client pointer to dino, so it can write too. + dino.setOutputStream(&client); } @@ -62,9 +46,9 @@ long lastRcv = micros(); long rcvWindow = 1000000; void acknowledge() { - client.write("RCV:"); - client.write(rcvBytes); - client.write("\n"); + client.print("RCV:"); + client.print(rcvBytes); + client.print("\n"); rcvBytes = 0; } @@ -85,10 +69,9 @@ void loop() { } // Also acknowledge when the last byte received goes outside the receive window. - if ((rcvBytes > 0) && ((micros() - lastRcv) > rcvWindow)) acknowledge(); + if ((rcvBytes > 0) && ((micros() - lastRcv) > rcvThreshold)) acknowledge(); dino.updateListeners(); - writeResponses(); } } client.stop(); diff --git a/src/dino_wifi.ino b/src/dino_wifi.ino index 17807eb1..752f0039 100644 --- a/src/dino_wifi.ino +++ b/src/dino_wifi.ino @@ -12,24 +12,8 @@ int status = WL_IDLE_STATUS; Dino dino; WiFiServer server(port); WiFiClient client; -char responseBuffer[65]; -// Dino.h doesn't handle TXRX. -// Setup a callback to buffer responses for writing. -void bufferResponse(char *response) { - if (strlen(responseBuffer) > 56 ) writeResponses(); - strcpy(responseBuffer, response); -} -void (*writeCallback)(char *str) = bufferResponse; - -// Write the buffered responses to the client. -void writeResponses() { - if (responseBuffer[0] != '\0') - client.write(responseBuffer); - responseBuffer[0] = '\0'; -} - void printWifiStatus() { Serial.print("SSID: "); Serial.println(WiFi.SSID()); @@ -59,8 +43,8 @@ void setup() { server.begin(); printWifiStatus(); - // Attach the write callback. - dino.setupWrite(writeCallback); + // Pass a client pointer to dino, so it can write too. + dino.setOutputStream(&client); } @@ -71,9 +55,9 @@ long lastRcv = micros(); long rcvWindow = 1000000; void acknowledge() { - client.write("RCV:"); - client.write(rcvBytes); - client.write("\n"); + client.print("RCV:"); + client.print(rcvBytes); + client.print("\n"); rcvBytes = 0; } @@ -98,7 +82,6 @@ void loop() { if ((rcvBytes > 0) && ((micros() - lastRcv) > rcvWindow)) acknowledge(); dino.updateListeners(); - writeResponses(); } } client.stop(); From 776b23749b686b6d739cbf76c9e274d0e926a0d3 Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 7 Jan 2018 17:34:41 -0400 Subject: [PATCH 141/296] Upgrade the ATMega168 target. Minor CLI bug fix. --- lib/dino_cli/packages.rb | 2 +- lib/dino_cli/targets.rb | 4 ++-- lib/dino_cli/targets.txt | 20 +++++++++++++------- src/lib/Dino.h | 22 +++++++++------------- src/lib/DinoServo.cpp | 10 ++++++++-- 5 files changed, 33 insertions(+), 25 deletions(-) diff --git a/lib/dino_cli/packages.rb b/lib/dino_cli/packages.rb index 5bb654fe..ddecccad 100644 --- a/lib/dino_cli/packages.rb +++ b/lib/dino_cli/packages.rb @@ -15,7 +15,7 @@ class DinoCLI::Generator }, servo: { description: "Servo support", - directive: "DINO_LCD", + directive: "DINO_SERVO", files: [ "lib/DinoServo.cpp", ] diff --git a/lib/dino_cli/targets.rb b/lib/dino_cli/targets.rb index 92d029f8..bcd81edb 100644 --- a/lib/dino_cli/targets.rb +++ b/lib/dino_cli/targets.rb @@ -8,8 +8,8 @@ class DinoCLI::Generator core: [:core], # Specific features for the old mega168 chips. - mega168: [:core, :servo, :lcd, :spi, :i2c], - + mega168: [:core, :servo, :dht, :one_wire, :ir_out, :spi, :i2c], + # ARM includes everytyhing except specific incompatibilities. arm: PACKAGES.each_key.map {|k| k} - [:serial, :tone, :ir_out, :i2c], } diff --git a/lib/dino_cli/targets.txt b/lib/dino_cli/targets.txt index b5082cda..b49870ca 100644 --- a/lib/dino_cli/targets.txt +++ b/lib/dino_cli/targets.txt @@ -28,26 +28,32 @@ This specifically targets the older ATmega168 chip used in early Arduinos. They have half the RAM and flash available, so we need to cut the sketch down. - Beyond core IO functionality, it ONLY includes the following features: - Servo, LCD, SPI, I2C + This option includes core functionality AND: + Servo (6 maxmium), DHT, OneWire, IR Out, SPI, I2C Chips: ATmega168 Boards: Duemilanove, Diecimila, Pro, Pro Mini (Later versions of these may carry an ATmega328) Note: While you can generate this sketch using any communication type, - it will only fit on the board when generated for serial communication. + it will only fit on the ATmega168 when generated for serial communication. There is not enough memory available for Wi-Fi or Ethernet. You can - try the core target to tradeoff the features for networking. + try the core target to tradeoff features for networking instead. - Note: Aux message length is always limited to 40 bytes on this chip. + You can also try to create a serial over Wi-Fi bridge with: + https://github.com/jeelabs/esp-link, if you have an ESP8266 handy. + + Note: Aux message is always limited to 264 bytes or less for the ATmega168. + Servo count is always limited to 6 (normally 12) for the ATmega168 chip. + These limits apply regardless of using this build target, as long as + a board with that chip is selected in the IDE. arm This is the same as mega, but omits libraries specifically known to be incompatible with the Atmel SAMD (ARM Cortex M0) chips. Use this when you need to load a sketch on the Arduino Due or similar boards. - This option excludes the following features: + This option includes all features EXCEPT: Serial, Tone, IR Out, I2C Chips: AT91SAM3X8E @@ -57,4 +63,4 @@ control which libraries are included at compile time by editing DinoDefines.h. All the library files (even the unused ones) are copied to the destination sketch folder to enable this. Uncomment a corresponding #define in DinoDefines.h to - include a library in the sketch. Comment it out to exclude that library. + include everything needed for that library. Comment it out to exclude the library. diff --git a/src/lib/Dino.h b/src/lib/Dino.h index 09a25326..fe47fc9a 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -108,21 +108,17 @@ class Dino { void process(); // Parsed message storage. - byte cmdStr[5]; int cmd; - byte pinStr[5]; int pin; - byte valStr[5]; int val; + byte cmdStr[4]; int cmd; + byte pinStr[4]; int pin; + byte valStr[4]; int val; // Scale aux message allocation based on enabled features and chip. - #if !defined (__AVR_ATmega168__) - #if defined(DINO_IR_OUT) - byte auxMsg[528]; - #elif defined(DINO_SHIFT) || defined(DINO_SPI) || defined (DINO_I2C) - byte auxMsg[272]; - #elif defined (DINO_LCD) - byte auxMsg[144]; - #else - byte auxMsg[48]; - #endif + #if defined(DINO_IR_OUT) && !defined (__AVR_ATmega168__) + byte auxMsg[528]; + #elif defined(DINO_SHIFT) || defined(DINO_SPI) || defined (DINO_I2C) + byte auxMsg[264]; + #elif defined (DINO_LCD) + byte auxMsg[136]; #else byte auxMsg[40]; #endif diff --git a/src/lib/DinoServo.cpp b/src/lib/DinoServo.cpp index e3735af4..195503e4 100644 --- a/src/lib/DinoServo.cpp +++ b/src/lib/DinoServo.cpp @@ -6,8 +6,14 @@ #include -// Maximum 12 servos on most boards. Could be up to 48 on Arduino Mega. -#define SERVO_COUNT 12 +// 12 servos on most boards. 6 on the ATmega168. +// Could be up to 48 on Arduino Mega. +#if defined (__AVR_ATmega168__) + #define SERVO_COUNT 6 +#else + #define SERVO_COUNT 12 +#endif + // Create an array of wrapper structs that link pins to servo objects. struct ServoWrapper{ From 1f8b60f341827507281dcc9b3f01ed1a6a55767b Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 7 Jan 2018 17:45:09 -0400 Subject: [PATCH 142/296] Fix tone timer by removing unused IRRemote.cpp. Give mega168 tone too. --- lib/dino_cli/packages.rb | 1 - lib/dino_cli/targets.rb | 7 +++---- lib/dino_cli/targets.txt | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/dino_cli/packages.rb b/lib/dino_cli/packages.rb index ddecccad..eb7c758c 100644 --- a/lib/dino_cli/packages.rb +++ b/lib/dino_cli/packages.rb @@ -72,7 +72,6 @@ class DinoCLI::Generator files: [ "lib/DinoIROut.cpp", "vendor/Arduino-IRremote/boarddefs.h", - "vendor/Arduino-IRremote/IRremote.cpp", "vendor/Arduino-IRremote/IRremote.h", "vendor/Arduino-IRremote/IRremoteInt.h", "vendor/Arduino-IRremote/irSend.cpp", diff --git a/lib/dino_cli/targets.rb b/lib/dino_cli/targets.rb index bcd81edb..9a34148d 100644 --- a/lib/dino_cli/targets.rb +++ b/lib/dino_cli/targets.rb @@ -1,15 +1,14 @@ class DinoCLI::Generator TARGETS = { # Default target always includes all packages. - # Tone temporarily disabled due to timer conflict. - mega: PACKAGES.each_key.map {|k| k} - [:tone], + mega: PACKAGES.each_key.map {|k| k}, # Core is core. core: [:core], # Specific features for the old mega168 chips. - mega168: [:core, :servo, :dht, :one_wire, :ir_out, :spi, :i2c], - + mega168: [:core, :servo, :dht, :one_wire, :ir_out, :tone, :spi, :i2c], + # ARM includes everytyhing except specific incompatibilities. arm: PACKAGES.each_key.map {|k| k} - [:serial, :tone, :ir_out, :i2c], } diff --git a/lib/dino_cli/targets.txt b/lib/dino_cli/targets.txt index b49870ca..49087ca7 100644 --- a/lib/dino_cli/targets.txt +++ b/lib/dino_cli/targets.txt @@ -29,7 +29,7 @@ This specifically targets the older ATmega168 chip used in early Arduinos. They have half the RAM and flash available, so we need to cut the sketch down. This option includes core functionality AND: - Servo (6 maxmium), DHT, OneWire, IR Out, SPI, I2C + Servo (6 maxmium), DHT, OneWire, IR Out, Tone, SPI, I2C Chips: ATmega168 Boards: Duemilanove, Diecimila, Pro, Pro Mini From 1c2335ba5a5bb2a877f28a8614cb1f2d5cd85a51 Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 8 Jan 2018 14:36:28 -0400 Subject: [PATCH 143/296] Make Board#high 1 not 255. Added Board#analog_high for that. --- lib/dino/board.rb | 5 +++-- lib/dino/components/basic/analog_output.rb | 8 +++++--- lib/dino/components/basic/digital_input.rb | 10 +++++----- lib/dino/components/basic/digital_output.rb | 3 +++ spec/lib/board_spec.rb | 13 +++++++++---- spec/lib/components/basic/analog_output_spec.rb | 7 +++---- spec/lib/components/basic/digital_input_spec.rb | 4 ++-- 7 files changed, 30 insertions(+), 20 deletions(-) diff --git a/lib/dino/board.rb b/lib/dino/board.rb index 3dce145a..60d7c575 100644 --- a/lib/dino/board.rb +++ b/lib/dino/board.rb @@ -1,6 +1,6 @@ module Dino class Board - attr_reader :high, :low, :components, :analog_zero, :dac_zero + attr_reader :high, :low, :analog_high, :components, :analog_zero, :dac_zero DIVIDERS = [1, 2, 4, 8, 16, 32, 64, 128] def initialize(io, options={}) @@ -16,7 +16,8 @@ def analog_resolution=(value) @bits = value || 8 write Dino::Message.encode(command: 96, value: @bits) @low = 0 - @high = (2 ** @bits) - 1 + @high = 1 + @analog_high = (2 ** @bits) - 1 end def analog_divider=(value) diff --git a/lib/dino/components/basic/analog_output.rb b/lib/dino/components/basic/analog_output.rb index fd7a57db..6277d163 100644 --- a/lib/dino/components/basic/analog_output.rb +++ b/lib/dino/components/basic/analog_output.rb @@ -9,10 +9,12 @@ def analog_write(value) end def write(value) - unless [board.low, board.high].include? value - analog_write(value) + if value == board.low + digital_write(board.low) + elsif value == board.analog_high + digital_write(board.high) else - digital_write(value) + analog_write(value) end end end diff --git a/lib/dino/components/basic/digital_input.rb b/lib/dino/components/basic/digital_input.rb index d0788194..912f1690 100644 --- a/lib/dino/components/basic/digital_input.rb +++ b/lib/dino/components/basic/digital_input.rb @@ -13,9 +13,6 @@ def after_initialize(options={}) _listen end - HIGH = 1 - LOW = 0 - def _read board.digital_read(self.pin) end @@ -26,15 +23,18 @@ def _listen def on_high(&block) add_callback(:high) do |data| - block.call(data) if data.to_i == HIGH + block.call(data) if data.to_i == board.high end end def on_low(&block) add_callback(:low) do |data| - block.call(data) if data.to_i == LOW + block.call(data) if data.to_i == board.low end end + + def high?; state == board.high end + def low?; state == board.low end end end end diff --git a/lib/dino/components/basic/digital_output.rb b/lib/dino/components/basic/digital_output.rb index 13c88136..3b3be84d 100644 --- a/lib/dino/components/basic/digital_output.rb +++ b/lib/dino/components/basic/digital_output.rb @@ -31,6 +31,9 @@ def toggle alias :off :low alias :on :high + + def high?; state == board.high end + def low?; state == board.low end end end end diff --git a/spec/lib/board_spec.rb b/spec/lib/board_spec.rb index 3fccbf9f..1b941ba0 100644 --- a/spec/lib/board_spec.rb +++ b/spec/lib/board_spec.rb @@ -31,10 +31,15 @@ def io_mock(methods = {}) expect(subject.dac_zero).to equal(20) end - it 'should set the analog resolution' do + it 'should set digital high and low' do board = Board.new(io_mock) expect(board.low).to equal(0) - expect(board.high).to equal(255) + expect(board.high).to equal(1) + end + + it 'should set analog high separately' do + board = Board.new(io_mock) + expect(board.analog_high).to equal(255) end end @@ -205,10 +210,10 @@ def io_mock(methods = {}) it 'should set @high and @low correctly' do subject.analog_resolution = 8 expect(subject.low).to equal(0) - expect(subject.high).to equal(255) + expect(subject.analog_high).to equal(255) subject.analog_resolution = 10 - expect(subject.high).to equal(1023) + expect(subject.analog_high).to equal(1023) end end end diff --git a/spec/lib/components/basic/analog_output_spec.rb b/spec/lib/components/basic/analog_output_spec.rb index 1d610320..ea2eaf07 100644 --- a/spec/lib/components/basic/analog_output_spec.rb +++ b/spec/lib/components/basic/analog_output_spec.rb @@ -18,13 +18,12 @@ module Basic end describe '#write' do - it 'should call #digital_write if value is HIGH' do + it 'should call #digital_write with board.high if value is board.analog_high' do expect(subject).to receive(:digital_write).with(board.high) - subject.write(board.high) + subject.write(board.analog_high) end - - it 'should call #digital_write if value is LOW' do + it 'should call #digital_write with board.low if value is board.low' do expect(subject).to receive(:digital_write).with(board.low) subject.write(board.low) end diff --git a/spec/lib/components/basic/digital_input_spec.rb b/spec/lib/components/basic/digital_input_spec.rb index e4daad23..56f62e2c 100644 --- a/spec/lib/components/basic/digital_input_spec.rb +++ b/spec/lib/components/basic/digital_input_spec.rb @@ -40,7 +40,7 @@ module Basic expect(@low_callback).to receive(:called) expect(@high_callback).not_to receive(:called) - subject.update(DigitalInput::LOW) + subject.update(board.low) end end @@ -49,7 +49,7 @@ module Basic expect(@high_callback).to receive(:called) expect(@low_callback).not_to receive(:called) - subject.update(DigitalInput::HIGH) + subject.update(board.high) end end end From 03b6bac7591bf60003370a438c9112d7e62b2229 Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 9 Jan 2018 18:27:50 -0400 Subject: [PATCH 144/296] Improve bandwidth with soft flow control. Cleanup `TxRx#synced_write` --- lib/dino/tx_rx/base.rb | 28 ++++++++++++++-------------- src/dino_ethernet.ino | 2 +- src/dino_serial.ino | 2 +- src/dino_wifi.ino | 2 +- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index 4e5970a0..f4cf87b3 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -8,9 +8,11 @@ class TCPConnectError < StandardError; end class HandshakeError < StandardError; end class RxFlushTimeout < StandardError; end + attr_accessor :sent_bytes + class Base include Observable - BOARD_BUFFER = 60 + BOARD_BUFFER = 64 HANDSHAKE_TRIES = 3 HANDSHAKE_TIMEOUT = 2 @@ -33,6 +35,7 @@ def write(message) end def handshake + @sent_bytes = 0 initialize_flow_control flush_read HANDSHAKE_TRIES.times do |retries| @@ -70,20 +73,17 @@ def flush_read end def synced_write(message) - message = message.split("") - loop do - @flow_control.synchronize do - bytes = BOARD_BUFFER - @transit_bytes - break unless bytes > 0 - - bytes = message.length if (message.length < bytes) - fragment = String.new - bytes.times { fragment << message.shift } + @sent_bytes += message.length + while message && !message.empty? + bytes = BOARD_BUFFER - transit_bytes + if bytes > 0 + fragment = message[0..(bytes-1)] + message = message[bytes..-1] io.write(fragment) - @transit_bytes = @transit_bytes + bytes + add_transit_bytes(fragment.length) + else + sleep 0.0005 end - return if message.empty? - sleep 0.005 end end @@ -93,7 +93,7 @@ def _write(message); raise "#_write should be defined in TxRx subclasses"; end def _read line = gets - line ? process_line(line) : sleep(0.005) + line ? process_line(line) : sleep(0.001) end def process_line(line) diff --git a/src/dino_ethernet.ino b/src/dino_ethernet.ino index 0915d5fe..73232469 100644 --- a/src/dino_ethernet.ino +++ b/src/dino_ethernet.ino @@ -41,7 +41,7 @@ void setup() { // Keep count of bytes as we receive them and send a dino message with how many. uint8_t rcvBytes = 0; -uint8_t rcvThreshold = 30; +uint8_t rcvThreshold = 64; long lastRcv = micros(); long rcvWindow = 1000000; diff --git a/src/dino_serial.ino b/src/dino_serial.ino index c7741ae6..c8b0b1bb 100644 --- a/src/dino_serial.ino +++ b/src/dino_serial.ino @@ -30,7 +30,7 @@ void setup() { // Keep count of bytes as we receive them and send a dino message with how many. uint8_t rcvBytes = 0; -uint8_t rcvThreshold = 30; +uint8_t rcvThreshold = 64; long lastRcv = micros(); long rcvWindow = 1000000; diff --git a/src/dino_wifi.ino b/src/dino_wifi.ino index 752f0039..6901efcf 100644 --- a/src/dino_wifi.ino +++ b/src/dino_wifi.ino @@ -50,7 +50,7 @@ void setup() { // Keep count of bytes as we receive them and send a dino message with how many. uint8_t rcvBytes = 0; -uint8_t rcvThreshold = 30; +uint8_t rcvThreshold = 64; long lastRcv = micros(); long rcvWindow = 1000000; From 2490a8d0cd0aee7c7183783d7bca05064e50bfbe Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 9 Jan 2018 18:30:00 -0400 Subject: [PATCH 145/296] Fix digital listeners always sending. Broken while removing sprintf usage. --- src/lib/DinoCoreIO.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/DinoCoreIO.cpp b/src/lib/DinoCoreIO.cpp index 5e363397..adac92f5 100644 --- a/src/lib/DinoCoreIO.cpp +++ b/src/lib/DinoCoreIO.cpp @@ -116,9 +116,10 @@ void Dino::removeListener() { void Dino::updateDigitalListeners() { for (int i = 0; i < PIN_COUNT; i++) { if (digitalListeners[i]) { - dRead(i); + rval = digitalRead(i); if (rval != digitalListenerValues[i]) { digitalListenerValues[i] = rval; + coreResponse(i, rval); } } } From 65f9bd5cc574fa2e2accba98ddd7e33d18486484 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 13 Jan 2018 13:06:00 -0400 Subject: [PATCH 146/296] Call micros only once when updating listeners. Stronger typing. --- src/lib/Dino.cpp | 12 +++--------- src/lib/Dino.h | 11 +++++------ 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 3f7b6ab8..d2c4d343 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -168,22 +168,16 @@ void Dino::setOutputStream(Stream* callback){ } -// Convenience wrapper to keep track of time since last read for listeners. -long Dino::timeSince(long event) { - long time = micros() - event; - return time; -} - - // Every heartRate microseconds, read the digital listeners and send values if changed. // Analog listeners use a divider to run at a fraction of that frequency. // Register listeners (Shift, I2C and SPI) all share a second divider value. // Analog and register listeners always send values even if not changed. // See Dino::reset for default timings. void Dino::updateListeners() { - if (timeSince(lastUpdate) > heartRate || timeSince(lastUpdate) < 0) { + unsigned long now = micros(); + if ((now - lastUpdate) > heartRate) { // Digital Listeners - lastUpdate = micros(); + lastUpdate = now; loopCount++; updateDigitalListeners(); diff --git a/src/lib/Dino.h b/src/lib/Dino.h index fe47fc9a..fd77e98f 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -127,11 +127,10 @@ class Dino { Stream* stream; // Internal timing variables and utility functions. - long heartRate; - long lastUpdate; - unsigned int loopCount; - unsigned int analogDivider; - unsigned int registerDivider; - long timeSince (long event); + unsigned long heartRate; + unsigned long lastUpdate; + byte loopCount; + byte analogDivider; + byte registerDivider; }; #endif From 78fb692bbbd8b2914f85fc631d3fd932549e0076 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 13 Jan 2018 13:07:05 -0400 Subject: [PATCH 147/296] Refactor acknowledgement out of sketch into Dino.cpp --- src/dino_ethernet.ino | 37 ++++++-------------------------- src/dino_serial.ino | 49 ++++++++----------------------------------- src/dino_wifi.ino | 39 ++++++---------------------------- src/lib/Dino.cpp | 25 ++++++++++++++++++++++ src/lib/Dino.h | 21 +++++++++++++------ 5 files changed, 61 insertions(+), 110 deletions(-) diff --git a/src/dino_ethernet.ino b/src/dino_ethernet.ino index 73232469..aec9362b 100644 --- a/src/dino_ethernet.ino +++ b/src/dino_ethernet.ino @@ -33,46 +33,21 @@ void setup() { Ethernet.begin(mac, ip); server.begin(); printEthernetStatus(); - - // Pass a client pointer to dino, so it can write too. - dino.setOutputStream(&client); } -// Keep count of bytes as we receive them and send a dino message with how many. -uint8_t rcvBytes = 0; -uint8_t rcvThreshold = 64; -long lastRcv = micros(); -long rcvWindow = 1000000; - -void acknowledge() { - client.print("RCV:"); - client.print(rcvBytes); - client.print("\n"); - rcvBytes = 0; -} - void loop() { // Listen for connections. client = server.available(); + // Pass the stream to dino so it can read/write. + dino.setOutputStream(&client); + // Handle a connection. if (client) { - while (client.connected()) { - while (client.available()){ - dino.parse(client.read()); - - // Acknowledge when we've received as many bytes as the serial input buffer. - lastRcv = micros(); - rcvBytes ++; - if (rcvBytes == rcvThreshold) acknowledge(); - } - - // Also acknowledge when the last byte received goes outside the receive window. - if ((rcvBytes > 0) && ((micros() - lastRcv) > rcvThreshold)) acknowledge(); - - dino.updateListeners(); - } + while (client.connected()) dino.run(); } + + // End the connection. client.stop(); } diff --git a/src/dino_serial.ino b/src/dino_serial.ino index c8b0b1bb..3c3eceaf 100644 --- a/src/dino_serial.ino +++ b/src/dino_serial.ino @@ -2,59 +2,28 @@ Dino dino; -// Use 'serial' as a pointer to the serial interface depending on device. -// Uses native USB connection on the Due by default. +// Define 'serial' as the serial interface on teh device that we want to use. +// Defaults to Native USB port on the Due. #if defined(__SAM3X8E__) - Serial_* serial = &SerialUSB; -#elif defined(__AVR_ATmega32U4__) - Serial_* serial = &Serial; -#elif defined(__AVR_ATtiny85__) - TinyDebugSerial* serial = &Serial; +#define serial SerialUSB +//#define serial Serial #else - HardwareSerial* serial = &Serial; +#define serial Serial #endif void setup() { - serial->begin(115200); + serial.begin(115200); // Wait for Leonardo serial port to connect. #if defined(__AVR_ATmega32U4__) while(!serial); #endif - // Pass the serial pointer to dino, so it can write too. - dino.setOutputStream(serial); + // Pass the stream to dino so it can read/write. + dino.setOutputStream(&serial); } - -// Keep count of bytes as we receive them and send a dino message with how many. -uint8_t rcvBytes = 0; -uint8_t rcvThreshold = 64; -long lastRcv = micros(); -long rcvWindow = 1000000; - -void acknowledge() { - serial->print("RCV:"); - serial->print(rcvBytes); - serial->print("\n"); - rcvBytes = 0; -} - - void loop() { - while(serial->available() > 0) { - dino.parse(serial->read()); - - // Acknowledge when we've received as many bytes as the serial input buffer. - lastRcv = micros(); - rcvBytes ++; - if (rcvBytes == rcvThreshold) acknowledge(); - } - - // Also acknowledge when the last byte received goes outside the receive window. - if ((rcvBytes > 0) && ((micros() - lastRcv) > rcvWindow)) acknowledge(); - - // Run dino's listeners. - dino.updateListeners(); + dino.run(); } diff --git a/src/dino_wifi.ino b/src/dino_wifi.ino index 6901efcf..2a48a402 100644 --- a/src/dino_wifi.ino +++ b/src/dino_wifi.ino @@ -42,47 +42,20 @@ void setup() { // Start the server. server.begin(); printWifiStatus(); - - // Pass a client pointer to dino, so it can write too. - dino.setOutputStream(&client); } - -// Keep count of bytes as we receive them and send a dino message with how many. -uint8_t rcvBytes = 0; -uint8_t rcvThreshold = 64; -long lastRcv = micros(); -long rcvWindow = 1000000; - -void acknowledge() { - client.print("RCV:"); - client.print(rcvBytes); - client.print("\n"); - rcvBytes = 0; -} - - void loop() { // Listen for connections. client = server.available(); + // Pass the stream to dino so it can read/write. + dino.setOutputStream(&client); + // Handle a connection. if (client) { - while (client.connected()) { - while (client.available()){ - dino.parse(client.read()); - - // Acknowledge when we've received as many bytes as the serial input buffer. - lastRcv = micros(); - rcvBytes ++; - if (rcvBytes == rcvThreshold) acknowledge(); - } - - // Also acknowledge when the last byte received goes outside the receive window. - if ((rcvBytes > 0) && ((micros() - lastRcv) > rcvWindow)) acknowledge(); - - dino.updateListeners(); - } + while (client.connected()) dino.run(); } + + // End the connection. client.stop(); } diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index d2c4d343..ee366a1d 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -12,6 +12,31 @@ Dino::Dino(){ } +void Dino::acknowledge() { + stream->print("RCV:"); + stream->print(rcvBytes); + stream->print("\n"); + rcvBytes = 0; +} + +void Dino::run(){ + while(stream->available() > 0) { + parse(stream->read()); + + // Acknowledge when we've received as many bytes as the serial input buffer. + lastRcv = micros(); + rcvBytes ++; + if (rcvBytes == rcvThreshold) acknowledge(); + } + + // Also acknowledge when the last byte received goes outside the receive window. + if ((rcvBytes > 0) && ((micros() - lastRcv) > rcvWindow)) acknowledge(); + + // Run dino's listeners. + updateListeners(); +} + + void Dino::parse(byte c) { if ((c == '\n') || (c == '\\')) { // If last char was a \, this \ or \n is escaped. diff --git a/src/lib/Dino.h b/src/lib/Dino.h index fd77e98f..51756a4a 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -19,15 +19,18 @@ class Dino { public: Dino(); void setOutputStream(Stream* callback); - void parse(byte c); - void updateListeners(); + void run(); private: - // Functions with a cmd value can be called through the remote API. + // Main loop functions. + void acknowledge(); + void parse(byte c); + void process(); // See explanation at top of DinoBugWorkaround.cpp void bugWorkaround(); + // Functions with a cmd value can be called through the remote API. // Core IO Functions void setMode (); //cmd = 0 void dWrite (); //cmd = 1 @@ -39,16 +42,19 @@ class Dino { void addDigitalListener (); //cmd = 5 void addAnalogListener (); //cmd = 6 void removeListener (); //cmd = 7 + void updateListeners(); void updateDigitalListeners(); void updateAnalogListeners (); void clearDigitalListeners (); void clearAnalogListeners (); + // Listener State and Storage // Array indices mapped to board pins. true = listener enabled on that pin. // Cache last value for digital listeners and only send on change. boolean analogListeners[PIN_COUNT]; boolean digitalListeners[PIN_COUNT]; byte digitalListenerValues[PIN_COUNT]; + // Storage and response func for features following the pin:rval pattern. int rval; void coreResponse(int p, int v); @@ -104,9 +110,6 @@ class Dino { boolean escaping; void append(byte c); - // Mostly a switch statement that decides what to run. - void process(); - // Parsed message storage. byte cmdStr[4]; int cmd; byte pinStr[4]; int pin; @@ -132,5 +135,11 @@ class Dino { byte loopCount; byte analogDivider; byte registerDivider; + + // Keep count of bytes as we receive them and send a dino message with how many. + uint8_t rcvBytes = 0; + uint8_t rcvThreshold = 64; + unsigned long lastRcv = micros(); + long long rcvWindow = 1000000; }; #endif From e8453ae7c0fd99bdfbee2003f22591d7c9a264f3 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 13 Jan 2018 21:47:52 -0400 Subject: [PATCH 148/296] More flow control improvements. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Handshake uses Observable, and board doesn’t acknowledge handshake bytes, so we just reset buffer to 0 after handshake. Scale tx and rx poll intervals based on ratio of successful reads/writes to rx/tx sleeps. Refactored flow control and handshake into their own modules.r --- lib/dino/board.rb | 13 +- lib/dino/components/register/select.rb | 1 - lib/dino/components/setup/input.rb | 1 - lib/dino/tx_rx.rb | 2 + lib/dino/tx_rx/base.rb | 123 ++++------------- lib/dino/tx_rx/flow_control.rb | 144 ++++++++++++++++++++ lib/dino/tx_rx/handshake.rb | 48 +++++++ lib/dino/tx_rx/serial.rb | 50 +++---- lib/dino/tx_rx/tcp.rb | 9 +- spec/lib/board_spec.rb | 14 -- spec/lib/components/register/select_spec.rb | 5 - spec/lib/components/setup/input_spec.rb | 5 - spec/lib/tx_rx/serial_spec.rb | 105 ++++++++------ spec/lib/tx_rx/tcp_spec.rb | 10 +- src/lib/Dino.cpp | 12 +- 15 files changed, 328 insertions(+), 214 deletions(-) create mode 100644 lib/dino/tx_rx/flow_control.rb create mode 100644 lib/dino/tx_rx/handshake.rb diff --git a/lib/dino/board.rb b/lib/dino/board.rb index 60d7c575..864f7661 100644 --- a/lib/dino/board.rb +++ b/lib/dino/board.rb @@ -5,11 +5,10 @@ class Board def initialize(io, options={}) @io, @components = io, [] - io.add_observer(self) - @analog_zero, @dac_zero = @io.handshake.to_s.split(",").map { |pin| pin.to_i } + + io.add_observer(self) self.analog_resolution = options[:bits] - start_read end def analog_resolution=(value) @@ -32,14 +31,6 @@ def heart_rate=(value) write Dino::Message.encode(command: 98, aux_message: value) end - def start_read - @io.read - end - - def stop_read - @io.close_read - end - def write(msg) @io.write(msg) end diff --git a/lib/dino/components/register/select.rb b/lib/dino/components/register/select.rb index 855f6814..6abe23ee 100644 --- a/lib/dino/components/register/select.rb +++ b/lib/dino/components/register/select.rb @@ -17,7 +17,6 @@ class Select def initialize_pins(options={}) super(options) if defined?(super) - board.start_read end end end diff --git a/lib/dino/components/setup/input.rb b/lib/dino/components/setup/input.rb index b5a00557..6553224b 100644 --- a/lib/dino/components/setup/input.rb +++ b/lib/dino/components/setup/input.rb @@ -15,7 +15,6 @@ def initialize_pins(options={}) super(options) self.mode = :in self.pullup = options[:pullup] - board.start_read end end end diff --git a/lib/dino/tx_rx.rb b/lib/dino/tx_rx.rb index d0399eae..a5983ad2 100644 --- a/lib/dino/tx_rx.rb +++ b/lib/dino/tx_rx.rb @@ -1,5 +1,7 @@ module Dino module TxRx + require 'dino/tx_rx/flow_control' + require 'dino/tx_rx/handshake' require 'dino/tx_rx/base' require 'dino/tx_rx/serial' require 'dino/tx_rx/tcp' diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index f4cf87b3..bf823132 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -8,130 +8,53 @@ class TCPConnectError < StandardError; end class HandshakeError < StandardError; end class RxFlushTimeout < StandardError; end - attr_accessor :sent_bytes - class Base include Observable - BOARD_BUFFER = 64 - HANDSHAKE_TRIES = 3 - HANDSHAKE_TIMEOUT = 2 - - def io - @io ||= connect + # Let the methods in FlowControl wrap subclass methods too. + def self.inherited(subclass) + subclass.prepend FlowControl end + include Handshake - def read - @thread ||= Thread.new { loop { _read } }.abort_on_exception = true - end + def read(message); raise "#read should be defined in TxRx subclasses"; end + def write(message); raise "#write should be defined in TxRx subclasses"; end - def close_read - return nil if @thread.nil? - Thread.kill(@thread) - @thread = nil - end + private - def write(message) - @write_mutex.synchronize { synced_write(message) } + def io + @io ||= connect end - def handshake - @sent_bytes = 0 - initialize_flow_control - flush_read - HANDSHAKE_TRIES.times do |retries| - begin - Timeout.timeout(HANDSHAKE_TIMEOUT) do - print "Sending handshake to: #{self.to_s}... " - write Dino::Message.encode(command: 90) - loop do - line = gets - if line && line.match(/\AACK:/) - flush_read - ignore_retry_bytes(retries) - puts "Acknowledged. Hardware ready...\n\n" - return line.split(":", 2)[1] - end - end - end - rescue Timeout::Error - print "No response, " - puts (retries + 1 < HANDSHAKE_TRIES ? "retrying..." : "exiting...") - next - end - end - raise HandshakeError, "Connected to wrong device, or device not running dino" + def io_reset + flush_read; stop_read; start_read end - private + def connect(message); raise "#connect should be defined in TxRx subclasses"; end def flush_read - Timeout.timeout(5) do - gets until gets == nil - end - rescue Timeout::Error + Timeout.timeout(5) { read until read == nil } + rescue Timeout::Error raise RxFlushTimeout "Cannot read from device, or device not running dino" end - def synced_write(message) - @sent_bytes += message.length - while message && !message.empty? - bytes = BOARD_BUFFER - transit_bytes - if bytes > 0 - fragment = message[0..(bytes-1)] - message = message[bytes..-1] - io.write(fragment) - add_transit_bytes(fragment.length) - else - sleep 0.0005 - end - end + def start_read + @thread ||= Thread.new { loop { read_and_process } }.abort_on_exception = true end - def connect(message); raise "#connect should be defined in TxRx subclasses"; end - def gets(message); raise "#gets should be defined in TxRx subclasses"; end - def _write(message); raise "#_write should be defined in TxRx subclasses"; end - - def _read - line = gets - line ? process_line(line) : sleep(0.001) + def stop_read + return nil if @thread.nil? + Thread.kill(@thread) + @thread = nil end - def process_line(line) + def read_and_process(message); raise "#read_and_process should be defined in FlowControl module"; end + + def process(line) if line.match(/\A\d+:/) pin, message = line.split(":", 2) pin && message && changed && notify_observers(pin, message) - elsif line.match(/\ARCV:/) - # This is acknowledgement from the board that bytes have been read - # out of the hardware buffer, freeing up that space. - # Subtract from the transit bytes so #synced_write can send more data. - remove_transit_bytes(line.split(/:/)[1].to_i) end end - - # Subtract bytes for failed handshakes from the total in transit bytes. - # The board likely reset, dropping these bytes, and causing the retries. - def ignore_retry_bytes(retries) - retry_bytes = Dino::Message.encode(command: 90).length * retries - remove_transit_bytes(retry_bytes) - end - - def initialize_flow_control - @flow_control ||= Mutex.new - @write_mutex ||= Mutex.new - @transit_bytes ||= 0 - end - - def transit_bytes - @flow_control.synchronize { @transit_bytes } - end - - def add_transit_bytes(value) - @flow_control.synchronize { @transit_bytes = @transit_bytes + value } - end - - def remove_transit_bytes(value) - @flow_control.synchronize { @transit_bytes = @transit_bytes - value } - end end end end diff --git a/lib/dino/tx_rx/flow_control.rb b/lib/dino/tx_rx/flow_control.rb new file mode 100644 index 00000000..d5a727b4 --- /dev/null +++ b/lib/dino/tx_rx/flow_control.rb @@ -0,0 +1,144 @@ +module Dino + module TxRx + module FlowControl + BOARD_BUFFER = 64 + SLEEP_UPDATE_INTERVAL = 0.1 + SLEEP_MIN = 0.00015625 + SLEEP_MAX = 0.01 + + def initialize(*args) + reset_flow_control + super(*args) + end + + def write(message) + add_write_call + @write_mutex.synchronize do + while message && !message.empty? + bytes = reserve_bytes(message.length) + if bytes > 0 + fragment = message[0..(bytes-1)] + message = message[bytes..-1] + super(fragment) + else + tx_wait + end + end + end + end + + # Keep the transit mutex lock for as little time as possible this way. + def reserve_bytes(length) + @transit_mutex.synchronize do + available = BOARD_BUFFER - @transit_bytes + reserved = (length > available) ? available : length + @transit_bytes += reserved + reserved + end + end + + private + + def reset_flow_control + @write_mutex ||= Mutex.new + @transit_mutex ||= Mutex.new + @tx_sleep_mutex ||= Mutex.new + @rx_sleep_mutex ||= Mutex.new + @transit_mutex.synchronize { @transit_bytes = 0 } + + @tx_sleep_mutex.synchronize do + @tx_sleep = SLEEP_MAX + @write_calls = 0 + @tx_sleep_calls = 0 + end + + @rx_sleep_mutex.synchronize do + @rx_sleep = SLEEP_MAX + @read_lines = 0 + @rx_sleep_calls = 0 + end + + @last_interval_update = Time.now + end + + def read_and_process + line = read + + if line && line.match(/\AACK:/) + # Handle handshake responses by passing to the observing HandshakeAttempt. + # Also pass self so it can detach itself when done. + changed && notify_observers(self, line.split(":", 2)[1]) + # Empty transit counter since ACK: won't also send RCV: + @transit_mutex.synchronize { @transit_bytes = 0 } + elsif line && line.match(/\ARCV:/) + remove_transit_bytes(line.split(/:/)[1].to_i) + elsif line + process(line) + else + rx_wait + end + + add_read_line if line + update_sleep_intervals + end + + def update_sleep_intervals + if (Time.now - @last_interval_update > SLEEP_UPDATE_INTERVAL) + @last_interval_update = Time.now + update_rx_sleep_interval + update_tx_sleep_interval + end + end + + def update_tx_sleep_interval + @tx_sleep_mutex.synchronize do + if (@write_calls > 0) && (@tx_sleep_calls.to_f/@write_calls.to_f > 0.1) + @tx_sleep /= 2 if (@tx_sleep > SLEEP_MIN) + else + @tx_sleep *= 2 if (@tx_sleep < SLEEP_MAX) + end + @write_calls = 0 + @tx_sleep_calls = 0 + end + end + + def update_rx_sleep_interval + @rx_sleep_mutex.synchronize do + if (@read_lines > 0) && (@read_lines.to_f/@rx_sleep_calls.to_f > 0.01) + @rx_sleep /= 2 if (@rx_sleep > SLEEP_MIN) + else + @rx_sleep *= 2 if (@rx_sleep < SLEEP_MAX) + end + @read_lines = 0 + @rx_sleep_calls = 0 + end + end + + def add_write_call + @tx_sleep_mutex.synchronize { @write_calls += 1 } + end + + def add_read_line + @rx_sleep_mutex.synchronize { @read_lines += 1 } + end + + def rx_wait + @rx_sleep_mutex.synchronize { @rx_sleep_calls += 1 } + sleep @rx_sleep + end + + def tx_wait + @tx_sleep_mutex.synchronize { @tx_sleep_calls += 1 } + sleep @tx_sleep + end + + def add_transit_bytes(value) + @transit_mutex.synchronize { @transit_bytes = @transit_bytes + value } + end + + def remove_transit_bytes(value) + @transit_mutex.synchronize { @transit_bytes = @transit_bytes - value } + end + end + end +end diff --git a/lib/dino/tx_rx/handshake.rb b/lib/dino/tx_rx/handshake.rb new file mode 100644 index 00000000..9b2361a4 --- /dev/null +++ b/lib/dino/tx_rx/handshake.rb @@ -0,0 +1,48 @@ +module Dino + module TxRx + class HandshakeAttempt + attr_reader :acknowledged, :result + def initialize + @acknowledged = false + @result = nil + end + + def update(sender, result) + @acknowledged = true + sender.delete_observer(self) + @result = result + end + end + + module Handshake + HANDSHAKE_TRIES = 3 + HANDSHAKE_TIMEOUT = 2 + + def handshake + io_reset + HANDSHAKE_TRIES.times do |retries| + begin + print "Sending handshake to: #{self.to_s}... " + self.add_observer(attempt = HandshakeAttempt.new) + write Dino::Message.encode(command: 90) + + Timeout.timeout(HANDSHAKE_TIMEOUT) do + loop do + if attempt.acknowledged + puts "Acknowledged. Hardware ready...\n\n" + return attempt.result + end + end + end + rescue Timeout::Error + self.delete_observer(attempt) + print "No response, " + puts (retries + 1 < HANDSHAKE_TRIES ? "retrying..." : "exiting...") + next + end + end + raise HandshakeError, "Connected to wrong device, or device not running dino" + end + end + end +end diff --git a/lib/dino/tx_rx/serial.rb b/lib/dino/tx_rx/serial.rb index 98968075..e5b18ad2 100644 --- a/lib/dino/tx_rx/serial.rb +++ b/lib/dino/tx_rx/serial.rb @@ -14,6 +14,31 @@ def to_s "#{@device} @ #{@baud} baud" end + def write(message) + io.write(message) + end + + def read + buff, escaped = "", false + loop do + char = io.read(1) + if ["\n", "\\"].include? char + if escaped + buff << char + escaped = false + elsif (char == "\n") + return buff + elsif (char == "\\") + escaped = true + end + else + escaped = false + buff << char + end + return nil if (buff.empty? && !escaped) + end + end + private def connect @@ -49,31 +74,6 @@ def tty_devices def on_windows? RUBY_PLATFORM.match /mswin|mingw/i end - - def _write(message) - io.write(message) - end - - def gets(timeout=0) - buff, escaped = "", false - loop do - char = io.read(1) - if ["\n", "\\"].include? char - if escaped - buff << char - escaped = false - elsif (char == "\n") - return buff - elsif (char == "\\") - escaped = true - end - else - escaped = false - buff << char - end - return nil if (buff.empty? && !escaped) - end - end end end end diff --git a/lib/dino/tx_rx/tcp.rb b/lib/dino/tx_rx/tcp.rb index f9da020a..a75ad170 100644 --- a/lib/dino/tx_rx/tcp.rb +++ b/lib/dino/tx_rx/tcp.rb @@ -12,6 +12,7 @@ def to_s end private + def connect print "Connecting to TCP at: #{self.to_s}... " connection = Timeout::timeout(10) { TCPSocket.open @host, @port } @@ -21,17 +22,17 @@ def connect raise TCPConnectError, error.message end - def _write(message) + def write(message) loop do - if IO.select(nil, [io], nil) + if IO.select(nil, [io], nil, 0) io.syswrite(message) break end end end - def gets(timeout=0.005) - IO.select([io], nil, nil, timeout) && io.gets.gsub(/\n\z/, "") + def read + IO.select([io], nil, nil, 0) && io.gets.gsub(/\n\z/, "") end end end diff --git a/spec/lib/board_spec.rb b/spec/lib/board_spec.rb index 1b941ba0..93dc81a7 100644 --- a/spec/lib/board_spec.rb +++ b/spec/lib/board_spec.rb @@ -74,20 +74,6 @@ def io_mock(methods = {}) end end - describe '#start_read' do - it 'should tell the io to read' do - expect(io_mock).to receive(:read) - subject.start_read - end - end - - describe '#stop_read' do - it 'should tell the io to read' do - expect(io_mock).to receive(:close_read) - subject.stop_read - end - end - describe '#write' do it 'should call #write on the io with the message' do expect(io_mock).to receive(:write).with('message') diff --git a/spec/lib/components/register/select_spec.rb b/spec/lib/components/register/select_spec.rb index b644780f..3a15edb3 100644 --- a/spec/lib/components/register/select_spec.rb +++ b/spec/lib/components/register/select_spec.rb @@ -13,11 +13,6 @@ module Register expect(board).to receive(:set_pin_mode).with(10, :out) subject end - - it 'should start the board reading' do - expect(board).to receive(:start_read) - subject - end end describe '#update' do diff --git a/spec/lib/components/setup/input_spec.rb b/spec/lib/components/setup/input_spec.rb index 4e7c1df8..817cdea5 100644 --- a/spec/lib/components/setup/input_spec.rb +++ b/spec/lib/components/setup/input_spec.rb @@ -32,11 +32,6 @@ class InputComponent expect(board).to receive(:set_pullup).with(subject.pin, true) InputComponent.new(options) end - - it 'should tell the board to start reading' do - expect(board).to receive(:start_read) - subject - end end end end diff --git a/spec/lib/tx_rx/serial_spec.rb b/spec/lib/tx_rx/serial_spec.rb index 1bfb8b3b..bcd017fd 100644 --- a/spec/lib/tx_rx/serial_spec.rb +++ b/spec/lib/tx_rx/serial_spec.rb @@ -2,11 +2,27 @@ module Dino describe TxRx::Serial do + def mock_tty + @mock_tty ||= "/dev/mock_serial" + end + + def mock_serial + @mock_serial ||= double("serial", read: "", write: nil) + end + + subject { + Dino::TxRx::Serial.new + } + + def io_instance + subject.send(:io) + end + describe '#connect' do it 'should use the specified device and baud rate from options hash' do - expect(::Serial).to receive(:new).with("/dev/ttyACM0", 9600).and_return(mock_serial = double) + expect(::Serial).to receive(:new).with("/dev/ttyACM0", 9600).and_return(mock_serial) txrx = Dino::TxRx::Serial.new(device: "/dev/ttyACM0", baud: 9600) - expect(txrx.io).to equal mock_serial + expect(txrx.send(:io)).to equal mock_serial end context "on windows" do @@ -16,11 +32,13 @@ module Dino Constants.redefine(:RUBY_PLATFORM, "mswin", :on => Object) # Simulate 3 COM ports with COM2 connected. - expect(subject).to receive(:tty_devices).and_return(["COM1", "COM2", "COM3"]) + expect_any_instance_of(TxRx::Serial).to receive(:tty_devices).and_return(["COM1", "COM2", "COM3"]) expect(::Serial).to receive(:new).with("COM1", TxRx::Serial::BAUD).and_raise(RubySerial::Error) - expect(::Serial).to receive(:new).with("COM2", TxRx::Serial::BAUD).and_return(mock_serial = double) + expect(::Serial).to receive(:new).with("COM2", TxRx::Serial::BAUD).and_return(mock_serial) expect(::Serial).to_not receive(:new).with("COM3", TxRx::Serial::BAUD) - expect(subject.io).to equal(mock_serial) + + txrx = TxRx::Serial.new + expect(txrx.send(:io)).to equal(mock_serial) # Set platform back to original value Constants.redefine(:RUBY_PLATFORM, original_platform, :on => Object) @@ -29,96 +47,103 @@ module Dino context "on *nix" do it 'should create a new ::Serial for the first connected tty device if none specified' do - expect(subject).to receive(:tty_devices).and_return(['/dev/ttyACM0', '/dev/tty.usbmodem1']) - # Simulate /dev/ttyACM0 connected. - expect(::Serial).to receive(:new).with('/dev/ttyACM0', TxRx::Serial::BAUD).and_return(mock_serial = double) + expect_any_instance_of(TxRx::Serial).to receive(:tty_devices).and_return(['/dev/ttyACM0', '/dev/tty.usbmodem1']) + expect(::Serial).to receive(:new).with('/dev/ttyACM0', TxRx::Serial::BAUD).and_return(mock_serial) expect(::Serial).to_not receive(:new).with('/dev/tty.usbmodem1', TxRx::Serial::BAUD) - expect(subject.io).to equal(mock_serial) - end - end - it 'should use the existing io instance if set' do - expect(subject).to receive(:tty_devices).once.and_return(['/dev/tty.ACM0']) - expect(::Serial).to receive(:new).and_return(mock_serial = double) - 3.times { subject.io } - expect(subject.io).to equal(mock_serial) + txrx = TxRx::Serial.new + expect(txrx.send(:io)).to equal(mock_serial) + end end it 'should raise SerialConnectError if it cannot connect to a board' do allow(::Serial).to receive(:new).and_raise(RubySerial::Error) - expect { subject.io }.to raise_exception Dino::TxRx::SerialConnectError + expect { TxRx::Serial.new.send(:io) }.to raise_exception Dino::TxRx::SerialConnectError + end + end + + describe '#io_reset' do + it 'should call flush read, stop read, and start read' do + expect(subject).to receive(:flush_read).and_return true + expect(subject).to receive(:stop_read).and_return true + expect(subject).to receive(:start_read).and_return true + subject.send(:io_reset) end end - describe '#_read' do + describe '#read_and_process' do it 'should notify observers on change' do - expect(subject).to receive(:gets).and_return("02:00") + expect(subject).to receive(:read).and_return("02:00") expect(subject).to receive(:changed).and_return(true) expect(subject).to receive(:notify_observers).with('02','00') - subject.send(:_read) + subject.send(:read_and_process) end it 'should not split messages into more than 2 parts on :' do - expect(subject).to receive(:gets).and_return("02:00:00") + expect(subject).to receive(:read).and_return("02:00:00") expect(subject).to receive(:changed).and_return(true) expect(subject).to receive(:notify_observers).with('02','00:00') - subject.send(:_read) + subject.send(:read_and_process) end end - describe '#read' do + describe '#start_read' do it 'should create a new thread and start looping' do expect(Thread).to receive(:new).and_yield.and_return(double("abort_on_exception=" => true)) expect(subject).to receive(:loop) - subject.read + subject.send(:start_read) end end - describe '#close_read' do + describe '#stop_read' do it 'should kill the reading thread' do subject.instance_variable_set(:@thread, mock_thread = double) expect(Thread).to receive(:kill).with(mock_thread) - subject.read - subject.close_read + subject.send(:start_read) + subject.send(:stop_read) end end describe '#write' do it 'should write to the device' do - expect(subject).to receive(:io).and_return(mock_serial = double) + expect(subject).to receive(:io).at_least(:once).and_return(mock_serial) expect(mock_serial).to receive(:write).with('a message') - subject.send(:initialize_flow_control) subject.write('a message') end - it 'should break up messages larger than the board input buffer' end - describe '#gets' do + describe '#read' do + before :each do + expect(subject).to receive(:io).at_least(:once).and_return(mock_serial) + end + it 'should read single characters until it hits a newline and strip it' do - expect(subject).to receive(:io).at_least(:once).and_return(mock_serial = double) expect(mock_serial).to receive(:read).exactly(5).times.with(1).and_return("l", "i", "n", "e", "\n") - expect(subject.send(:gets)).to eq("line") + expect(subject.send(:read)).to eq("line") end it 'should catch escaped newlines' do - expect(subject).to receive(:io).at_least(:once).and_return(mock_serial = double) expect(mock_serial).to receive(:read).exactly(7).times.with(1).and_return("l", "1", "\\", "\n", "l", "2", "\n") - expect(subject.send(:gets)).to eq("l1\nl2") + expect(subject.send(:read)).to eq("l1\nl2") end it 'should return a blank string if there is just a newline' do - expect(subject).to receive(:io).at_least(:once).and_return(mock_serial = double) expect(mock_serial).to receive(:read).exactly(1).times.with(1).and_return("\n") - expect(subject.send(:gets)).to eq("") + expect(subject.send(:read)).to eq("") end it 'should allow escaped backslashes' do - expect(subject).to receive(:io).at_least(:once).and_return(mock_serial = double) - expect(mock_serial).to receive(:read).exactly(3).times.with(1).and_return("\\", "\\", "\n") - expect(subject.send(:gets)).to eq("\\") + expect(io_instance).to receive(:read).exactly(3).times.with(1).and_return("\\", "\\", "\n") + expect(subject.send(:read)).to eq("\\") end end + + describe '#handshake' do + it 'should call #io_reset' + it 'should write again after timeout' + it 'should return the value from the ACK' + end end end diff --git a/spec/lib/tx_rx/tcp_spec.rb b/spec/lib/tx_rx/tcp_spec.rb index c610bf3a..e2424564 100644 --- a/spec/lib/tx_rx/tcp_spec.rb +++ b/spec/lib/tx_rx/tcp_spec.rb @@ -10,12 +10,12 @@ module Dino describe "#connect" do it 'should raise a TCPConnectError exception if it cannot connect to the server' do - expect { @instance.io }.to raise_exception Dino::TxRx::TCPConnectError + expect { @instance.send :io }.to raise_exception Dino::TxRx::TCPConnectError end it 'should return the TCPSocket if connected' do @server = TCPServer.new @port - expect(@instance.io).to be_a(TCPSocket) + expect(@instance.send :io).to be_a(TCPSocket) @server.close end end @@ -23,13 +23,13 @@ module Dino describe '#io' do it 'should set io to a new TCPSocket with the specified host and port' do expect(TCPSocket).to receive(:open).with(@host, @port) - @instance.io + @instance.send :io end it 'should use the existing io instance if set' do @server = TCPServer.new @port - socket = @instance.io - expect(@instance.io).to equal(socket) + socket = @instance.send :io + expect(@instance.send :io).to equal(socket) @server.close end end diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index ee366a1d..8a591d22 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -20,14 +20,17 @@ void Dino::acknowledge() { } void Dino::run(){ + boolean gotByte = false; + while(stream->available() > 0) { + gotByte = true; + rcvBytes ++; parse(stream->read()); - // Acknowledge when we've received as many bytes as the serial input buffer. - lastRcv = micros(); - rcvBytes ++; + // Acknowledge when we've received as many bytes as the serial input buffer if (rcvBytes == rcvThreshold) acknowledge(); } + if (gotByte) lastRcv = micros(); // Also acknowledge when the last byte received goes outside the receive window. if ((rcvBytes > 0) && ((micros() - lastRcv) > rcvWindow)) acknowledge(); @@ -224,6 +227,9 @@ void Dino::updateListeners() { void Dino::reset() { resetState(); + // Reset this so we never send RCV: along with ACK: + rcvBytes = 0; + stream->print("ACK:"); stream->print(A0); #if defined(__SAM3X8E__) From eeebd6b3a9a5a088d8c47f93c377319a4022cdaf Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 13 Jan 2018 22:38:56 -0400 Subject: [PATCH 149/296] Fix bug causing first servo pin used to be assigned to all objects. --- src/lib/DinoServo.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/DinoServo.cpp b/src/lib/DinoServo.cpp index 195503e4..6691e353 100644 --- a/src/lib/DinoServo.cpp +++ b/src/lib/DinoServo.cpp @@ -43,6 +43,7 @@ void Dino::servoToggle() { found = true; servos[i].servo.attach(pin); servos[i].active = true; + break; } } // If it doesn't exist, use the first inactive object. @@ -51,6 +52,7 @@ void Dino::servoToggle() { if (servos[i].active == false) { servos[i].servo.attach(pin); servos[i].active = true; + break; } } } From e316a26f64bebe40909c9ca4a800164f3cac292b Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 14 Jan 2018 01:46:54 -0400 Subject: [PATCH 150/296] Give every listener its own divider and just tick every 1ms instead. --- lib/dino/board.rb | 56 +++++++++++++------- spec/lib/board_spec.rb | 25 +++++++-- src/dino_serial.ino | 5 +- src/lib/Dino.cpp | 71 ++++++++----------------- src/lib/Dino.h | 52 ++++++++++--------- src/lib/DinoCoreIO.cpp | 114 ++++++++++++++++++++--------------------- 6 files changed, 169 insertions(+), 154 deletions(-) diff --git a/lib/dino/board.rb b/lib/dino/board.rb index 864f7661..1bb583cd 100644 --- a/lib/dino/board.rb +++ b/lib/dino/board.rb @@ -6,9 +6,8 @@ class Board def initialize(io, options={}) @io, @components = io, [] @analog_zero, @dac_zero = @io.handshake.to_s.split(",").map { |pin| pin.to_i } - io.add_observer(self) - self.analog_resolution = options[:bits] + self.analog_resolution = options[:bits] || 8 end def analog_resolution=(value) @@ -19,18 +18,6 @@ def analog_resolution=(value) @analog_high = (2 ** @bits) - 1 end - def analog_divider=(value) - unless DIVIDERS.include? value - puts "Analog divider must be in #{DIVIDERS.inspect}" - else - write Dino::Message.encode(command: 97, value: value) - end - end - - def heart_rate=(value) - write Dino::Message.encode(command: 98, aux_message: value) - end - def write(msg) @io.write(msg) end @@ -66,9 +53,9 @@ def set_pullup(pin, pullup) digital_read: '2', analog_write: '3', analog_read: '4', - digital_listen: '5', - analog_listen: '6', - stop_listener: '7', + # unused: '5', + # unused: '6', + set_listener: '7', servo_toggle: '8', servo_write: '9', # LCD '10' @@ -79,7 +66,7 @@ def set_pullup(pin, pullup) ds18b20_read: '15', # IR send: '16' tone: '17', - no_tone: '18' + no_tone: '18', } PIN_COMMANDS.each_key do |command| @@ -93,6 +80,39 @@ def tone(pin, value, duration) write Dino::Message.encode(command: PIN_COMMANDS[:tone], pin: convert_pin(pin), value: value, aux_message: duration) end + def set_listener(pin, state=:off, options={}) + mode = options[:mode] || :digital + divider = options[:divider] || 8 + + unless [:digital, :analog].include? mode + raise "Mode must be either digital or analog" + end + unless DIVIDERS.include? divider + raise "Listener divider must be in #{DIVIDERS.inspect}" + end + + # Create a bit mask for the settings we want to use. Gets sent in value. + mask = 0 + mask |= 0b10000000 if (state == :on) + mask |= 0b01000000 if (mode == :analog) + mask |= Math.log2(divider).to_i + + write Dino::Message.encode(command: PIN_COMMANDS[:set_listener], pin: convert_pin(pin), value: mask) + end + + # Implement the simpler methods by wrapping set_listener with old defaults. + def digital_listen(pin, divider=4) + set_listener(pin, :on, mode: :digital, divider: divider) + end + + def analog_listen(pin, divider=16) + set_listener(pin, :on, mode: :analog, divider: divider) + end + + def stop_listener(pin) + set_listener(pin, :off) + end + DIGITAL_REGEX = /\A\d+\z/i ANALOG_REGEX = /\A(a)\d+\z/i DAC_REGEX = /\A(dac)\d+\z/i diff --git a/spec/lib/board_spec.rb b/spec/lib/board_spec.rb index 93dc81a7..e549759e 100644 --- a/spec/lib/board_spec.rb +++ b/spec/lib/board_spec.rb @@ -161,23 +161,42 @@ def io_mock(methods = {}) end end + describe '#set_listener' do + it 'should set the right pin' do + expect(io_mock).to receive(:write).with(Dino::Message.encode command: 7, pin: 11, value: 0b00000011) + subject.set_listener(11, :off) + end + it 'should set the right mode' do + expect(io_mock).to receive(:write).with(Dino::Message.encode command: 7, pin: 11, value: 0b01000011) + subject.set_listener(11, :off, mode: :analog) + end + it 'should set the right state' do + expect(io_mock).to receive(:write).with(Dino::Message.encode command: 7, pin: 11, value: 0b11000011) + subject.set_listener(11, :on, mode: :analog) + end + it 'should set the right divider' do + expect(io_mock).to receive(:write).with(Dino::Message.encode command: 7, pin: 11, value: 0b11000000) + subject.set_listener(11, :on, mode: :analog, divider: 1) + end + end + describe '#digital_listen' do it 'should start listening for a digital signal on the given pin' do - expect(io_mock).to receive(:write).with(Dino::Message.encode command: 5, pin: 13) + expect(subject).to receive(:set_listener).with(13, :on, mode: :digital, divider: 4) subject.digital_listen(13) end end describe '#analog_listen' do it 'should start listening for an analog signal on the given pin' do - expect(io_mock).to receive(:write).with(Dino::Message.encode command: 6, pin: 13) + expect(subject).to receive(:set_listener).with(13, :on, mode: :analog, divider: 16) subject.analog_listen(13) end end describe '#stop_listener' do it 'should stop listening for any signal on the given pin' do - expect(io_mock).to receive(:write).with(Dino::Message.encode command: 7, pin: 13) + expect(subject).to receive(:set_listener).with(13, :off) subject.stop_listener(13) end end diff --git a/src/dino_serial.ino b/src/dino_serial.ino index 3c3eceaf..a89ddf72 100644 --- a/src/dino_serial.ino +++ b/src/dino_serial.ino @@ -2,8 +2,9 @@ Dino dino; -// Define 'serial' as the serial interface on teh device that we want to use. -// Defaults to Native USB port on the Due. +// Define 'serial' as the serial interface we want to use. +// Defaults to Native USB port on the Due, whatever class "Serial" is on everything else. +// Classes need to inherit from Stream to be compatible with the Dino library. #if defined(__SAM3X8E__) #define serial SerialUSB //#define serial Serial diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 8a591d22..e350c68f 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -106,9 +106,7 @@ void Dino::process() { case 2: dRead (pin); break; case 3: aWrite (); break; case 4: aRead (pin); break; - case 5: addDigitalListener (); break; - case 6: addAnalogListener (); break; - case 7: removeListener (); break; + case 7: setListener (); break; // Implemented in DinoServo.cpp #ifdef DINO_SERVO @@ -174,10 +172,8 @@ void Dino::process() { // Implemented in this file. case 90: reset (); break; - case 95: setAnalogDivider (); break; + case 95: setRegisterDivider (); break; case 96: setAnalogResolution (); break; - case 97: setAnalogDivider (); break; - case 98: setHeartRate (); break; // Should send a "feature not implemented" message as default. default: break; @@ -195,30 +191,30 @@ void Dino::setOutputStream(Stream* callback){ stream = callback; } - -// Every heartRate microseconds, read the digital listeners and send values if changed. -// Analog listeners use a divider to run at a fraction of that frequency. -// Register listeners (Shift, I2C and SPI) all share a second divider value. +// +// Every 1000 microseconds count a tick and call the listeners. +// Each core listener has its own divider, so it can read every +// 1, 2, 4, 8, 16, 32, 64 or 128 ticks, independent of the others. +// +// Register listeners are still on a global divider for now. // Analog and register listeners always send values even if not changed. -// See Dino::reset for default timings. +// Digital listeners only send values on change. +// void Dino::updateListeners() { unsigned long now = micros(); - if ((now - lastUpdate) > heartRate) { - // Digital Listeners - lastUpdate = now; - loopCount++; - updateDigitalListeners(); + if ((now - lastTick) > 1000) { + lastTick = now; + tickCount++; + + updateCoreListeners(tickCount); // Register Listeners #ifdef DINO_SHIFT - if (loopCount % registerDivider == 0) updateShiftListeners(); + if (tickCount % registerDivider == 0) updateShiftListeners(); #endif #ifdef DINO_SPI - if (loopCount % registerDivider == 0) updateSpiListeners(); + if (tickCount % registerDivider == 0) updateSpiListeners(); #endif - - // Analog Listeners - if (loopCount % analogDivider == 0) updateAnalogListeners(); } } @@ -241,22 +237,19 @@ void Dino::reset() { void Dino::resetState() { - // Disable all the types of listeners. - clearDigitalListeners(); - clearAnalogListeners(); + clearCoreListeners(); + lastActiveListener = 0; #ifdef DINO_SPI clearSpiListeners(); #endif #ifdef DINO_SHIFT clearShiftListeners(); #endif - heartRate = 4000; // Update digital listeners every ~4ms. - analogDivider = 4; // Update analog listeners every ~16ms. - registerDivider = 2; // Update register listeners every ~8ms. + registerDivider = 8; // Update register listeners every ~8ms. fragmentIndex = 0; charIndex = 0; - loopCount = 0; - lastUpdate = micros(); + tickCount = 0; + lastTick = micros(); } @@ -281,23 +274,3 @@ void Dino::setAnalogResolution() { Serial.print("Called Dino::setAnalogResolution()\n"); #endif } - - -// CMD = 97 -// Set the analog divider. Powers of 2 up to 128 are valid. -void Dino::setAnalogDivider() { - analogDivider = val; - #ifdef debug - Serial.print("Called Dino::setAnalogDivider()\n"); - #endif -} - - -// CMD = 98 -// Set the heart rate in milliseconds. Store it in microseconds. -void Dino::setHeartRate() { - heartRate = atoi((char *)auxMsg); - #ifdef debug - Serial.print("Called Dino::setHeartRate()\n"); - #endif -} diff --git a/src/lib/Dino.h b/src/lib/Dino.h index 51756a4a..81c27c96 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -39,21 +39,29 @@ class Dino { void aRead (int pin); //cmd = 4 // Core IO Listeners - void addDigitalListener (); //cmd = 5 - void addAnalogListener (); //cmd = 6 - void removeListener (); //cmd = 7 - void updateListeners(); - void updateDigitalListeners(); - void updateAnalogListeners (); - void clearDigitalListeners (); - void clearAnalogListeners (); - - // Listener State and Storage - // Array indices mapped to board pins. true = listener enabled on that pin. - // Cache last value for digital listeners and only send on change. - boolean analogListeners[PIN_COUNT]; - boolean digitalListeners[PIN_COUNT]; - byte digitalListenerValues[PIN_COUNT]; + void setListener (); //cmd = 7 + void updateListeners (); + void updateCoreListeners (byte tickCount); + void digitalListenerUpdate (byte index); + void clearCoreListeners (); + + // + // Store listeners as a 2 dimensional array where each gets 2 bytes, such that: + // + // byte 0, bit 7 : 1 for listener enabled, 0 for listener disabled. + // byte 0, bit 6 : 1 for analog listener, 0 for digital listener. + // byte 0, bit 5 : storage for digital listener state + // byte 0, bits 4-3: unused + // byte 0, bits 2-0: timing divider exponent specific to this pin, 2^0 through 2^8 + // + // byte 1 : pin number + // + byte listeners [PIN_COUNT][2]; + + // Track the highest number listener that's active. + byte lastActiveListener; + // Map 2's exponents for dividers to save time. + const byte dividerMap[8] = {1, 2, 4, 8, 16, 32, 64, 128}; // Storage and response func for features following the pin:rval pattern. int rval; @@ -100,8 +108,6 @@ class Dino { void resetState (); void setRegisterDivider (); //cmd = 97 void setAnalogResolution (); //cmd = 96 - void setAnalogDivider (); //cmd = 97 - void setHeartRate (); //cmd = 98 // Parser state storage and utility functions. byte *messageFragments[4]; @@ -111,9 +117,9 @@ class Dino { void append(byte c); // Parsed message storage. - byte cmdStr[4]; int cmd; - byte pinStr[4]; int pin; - byte valStr[4]; int val; + byte cmdStr[4]; byte cmd; + byte pinStr[4]; byte pin; + byte valStr[4]; byte val; // Scale aux message allocation based on enabled features and chip. #if defined(DINO_IR_OUT) && !defined (__AVR_ATmega168__) @@ -130,10 +136,8 @@ class Dino { Stream* stream; // Internal timing variables and utility functions. - unsigned long heartRate; - unsigned long lastUpdate; - byte loopCount; - byte analogDivider; + unsigned long lastTick; + byte tickCount; byte registerDivider; // Keep count of bytes as we receive them and send a dino message with how many. diff --git a/src/lib/DinoCoreIO.cpp b/src/lib/DinoCoreIO.cpp index adac92f5..acd18790 100644 --- a/src/lib/DinoCoreIO.cpp +++ b/src/lib/DinoCoreIO.cpp @@ -4,7 +4,6 @@ // Set a pin to output (0), or input (1). void Dino::setMode() { if (val == 0) { - removeListener(); pinMode(pin, OUTPUT); } else { @@ -74,74 +73,73 @@ void Dino::coreResponse(int p, int v){ stream->print('\n'); } - -// CMD = 05 -// Set a flag to periodically read a digital pin without further requests. -// Runs around other requests in the main loop. Only sends value when changed. -void Dino::addDigitalListener() { - removeListener(); - digitalListeners[pin] = true; - digitalListenerValues[pin] = 2; - - #ifdef debug - Serial.print("Called Dino::addDigitalListener()\n"); - #endif -} - -// CMD = 06 -// Set a flag to periodically read an analog pin without further requests. -// Runs around other requests in the main loop. Sends value on every read. -void Dino::addAnalogListener() { - removeListener(); - analogListeners[pin] = true; - - #ifdef debug - Serial.print("Called Dino::addAnalogListener()\n"); - #endif -} - // CMD = 07 -// Remove analog and digital listen flags on the pin. -void Dino::removeListener() { - analogListeners[pin] = false; - digitalListeners[pin] = false; - - #ifdef debug - Serial.print("Called Dino::removeListener()\n"); - #endif -} +// Set a listener ON or OFF, or change its type, or divider. +// Takes settings as binary stored in val from the parser and applies +// them to an existing listener if pin was already used, or first inactive. +void Dino::setListener(){ + boolean found = false; + // Check if previously assigned a listener to this pin and re-use. + for(byte i=0; i Date: Sun, 14 Jan 2018 02:53:17 -0400 Subject: [PATCH 151/296] Add rotary encoder component by polling @1ms with the new listeners. --- examples/rotary_encoder/rotary_encoder.rb | 17 +++++++++++ lib/dino/components.rb | 1 + lib/dino/components/basic/analog_input.rb | 5 ++-- lib/dino/components/basic/digital_input.rb | 5 ++-- lib/dino/components/mixins/listener.rb | 4 +-- lib/dino/components/rotary_encoder.rb | 29 +++++++++++++++++++ .../lib/components/basic/analog_input_spec.rb | 2 +- .../components/basic/digital_input_spec.rb | 4 +-- 8 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 examples/rotary_encoder/rotary_encoder.rb create mode 100644 lib/dino/components/rotary_encoder.rb diff --git a/examples/rotary_encoder/rotary_encoder.rb b/examples/rotary_encoder/rotary_encoder.rb new file mode 100644 index 00000000..2dc2085f --- /dev/null +++ b/examples/rotary_encoder/rotary_encoder.rb @@ -0,0 +1,17 @@ +# +# Example of a smiple rotary encoder polling at ~1ms. +# Not as precise as using an interrupt and hardware debounce, but should be OK +# at lower speeds. 1ms polling is kind of a free software debounce anyway? +# +require 'bundler/setup' +require 'dino' + +board = Dino::Board.new(Dino::TxRx::Serial.new) +encoder = Dino::Components::RotaryEncoder.new board: board, + pins:{ data: 4, clock: 5 } + +encoder.add_callback do |data| + puts "Encoder position: #{data}" +end + +sleep diff --git a/lib/dino/components.rb b/lib/dino/components.rb index eeceb214..e1a62fd0 100644 --- a/lib/dino/components.rb +++ b/lib/dino/components.rb @@ -19,5 +19,6 @@ module Components autoload :DS18B20, 'dino/components/ds18b20' autoload :IREmitter, 'dino/components/ir_emitter' autoload :Piezo, 'dino/components/piezo' + autoload :RotaryEncoder, 'dino/components/rotary_encoder' end end diff --git a/lib/dino/components/basic/analog_input.rb b/lib/dino/components/basic/analog_input.rb index 3d46c6b3..5e3ebb08 100644 --- a/lib/dino/components/basic/analog_input.rb +++ b/lib/dino/components/basic/analog_input.rb @@ -12,8 +12,9 @@ def _read board.analog_read(self.pin) end - def _listen - board.analog_listen(self.pin) + def _listen(divider=16) + divider ||= 16 + board.analog_listen(self.pin, divider) end end end diff --git a/lib/dino/components/basic/digital_input.rb b/lib/dino/components/basic/digital_input.rb index 912f1690..db24b99b 100644 --- a/lib/dino/components/basic/digital_input.rb +++ b/lib/dino/components/basic/digital_input.rb @@ -17,8 +17,9 @@ def _read board.digital_read(self.pin) end - def _listen - board.digital_listen(self.pin) + def _listen(divider=4) + divider ||= 4 + board.digital_listen(self.pin, divider) end def on_high(&block) diff --git a/lib/dino/components/mixins/listener.rb b/lib/dino/components/mixins/listener.rb index 9e1e040e..9db58fbb 100644 --- a/lib/dino/components/mixins/listener.rb +++ b/lib/dino/components/mixins/listener.rb @@ -4,10 +4,10 @@ module Mixins module Listener include Callbacks - def listen(&block) + def listen(divider=nil, &block) stop add_callback(:listen, &block) if block_given? - _listen + _listen(divider) end def stop diff --git a/lib/dino/components/rotary_encoder.rb b/lib/dino/components/rotary_encoder.rb new file mode 100644 index 00000000..53e45006 --- /dev/null +++ b/lib/dino/components/rotary_encoder.rb @@ -0,0 +1,29 @@ +module Dino + module Components + class RotaryEncoder + include Setup::MultiPin + include Mixins::Callbacks + + proxy_pins data: Basic::DigitalInput, + clock: Basic::DigitalInput + + def after_initialize(options={}) + super(options) if defined?(super) + @state = 0 + + # Stop the default behavior of the DigitalInput instances. + clock.stop; data.stop; + clock.listen(1); data.listen(1) + sleep 0.5 + + start + end + + def start + proxies[:clock].add_callback do |clock_state| + (data.state == clock_state) ? self.update(@state + 1) : self.update(@state - 1) + end + end + end + end +end diff --git a/spec/lib/components/basic/analog_input_spec.rb b/spec/lib/components/basic/analog_input_spec.rb index f5d95908..69ae26b8 100644 --- a/spec/lib/components/basic/analog_input_spec.rb +++ b/spec/lib/components/basic/analog_input_spec.rb @@ -17,7 +17,7 @@ module Basic describe '#_listen' do it 'should send #analog_listen to the board with its pin' do - expect(board).to receive(:analog_listen).with(subject.pin) + expect(board).to receive(:analog_listen).with(subject.pin, 16) subject._listen end end diff --git a/spec/lib/components/basic/digital_input_spec.rb b/spec/lib/components/basic/digital_input_spec.rb index 56f62e2c..dd82fde2 100644 --- a/spec/lib/components/basic/digital_input_spec.rb +++ b/spec/lib/components/basic/digital_input_spec.rb @@ -9,7 +9,7 @@ module Basic subject { DigitalInput.new(options) } it 'should start listening immediately' do - expect(board).to receive(:digital_listen).with(14) + expect(board).to receive(:digital_listen).with(14, 4) component = DigitalInput.new(options) end @@ -22,7 +22,7 @@ module Basic describe '#_listen' do it 'should call board#digital_listen with its pin once' do - expect(board).to receive(:digital_listen).with(subject.pin).once + expect(board).to receive(:digital_listen).with(subject.pin, 4).once subject._listen end end From 98cdd4ece63c55d5419c36035c6955c8733923a1 Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 14 Jan 2018 17:30:53 -0400 Subject: [PATCH 152/296] RotaryEncoder tracks position in degrees. Add Mac volume control example. Minor changes to MultiPin and Callbacks so they work better together. Classes can now mutate the value before it gets passed to callbacks, and override the automatic state update after callbacks, without overriding #update entirely. RotaryEncoder class does both. --- examples/rotary_encoder/rotary_encoder.rb | 12 ++-- .../rotary_encoder_mac_volume.rb | 57 +++++++++++++++++++ lib/dino/components/mixins/callbacks.rb | 31 +++++++++- lib/dino/components/rotary_encoder.rb | 49 +++++++++++++--- lib/dino/components/setup/multi_pin.rb | 3 +- spec/lib/components/setup/multi_pin_spec.rb | 4 +- 6 files changed, 140 insertions(+), 16 deletions(-) create mode 100644 examples/rotary_encoder/rotary_encoder_mac_volume.rb diff --git a/examples/rotary_encoder/rotary_encoder.rb b/examples/rotary_encoder/rotary_encoder.rb index 2dc2085f..e5a26af3 100644 --- a/examples/rotary_encoder/rotary_encoder.rb +++ b/examples/rotary_encoder/rotary_encoder.rb @@ -1,17 +1,21 @@ # # Example of a smiple rotary encoder polling at ~1ms. -# Not as precise as using an interrupt and hardware debounce, but should be OK -# at lower speeds. 1ms polling is kind of a free software debounce anyway? +# +# WARNING: This method is not precise at all. Please do not use it for anything +# that requires all steps to be read for precise positioning or high speed. # require 'bundler/setup' require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) encoder = Dino::Components::RotaryEncoder.new board: board, - pins:{ data: 4, clock: 5 } + pins:{ clock: 4, data: 5 }, + divider: 1, # (default) read approx every divider ms + steps: 30 # (default) steps / revolution encoder.add_callback do |data| - puts "Encoder position: #{data}" + puts "Encoder position: #{data[:position]}°" + puts "Encoder change: #{data[:change]}" end sleep diff --git a/examples/rotary_encoder/rotary_encoder_mac_volume.rb b/examples/rotary_encoder/rotary_encoder_mac_volume.rb new file mode 100644 index 00000000..bc80a479 --- /dev/null +++ b/examples/rotary_encoder/rotary_encoder_mac_volume.rb @@ -0,0 +1,57 @@ +# +# Example of a smiple rotary encoder polling at ~1ms. +# +# WARNING: This method is not precise at all. Please do not use it for anything +# that requires all steps to be read for precise positioning or high speed. +# +# Works well enough for making knobs like this example though. +# +require 'bundler/setup' +require 'dino' + +board = Dino::Board.new(Dino::TxRx::Serial.new) +encoder = Dino::Components::RotaryEncoder.new board: board, + pins:{ clock: 4, data: 5 }, + divider: 1, # (default) read approx every divider ms + steps: 30 # (default) steps / revolution + +# Set up a pseudo terminal with osascript (AppleScript) in interactive mode. +# Calling a separate script each update is too slow. +class AppleVolumeWrapper + require 'pty' + require 'expect' + + def initialize + @in, @out, pid = PTY.spawn('osascript -i') + @in.expect(/>> /) # Terminal ready. + end + + def get + @out.write("output volume of (get volume settings)\r\n") + @in.expect(/=> (\d+)\r\n/)[1].to_i + end + + def set(value) + @out.write("set volume output volume #{value}\r\n") + @in.expect(/>> /) + end +end + +volume = AppleVolumeWrapper.new +puts "Current volume: #{volume.get}%" + +# Some values from 0-100 aren't applied so the encoder gets stuck in a +# small range if we write every step. Track when a step has no effect +# and apply it later with this. +unused_steps = 0 + +encoder.add_callback do |update| + value = volume.get + update[:change] + unused_steps + value = 0 if value < 0 + value = 100 if value > 100 + volume.set(value) + unused_steps = value - volume.get + puts "Current volume: #{volume.get}%" +end + +sleep diff --git a/lib/dino/components/mixins/callbacks.rb b/lib/dino/components/mixins/callbacks.rb index 15253360..1f9757e3 100644 --- a/lib/dino/components/mixins/callbacks.rb +++ b/lib/dino/components/mixins/callbacks.rb @@ -25,13 +25,40 @@ def remove_callback(key=nil) alias :remove_callbacks :remove_callback def update(data) - @callback_mutex.synchronize { + data = pre_callback_filter(data) + @callback_mutex.synchronize do @callbacks.each_value do |array| array.each { |callback| callback.call(data) } end # Remove the special :read callback while still inside the lock. @callbacks.delete(:read) - } + end + update_self(data) + end + + # + # Values received by #update are usually idempotent, i.e. the new state + # of the component, and can pass to callbacks as-is. But some components + # input a state change instead. Eg. rotary encoders with +1 or -1 steps. + # + # To maintain the pattern of callbacks receiving new state, while leaving + # old state in the instance variable for comparison, we may want to + # calculate the new state from a change input, and pass that instead. + # Override this method to do so. See RotaryEncoder class for an example. + # + def pre_callback_filter(data) + data + end + + # + # Assign data to @state automatically after callbacks by default. + # + # Override this to add behavior not matching this pattern, such as + # components where data cannot be directly assigned to @state. + # Eg. data is a hash, and value from a specific key reflects @state. + # See RotaryEncoder class for an example. + # + def update_self(data) @state = data end end diff --git a/lib/dino/components/rotary_encoder.rb b/lib/dino/components/rotary_encoder.rb index 53e45006..45293166 100644 --- a/lib/dino/components/rotary_encoder.rb +++ b/lib/dino/components/rotary_encoder.rb @@ -7,23 +7,58 @@ class RotaryEncoder proxy_pins data: Basic::DigitalInput, clock: Basic::DigitalInput + attr_reader :position, :steps, :degrees_per_step + alias :position :state + def after_initialize(options={}) super(options) if defined?(super) - @state = 0 - # Stop the default behavior of the DigitalInput instances. - clock.stop; data.stop; - clock.listen(1); data.listen(1) - sleep 0.5 + # Default to listening every tick (1ms / 1kHz) + divider = options[:divider] || 1 + clock.listen(divider) + data.listen(divider) + + # Setup to track position in degrees. + @steps = options[:steps] || 30 + @degrees_per_step = (360 / steps).to_f + @state = 0.0 start end def start - proxies[:clock].add_callback do |clock_state| - (data.state == clock_state) ? self.update(@state + 1) : self.update(@state - 1) + clock.add_callback do |clock_state| + (data.state == clock_state) ? self.update(-1) : self.update(1) end end + + # + # Callbacks#update calls these before and after callbacks respectively. + # + # Take data (+/- 1 step change) and calculate new position (state) in degrees. + # Leave old position in @state for now, so callbacks can compare to it. + # + # Return a hash with the new :position and pass through :change, overriding + # the data param we took, which would have passed directly to callbacks. + # Callbacks can use either :position (degrees) or :change (steps). + # + def pre_callback_filter(data) + data = { change: data, + position: (state + (data * degrees_per_step)) % 360 } + end + # + # Callbacks run now, receiving only the value of #pre_callback_filter + # + # After callbacks, set @state to the position calculated earlier. + # This method also receives the value of #pre_callback_filter. + # + def update_self(data) + @state = data[:position] + end + + def reset_position + @callbacks_mutex.synchronize { @state = 0 } + end end end end diff --git a/lib/dino/components/setup/multi_pin.rb b/lib/dino/components/setup/multi_pin.rb index 01271f64..cfd1aab0 100644 --- a/lib/dino/components/setup/multi_pin.rb +++ b/lib/dino/components/setup/multi_pin.rb @@ -14,7 +14,7 @@ module MultiPin # # Return a hash with the state of each proxy component. # - def state + def proxy_states hash = {} proxies.each_key do |key| hash[key] = self.proxies[key].state rescue nil @@ -23,6 +23,7 @@ def state end protected + attr_writer :pins, :pullups, :proxies def self.included(base) diff --git a/spec/lib/components/setup/multi_pin_spec.rb b/spec/lib/components/setup/multi_pin_spec.rb index c9645803..e1d815c4 100644 --- a/spec/lib/components/setup/multi_pin_spec.rb +++ b/spec/lib/components/setup/multi_pin_spec.rb @@ -61,10 +61,10 @@ class MultiPinComponent expect(subject.maybe.pin).to eq(11) end end - describe '#states' do + describe '#proxy_states' do it 'should return a hash with the state of each subcomponent' do subject.two.high - expect(subject.state).to eq({two: board.high, maybe: nil}) + expect(subject.proxy_states).to eq({two: board.high, maybe: nil}) end end end From b6908a1f28e6911e180a694d1c63f86cd995abe8 Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 16 Jan 2018 00:29:22 -0400 Subject: [PATCH 153/296] Add ESP8266 with most features. Uses GPIO numbered pins. Minor cleanup. --- lib/dino.rb | 2 + lib/dino/tx_rx/base.rb | 35 +++++++++++++----- lib/dino/tx_rx/flow_control.rb | 12 +++--- lib/dino/tx_rx/serial.rb | 1 - lib/dino/tx_rx/tcp.rb | 9 +++-- lib/dino_cli/targets.rb | 7 +++- spec/lib/tx_rx/serial_spec.rb | 6 +-- src/dino_ethernet.ino | 4 +- src/dino_serial.ino | 9 ++--- src/dino_wifi.ino | 67 ++++++++++++++++++++++++---------- 10 files changed, 100 insertions(+), 52 deletions(-) diff --git a/lib/dino.rb b/lib/dino.rb index b63aa07a..9a6ed2ec 100644 --- a/lib/dino.rb +++ b/lib/dino.rb @@ -1,3 +1,5 @@ +Thread.abort_on_exception = true + require 'dino/version' require 'dino/message' require 'dino/tx_rx' diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index bf823132..db90297f 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -10,14 +10,21 @@ class RxFlushTimeout < StandardError; end class Base include Observable + include Handshake # Let the methods in FlowControl wrap subclass methods too. def self.inherited(subclass) - subclass.prepend FlowControl + subclass.send(:prepend, FlowControl) end - include Handshake - def read(message); raise "#read should be defined in TxRx subclasses"; end - def write(message); raise "#write should be defined in TxRx subclasses"; end + def read(message) + raise NotImplementedError + .new("#{self.class.name}#read not defined in Dino::TxRx subclass") + end + + def write(message) + raise NotImplementedError + .new("#{self.class.name}#write not defined in Dino::TxRx subclass") + end private @@ -25,11 +32,16 @@ def io @io ||= connect end - def io_reset - flush_read; stop_read; start_read + def connect + raise NotImplementedError + .new("#{self.class.name}#connect not defined in Dino::TxRx subclass") end - def connect(message); raise "#connect should be defined in TxRx subclasses"; end + def io_reset + flush_read + stop_read + start_read + end def flush_read Timeout.timeout(5) { read until read == nil } @@ -38,7 +50,7 @@ def flush_read end def start_read - @thread ||= Thread.new { loop { read_and_process } }.abort_on_exception = true + @thread ||= Thread.new { loop { read_and_parse } } end def stop_read @@ -47,9 +59,12 @@ def stop_read @thread = nil end - def read_and_process(message); raise "#read_and_process should be defined in FlowControl module"; end + def read_and_parse + raise NotImplementedError + .new("#{self.class.name}#read_and_parse not defined in Dino::TxRx::FlowControl") + end - def process(line) + def parse(line) if line.match(/\A\d+:/) pin, message = line.split(":", 2) pin && message && changed && notify_observers(pin, message) diff --git a/lib/dino/tx_rx/flow_control.rb b/lib/dino/tx_rx/flow_control.rb index d5a727b4..8436c38e 100644 --- a/lib/dino/tx_rx/flow_control.rb +++ b/lib/dino/tx_rx/flow_control.rb @@ -7,10 +7,11 @@ module FlowControl SLEEP_MAX = 0.01 def initialize(*args) - reset_flow_control super(*args) + reset_flow_control end + # Split messages to use available buffer, and wrap #write in a mutex. def write(message) add_write_call @write_mutex.synchronize do @@ -61,19 +62,18 @@ def reset_flow_control @last_interval_update = Time.now end - def read_and_process + def read_and_parse line = read if line && line.match(/\AACK:/) - # Handle handshake responses by passing to the observing HandshakeAttempt. - # Also pass self so it can detach itself when done. + # A HandshakeAttempt is observing. Also pass self so it can stop. changed && notify_observers(self, line.split(":", 2)[1]) - # Empty transit counter since ACK: won't also send RCV: + # Empty @transit_bytes. ACK: means the board reset its counter too. @transit_mutex.synchronize { @transit_bytes = 0 } elsif line && line.match(/\ARCV:/) remove_transit_bytes(line.split(/:/)[1].to_i) elsif line - process(line) + parse(line) else rx_wait end diff --git a/lib/dino/tx_rx/serial.rb b/lib/dino/tx_rx/serial.rb index e5b18ad2..44554744 100644 --- a/lib/dino/tx_rx/serial.rb +++ b/lib/dino/tx_rx/serial.rb @@ -50,7 +50,6 @@ def connect puts "Connected" return connection rescue RubySerial::Error => error - puts error handle_error(error); next end end diff --git a/lib/dino/tx_rx/tcp.rb b/lib/dino/tx_rx/tcp.rb index a75ad170..df8eb31f 100644 --- a/lib/dino/tx_rx/tcp.rb +++ b/lib/dino/tx_rx/tcp.rb @@ -3,8 +3,9 @@ module Dino module TxRx class TCP < Base - def initialize(host, port=3466) - @host, @port = host, port + def initialize(host="127.0.0.1", port=3466) + @host = host + @port = port end def to_s @@ -15,7 +16,9 @@ def to_s def connect print "Connecting to TCP at: #{self.to_s}... " - connection = Timeout::timeout(10) { TCPSocket.open @host, @port } + connection = Timeout::timeout(10) do + TCPSocket.open(@host, @port) + end puts "Connected" connection rescue => error diff --git a/lib/dino_cli/targets.rb b/lib/dino_cli/targets.rb index 9a34148d..b9a3d1f5 100644 --- a/lib/dino_cli/targets.rb +++ b/lib/dino_cli/targets.rb @@ -1,7 +1,7 @@ class DinoCLI::Generator TARGETS = { # Default target always includes all packages. - mega: PACKAGES.each_key.map {|k| k}, + mega: PACKAGES.each_key.to_a, # Core is core. core: [:core], @@ -10,6 +10,9 @@ class DinoCLI::Generator mega168: [:core, :servo, :dht, :one_wire, :ir_out, :tone, :spi, :i2c], # ARM includes everytyhing except specific incompatibilities. - arm: PACKAGES.each_key.map {|k| k} - [:serial, :tone, :ir_out, :i2c], + arm: PACKAGES.each_key.to_a - [:serial, :tone, :ir_out, :i2c], + + # A surprising amount "just works" on the ESP, notably not LCD. + esp8266: PACKAGES.each_key.to_a - [:lcd, :serial, :ir_out, :i2c], } end diff --git a/spec/lib/tx_rx/serial_spec.rb b/spec/lib/tx_rx/serial_spec.rb index bcd017fd..421a167f 100644 --- a/spec/lib/tx_rx/serial_spec.rb +++ b/spec/lib/tx_rx/serial_spec.rb @@ -72,19 +72,19 @@ def io_instance end end - describe '#read_and_process' do + describe '#read_and_parse' do it 'should notify observers on change' do expect(subject).to receive(:read).and_return("02:00") expect(subject).to receive(:changed).and_return(true) expect(subject).to receive(:notify_observers).with('02','00') - subject.send(:read_and_process) + subject.send(:read_and_parse) end it 'should not split messages into more than 2 parts on :' do expect(subject).to receive(:read).and_return("02:00:00") expect(subject).to receive(:changed).and_return(true) expect(subject).to receive(:notify_observers).with('02','00:00') - subject.send(:read_and_process) + subject.send(:read_and_parse) end end diff --git a/src/dino_ethernet.ino b/src/dino_ethernet.ino index aec9362b..cc762432 100644 --- a/src/dino_ethernet.ino +++ b/src/dino_ethernet.ino @@ -13,7 +13,6 @@ EthernetClient client; void printEthernetStatus() { - // Print ethernet status. Serial.print("IP Address: "); Serial.println(Ethernet.localIP()); Serial.print("Port: "); @@ -23,7 +22,8 @@ void printEthernetStatus() { void setup() { // Start serial for debugging. - Serial.begin(9600); + Serial.begin(115200); + while(!serial); // Explicitly disable the SD card. pinMode(4,OUTPUT); diff --git a/src/dino_serial.ino b/src/dino_serial.ino index a89ddf72..1c890f07 100644 --- a/src/dino_serial.ino +++ b/src/dino_serial.ino @@ -14,14 +14,11 @@ Dino dino; void setup() { + // Wait for serial ready. serial.begin(115200); + while(!serial); - // Wait for Leonardo serial port to connect. - #if defined(__AVR_ATmega32U4__) - while(!serial); - #endif - - // Pass the stream to dino so it can read/write. + // Pass serial stream to dino so it can read/write. dino.setOutputStream(&serial); } diff --git a/src/dino_wifi.ino b/src/dino_wifi.ino index 2a48a402..38c95807 100644 --- a/src/dino_wifi.ino +++ b/src/dino_wifi.ino @@ -1,20 +1,35 @@ #include "Dino.h" -#include -#include +#ifdef ESP8266 + #include + #define LED_PIN 2 +#else + #include + #include + #define LED_PIN 13 +#endif -// Configure your WiFi options here. MAC address and IP address are not configurable. +// Configure your WiFi options here. IP address are not configurable. Uses DHCP. int port = 3466; -char ssid [] = "yourNetwork"; -char pass [] = "yourPassword"; -int keyIndex = 0; -int status = WL_IDLE_STATUS; +char* ssid = "yourNetwork"; +char* pass = "yourPassword"; Dino dino; WiFiServer server(port); WiFiClient client; +// Use the built in LED to indicate WiFi status. +void indicate(byte value) { + #ifdef ESP8266 + digitalWrite(LED_PIN, !value); + #else + digitalWrite(LED_PIN, value); + #endif +} + + void printWifiStatus() { + Serial.println("Connected"); Serial.print("SSID: "); Serial.println(WiFi.SSID()); Serial.print("Signal Strength (RSSI):"); @@ -22,29 +37,43 @@ void printWifiStatus() { Serial.println(" dBm"); Serial.print("IP Address: "); Serial.println(WiFi.localIP()); - Serial.print("Port: "); + Serial.print("Dino TCP Port: "); Serial.println(port); + indicate(true); +} + + +void connect(){ + Serial.print("Attempting to connect to SSID: "); + Serial.print(ssid); + WiFi.begin(ssid, pass); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + printWifiStatus(); } void setup() { + pinMode(LED_PIN, OUTPUT); + // Start serial for debugging. - Serial.begin(9600); - - // Try to connect to the specified network. - while ( status != WL_CONNECTED) { - Serial.print("Attempting to connect to SSID: "); - Serial.println(ssid); - status = WiFi.begin(ssid, pass); - delay(10000); - } + Serial.begin(115200); + while(!Serial); - // Start the server. + connect(); server.begin(); - printWifiStatus(); } + void loop() { + // Reconnect if we've lost WiFi.. + if (WiFi.status() != WL_CONNECTED){ + indicate(false); + connect(); + } + // Listen for connections. client = server.available(); From 910ad64b6eb93a66b67c5833d77b0118a87535e2 Mon Sep 17 00:00:00 2001 From: vickash Date: Thu, 18 Jan 2018 22:07:58 -0400 Subject: [PATCH 154/296] Return #read value. Test coverage for all component mixin behavior. Drop support for ruby < 2.0.0. --- .travis.yml | 1 - lib/dino/components/mixins/listener.rb | 15 ++- lib/dino/components/mixins/poller.rb | 11 +- lib/dino/components/mixins/reader.rb | 14 ++- lib/dino/components/mixins/threaded.rb | 6 +- lib/dino/components/setup/input.rb | 4 + spec/lib/components/mixins/callbacks_spec.rb | 30 ++++-- spec/lib/components/mixins/listener_spec.rb | 57 ++++++++++ spec/lib/components/mixins/poller_spec.rb | 65 ++++++++++++ spec/lib/components/mixins/reader_spec.rb | 77 ++++++++++++++ spec/lib/components/mixins/threaded_spec.rb | 105 +++++++++++++++++++ 11 files changed, 358 insertions(+), 27 deletions(-) create mode 100644 spec/lib/components/mixins/listener_spec.rb create mode 100644 spec/lib/components/mixins/poller_spec.rb create mode 100644 spec/lib/components/mixins/reader_spec.rb create mode 100644 spec/lib/components/mixins/threaded_spec.rb diff --git a/.travis.yml b/.travis.yml index 8499f94c..171ae6bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,6 @@ before_install: rvm: - 2.4.1 - 2.0.0 - - 1.9.3 - jruby-19mode script: bundle exec rspec spec diff --git a/lib/dino/components/mixins/listener.rb b/lib/dino/components/mixins/listener.rb index 9db58fbb..88e07835 100644 --- a/lib/dino/components/mixins/listener.rb +++ b/lib/dino/components/mixins/listener.rb @@ -12,14 +12,19 @@ def listen(divider=nil, &block) def stop super if defined?(super) - board.stop_listener(pin) + _stop_listen remove_callbacks :listen end - # - # Including component should define this to start a listener on the board. - # - def _listen; end + def _listen + raise NotImplementedError + .new("#{self.class.name}#_listen is not defined.") + end + + def _stop_listen + raise NotImplementedError + .new("#{self.class.name}#_stop_listen is not defined.") + end end end end diff --git a/lib/dino/components/mixins/poller.rb b/lib/dino/components/mixins/poller.rb index ac478a1f..35b17938 100644 --- a/lib/dino/components/mixins/poller.rb +++ b/lib/dino/components/mixins/poller.rb @@ -3,14 +3,9 @@ module Components module Mixins module Poller include Reader - # - # Make sure Threaded is in the including class. - # - def self.included(base) - base.class_eval { include Threaded } - end + include Threaded - def poll(interval, &block) + def poll(interval=1, &block) stop add_callback(:poll, &block) if block_given? threaded_loop do @@ -21,7 +16,7 @@ def poll(interval, &block) def stop super if defined?(super) stop_thread - remove_callback :poll + remove_callbacks :poll end end end diff --git a/lib/dino/components/mixins/reader.rb b/lib/dino/components/mixins/reader.rb index 285a6eca..03ad8aec 100644 --- a/lib/dino/components/mixins/reader.rb +++ b/lib/dino/components/mixins/reader.rb @@ -6,14 +6,20 @@ module Reader def read(&block) add_callback(:read, &block) if block_given? + + value = nil + add_callback(:read) { |data| value = data } + _read loop { break if !@callbacks[:read] } + + value end - # - # Including component should define this to perform a single read on the board. - # - def _read; end + def _read + raise NotImplementedError + .new("#{self.class.name}#_read is not defined.") + end end end end diff --git a/lib/dino/components/mixins/threaded.rb b/lib/dino/components/mixins/threaded.rb index be981dea..bcfe3f4f 100644 --- a/lib/dino/components/mixins/threaded.rb +++ b/lib/dino/components/mixins/threaded.rb @@ -19,12 +19,12 @@ def self.included(base) def threaded(&block) stop_thread enable_interrupts unless interrupts_enabled - @thread = Thread.new { block.call} + @thread = Thread.new(&block) end def threaded_loop(&block) - threaded do - loop { block.call } + threaded do + loop(&block) end end diff --git a/lib/dino/components/setup/input.rb b/lib/dino/components/setup/input.rb index 6553224b..f2dfbfb6 100644 --- a/lib/dino/components/setup/input.rb +++ b/lib/dino/components/setup/input.rb @@ -16,6 +16,10 @@ def initialize_pins(options={}) self.mode = :in self.pullup = options[:pullup] end + + def _stop_listen + board.stop_listener(pin) + end end end end diff --git a/spec/lib/components/mixins/callbacks_spec.rb b/spec/lib/components/mixins/callbacks_spec.rb index cbc23595..54941442 100644 --- a/spec/lib/components/mixins/callbacks_spec.rb +++ b/spec/lib/components/mixins/callbacks_spec.rb @@ -50,7 +50,7 @@ def initialize; after_initialize; end expect(subject.instance_variable_get :@callbacks).to eq({}) end - it 'should remove only callbacks for a specific key given' do + it 'should remove only callbacks the given key if given' do subject.remove_callbacks(:read) expect(subject.instance_variable_get(:@callbacks)[:read]).to eq(nil) expect(subject.instance_variable_get(:@callbacks)[:persistent]).to_not eq(nil) @@ -58,11 +58,6 @@ def initialize; after_initialize; end end describe '#update' do - it 'should set the @state variable' do - subject.update("thing") - expect(subject.instance_variable_get :@state).to eq("thing") - end - it 'should call all the callbacks' do expect(@callback1).to receive(:call).once expect(@callback2).to receive(:call).once @@ -74,6 +69,29 @@ def initialize; after_initialize; end expect(subject.instance_variable_get(:@callbacks)[:read]).to eq(nil) end end + + + describe '#pre_callback_filter' do + it 'should mutate data being passed to callbacks' do + class FilteredComponent < CallbackComponent + def pre_callback_filter(data) + "dino: #{data}" + end + end + callback = Proc.new{|data| data} + subject = FilteredComponent.new + subject.add_callback(&callback) + expect(callback).to receive(:call).with("dino: dino") + subject.update("dino") + end + end + + describe '#update_self' do + it 'should set the @state variable' do + subject.update("dino") + expect(subject.instance_variable_get :@state).to eq("dino") + end + end end end end diff --git a/spec/lib/components/mixins/listener_spec.rb b/spec/lib/components/mixins/listener_spec.rb new file mode 100644 index 00000000..7dc92b86 --- /dev/null +++ b/spec/lib/components/mixins/listener_spec.rb @@ -0,0 +1,57 @@ +require 'spec_helper' + +module Dino + module Components + module Mixins + describe Poller do + + class ListenComponent + include Listener + def _listen(divider=nil); end + def _stop_listen; end + def initialize; after_initialize; end + end + + subject { ListenComponent.new } + + describe '#initialize' do + it 'should automatically include Callbacks' do + expect(subject.class.ancestors).to include(Callbacks) + end + end + + describe '#listen' do + it 'should call #stop' do + expect(subject).to receive(:stop) + subject.listen + end + + it 'should call #_listen' do + expect(subject).to receive(:_listen) + subject.listen + end + + it 'should add a given block as callback with :listen key' do + callback = Proc.new{} + expect(subject).to receive(:add_callback).with(:listen) do |&block| + expect(block).to eq(callback) + end + subject.listen(&callback) + end + end + + describe '#stop' do + it 'should call #_stop_listen' do + expect(subject).to receive(:_listen) + subject.listen + end + + it 'should remove all callbacks with the :listen key' do + expect(subject).to receive(:remove_callbacks).with(:listen) + subject.stop + end + end + end + end + end +end diff --git a/spec/lib/components/mixins/poller_spec.rb b/spec/lib/components/mixins/poller_spec.rb new file mode 100644 index 00000000..e87ddc7f --- /dev/null +++ b/spec/lib/components/mixins/poller_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' + +module Dino + module Components + module Mixins + describe Poller do + + class PollComponent + include Poller + def _read; end + def initialize; after_initialize; end + end + + subject { PollComponent.new } + + describe '#initialize' do + it 'should automatically include Callbacks' do + expect(subject.class.ancestors).to include(Threaded) + expect(subject.class.ancestors).to include(Reader) + end + end + + describe '#poll' do + it 'should call #stop' do + expect(subject).to receive(:stop) + subject.poll + end + + it 'should add a given block as callback with :poll key' do + callback = Proc.new{} + expect(subject).to receive(:add_callback).with(:poll) do |&block| + expect(block).to eq(callback) + end + subject.poll(&callback) + end + + it 'should start a new thread' do + expect(subject).to receive(:threaded_loop) + subject.poll + subject.stop + end + + it 'should call #_read repeatedly' do + expect(subject).to receive(:_read).at_least(:twice) + subject.poll(0.01) + sleep 0.1 + subject.stop + end + end + + describe '#stop' do + it 'should not override the call to Threaded#stop_thread' do + expect(subject).to receive(:stop_thread) + subject.stop + end + + it 'should remove any callbacks with the :poll key' do + expect(subject).to receive(:remove_callbacks).with(:poll) + subject.stop + end + end + end + end + end +end diff --git a/spec/lib/components/mixins/reader_spec.rb b/spec/lib/components/mixins/reader_spec.rb new file mode 100644 index 00000000..8f92eb23 --- /dev/null +++ b/spec/lib/components/mixins/reader_spec.rb @@ -0,0 +1,77 @@ +require 'spec_helper' + +module Dino + module Components + module Mixins + describe Reader do + + class ReadComponent + include Reader + def _read; end + def initialize; after_initialize; end + end + + subject { ReadComponent.new } + + def read_callbacks + subject.instance_variable_get(:@callbacks)[:read] + end + + def inject(data, wait_for_callbacks = true) + Thread.new do + if wait_for_callbacks + while (!read_callbacks) do; sleep 0.01; end + end + loop do + sleep 0.01 + subject.update(data) + break unless read_callbacks + end + end + end + + describe '#initialize' do + it 'should automatically include Callbacks' do + expect(ReadComponent.ancestors).to include(Callbacks) + end + end + + describe '#read' do + it 'should call #_read exactly once' do + expect(subject).to receive(:_read).exactly(1).times + inject(1) + subject.read + end + + it 'should add the given block as a callback with key :read' do + callback = Proc.new{} + + # expect(subject).to receive(:add_callback).with(:read, &callback) + # would just be too easy? + blocks = [] + allow(subject).to receive(:add_callback).with(:read) do |&block| + blocks << block + expect(blocks).to include(callback) + end + + inject(1) + subject.read(&callback) + end + + it 'should run the given callback exactly once' do + callback = Proc.new{} + expect(callback).to receive(:call).exactly(1).times + inject(1) + subject.read(&callback) + inject(1, false) + end + + it 'should return the read value' do + inject("Hello") + expect(subject.read).to eq("Hello") + end + end + end + end + end +end diff --git a/spec/lib/components/mixins/threaded_spec.rb b/spec/lib/components/mixins/threaded_spec.rb new file mode 100644 index 00000000..6874a781 --- /dev/null +++ b/spec/lib/components/mixins/threaded_spec.rb @@ -0,0 +1,105 @@ +require 'spec_helper' + +module Dino + module Components + module Mixins + describe Threaded do + + class ThreadedComponent + include Threaded + def foo(str="test") + @bar = str + end + attr_reader :bar + interrupt_with :foo + end + + subject { ThreadedComponent.new } + + describe 'ClassMethods' do + it 'should add methods to interrupt the thread using #interrupt_with' do + ThreadedComponent.class_variable_get(:@@interrupts).should eq [:foo] + end + end + + describe '#threaded' do + it 'should stop any existing thread' do + expect(subject).to receive(:stop_thread) + subject.threaded do; end + end + + it 'should enable interrupts' do + expect(subject).to receive(:enable_interrupts) + subject.threaded do; end + end + + it 'should start a new thread that calls the given block' do + async = Proc.new {} + expect(Thread).to receive(:new) do |&block| + expect(block).to eq(async) + end + subject.threaded(&async) + end + + it 'should store in @thread' do + thread = Thread.current + async = Proc.new { thread = Thread.current } + subject.threaded(&async) + sleep 0.25 + expect(subject.instance_variable_get :@thread).to eq thread + end + end + + describe '#threaded_loop' do + it 'should loop the block in the thread' do + async = Proc.new {} + expect(subject).to receive(:loop) do |&block| + expect(block).to eq(async) + end + + subject.threaded_loop(&async) + sleep 0.25 + subject.stop_thread + end + end + + describe '#stop_thread' do + it 'should kill the thread' do + subject.threaded { sleep } + expect(subject.instance_variable_get :@thread).to receive(:kill) + subject.stop_thread + end + end + + describe '#enable_interrupts' do + it 'should override the given method on the singleton class only' do + second_part = ThreadedComponent.new + before_class = second_part.method(:foo) + before_instance = subject.method(:foo) + + subject.enable_interrupts + after_class = second_part.method(:foo) + after_instance = subject.method(:foo) + + expect(after_class).to eq(before_class) + expect(after_instance).to_not eq(before_instance) + end + + it 'should pass arguments through to the original method' do + subject.enable_interrupts + subject.foo("dino") + expect(subject.bar).to eq("dino") + end + end + + describe 'calling an interrupt' do + it 'should stop the thread' do + subject.threaded { sleep } + expect(subject).to receive(:stop_thread) + subject.foo + end + end + end + end + end +end From 42177cb4410e1f066426a2ae856e0f2d60907a08 Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 23 Jan 2018 22:28:56 -0400 Subject: [PATCH 155/296] Cleaner IR code. Uses unmodified libs, works on ESP8266 now --- .gitmodules | 5 ++++- lib/dino_cli/generator.rb | 3 +++ lib/dino_cli/packages.rb | 13 ++++++++++++ lib/dino_cli/targets.rb | 2 +- spec/lib/components/mixins/threaded_spec.rb | 17 ++++++++++----- src/lib/DinoIROut.cpp | 23 +++++++++++++++++---- src/vendor/Arduino-IRremote | 2 +- src/vendor/IRremoteESP8266 | 1 + src/vendor/OneWire | 2 +- 9 files changed, 55 insertions(+), 13 deletions(-) create mode 160000 src/vendor/IRremoteESP8266 diff --git a/.gitmodules b/.gitmodules index 47bcd249..e48ee19d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "src/vendor/Arduino-IRremote"] path = src/vendor/Arduino-IRremote - url = https://github.com/vickash/Arduino-IRremote.git + url = https://github.com/z3t0/Arduino-IRremote.git [submodule "src/vendor/I2C-Master-Library"] path = src/vendor/I2C-Master-Library url = https://github.com/vickash/I2C-Master-Library.git @@ -10,3 +10,6 @@ [submodule "src/vendor/OneWire"] path = src/vendor/OneWire url = https://github.com/PaulStoffregen/OneWire.git +[submodule "src/vendor/IRremoteESP8266"] + path = src/vendor/IRremoteESP8266 + url = https://github.com/markszabo/IRremoteESP8266.git diff --git a/lib/dino_cli/generator.rb b/lib/dino_cli/generator.rb index 26853d4d..e3ab73ec 100644 --- a/lib/dino_cli/generator.rb +++ b/lib/dino_cli/generator.rb @@ -41,6 +41,9 @@ def read if f.match /\Avendor/ directive = @packages[k][:directive] contents = "#include \"DinoDefines.h\"\n#ifdef #{directive}\n" << contents << "\n#endif\n" + + # Hack to workaround /util folder includes in latest OneWire library. + contents.gsub!("util/OneWire_direct_gpio.h", "OneWire_direct_gpio.h") if f.match /OneWire/ end { path: f, contents: contents } end diff --git a/lib/dino_cli/packages.rb b/lib/dino_cli/packages.rb index eb7c758c..bf156d89 100644 --- a/lib/dino_cli/packages.rb +++ b/lib/dino_cli/packages.rb @@ -77,6 +77,18 @@ class DinoCLI::Generator "vendor/Arduino-IRremote/irSend.cpp", ] }, + ir_out_esp8266: { + description: "Transmit infrared signals with the ESP8266", + directive: "DINO_IR_OUT", + files: [ + "lib/DinoIROut.cpp", + "vendor/IRremoteESP8266/src/IRremoteESP8266.h", + "vendor/IRremoteESP8266/src/IRsend.h", + "vendor/IRremoteESP8266/src/IRsend.cpp", + "vendor/IRremoteESP8266/src/IRtimer.h", + "vendor/IRremoteESP8266/src/IRtimer.cpp", + ] + }, one_wire: { description: "OneWire bus support (Just DS18B20 for now)", directive: "DINO_ONE_WIRE", @@ -84,6 +96,7 @@ class DinoCLI::Generator "lib/DinoOneWire.cpp", "vendor/OneWire/OneWire.cpp", "vendor/OneWire/OneWire.h", + "vendor/OneWire/util/OneWire_direct_gpio.h", ] }, i2c: { diff --git a/lib/dino_cli/targets.rb b/lib/dino_cli/targets.rb index b9a3d1f5..65f4e167 100644 --- a/lib/dino_cli/targets.rb +++ b/lib/dino_cli/targets.rb @@ -13,6 +13,6 @@ class DinoCLI::Generator arm: PACKAGES.each_key.to_a - [:serial, :tone, :ir_out, :i2c], # A surprising amount "just works" on the ESP, notably not LCD. - esp8266: PACKAGES.each_key.to_a - [:lcd, :serial, :ir_out, :i2c], + esp8266: PACKAGES.each_key.to_a - [:lcd, :serial, :ir_out, :i2c] + [:ir_out_esp8266], } end diff --git a/spec/lib/components/mixins/threaded_spec.rb b/spec/lib/components/mixins/threaded_spec.rb index 6874a781..bcd689f6 100644 --- a/spec/lib/components/mixins/threaded_spec.rb +++ b/spec/lib/components/mixins/threaded_spec.rb @@ -45,21 +45,26 @@ def foo(str="test") thread = Thread.current async = Proc.new { thread = Thread.current } subject.threaded(&async) - sleep 0.25 - expect(subject.instance_variable_get :@thread).to eq thread + while(!subject.instance_variable_get :@thread) do; end + expect(subject.instance_variable_get :@thread).to_not eq(thread) end end describe '#threaded_loop' do it 'should loop the block in the thread' do - async = Proc.new {} + main_thread = Thread.current + async_thread = Thread.current + async = Proc.new { async_thread = Thread.current} + + expect(async).to receive(:call).and_call_original expect(subject).to receive(:loop) do |&block| expect(block).to eq(async) - end + end.and_yield subject.threaded_loop(&async) - sleep 0.25 + while(main_thread == async_thread) do; end subject.stop_thread + expect(main_thread).to_not eq(async_thread) end end @@ -86,8 +91,10 @@ def foo(str="test") end it 'should pass arguments through to the original method' do + original = subject.method(:foo) subject.enable_interrupts subject.foo("dino") + expect(original).to_not eq(subject.method(:foo)) expect(subject.bar).to eq("dino") end end diff --git a/src/lib/DinoIROut.cpp b/src/lib/DinoIROut.cpp index 9ba67395..a9172c8a 100644 --- a/src/lib/DinoIROut.cpp +++ b/src/lib/DinoIROut.cpp @@ -4,13 +4,28 @@ #include "Dino.h" #ifdef DINO_IR_OUT -#include "IRremote.h" -IRsend infraredOut; +#ifdef ESP8266 + #include "IRremoteESP8266.h" + #include "Irsend.h" + IRsend infraredOut(2); +#else + #include "IRremote.h" + IRsend infraredOut; +#endif // CMD = 16 // Send an infrared signal. void Dino::irSend(){ - infraredOut.sendRaw((uint16_t)&auxMsg[1], (uint8_t)auxMsg[0], val); -} + infraredOut.enableIROut(val); + for (int i=0; i<(uint8_t)auxMsg[0]; i++){ + uint16_t pulse = ((uint16_t)auxMsg[(i*2)+2] << 8) | auxMsg[(i*2)+1]; + if ((i % 2) == 0) { + infraredOut.mark(pulse); + } else { + infraredOut.space(pulse); + } + } + infraredOut.space(0); +} #endif diff --git a/src/vendor/Arduino-IRremote b/src/vendor/Arduino-IRremote index b6cbb73a..96186317 160000 --- a/src/vendor/Arduino-IRremote +++ b/src/vendor/Arduino-IRremote @@ -1 +1 @@ -Subproject commit b6cbb73abc176f241942724a257c7217baeeee5d +Subproject commit 96186317b251c04da595159402f80538c0be5eed diff --git a/src/vendor/IRremoteESP8266 b/src/vendor/IRremoteESP8266 new file mode 160000 index 00000000..fce4ccf0 --- /dev/null +++ b/src/vendor/IRremoteESP8266 @@ -0,0 +1 @@ +Subproject commit fce4ccf0bd58eb7e0d905ee279eeccdca6ba1e69 diff --git a/src/vendor/OneWire b/src/vendor/OneWire index ebc5f7f5..ac0ea9a7 160000 --- a/src/vendor/OneWire +++ b/src/vendor/OneWire @@ -1 +1 @@ -Subproject commit ebc5f7f503b7be9c3772c3c131d86d7080cc77cf +Subproject commit ac0ea9a7fc266b2d709532fcb1269ffe5fcbbb46 From 46f11dff71bacf931675e6a4ea822b9221d0b325 Mon Sep 17 00:00:00 2001 From: vickash Date: Wed, 24 Jan 2018 13:26:43 -0400 Subject: [PATCH 156/296] Copy dependency sources when building gem --- dino.gemspec | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/dino.gemspec b/dino.gemspec index 23d14b9d..171b28fc 100644 --- a/dino.gemspec +++ b/dino.gemspec @@ -10,6 +10,24 @@ Gem::Specification.new do |gem| gem.homepage = 'https://github.com/austinbv/dino' gem.files = `git ls-files`.split($\) + + # Copy full submodule contents into the gem when building. + # Credit: + # https://gist.github.com/mattconnolly/5875987#file-gem-with-git-submodules-gemspec + # + # get an array of submodule dirs by executing 'pwd' inside each submodule + gem_dir = File.expand_path(File.dirname(__FILE__)) + "/" + `git submodule --quiet foreach pwd`.split($\).each do |submodule_path| + Dir.chdir(submodule_path) do + submodule_relative_path = submodule_path.sub gem_dir, "" + # issue git ls-files in submodule's directory and + # prepend the submodule path to create absolute file paths + `git ls-files`.split($\).each do |filename| + gem.files << "#{submodule_relative_path}/#{filename}" + end + end + end + gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) gem.name = "dino" gem.require_paths = ["lib"] From 0b65a3531c037529ae68a36f6724c411fddfb374 Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 29 Jan 2018 21:14:19 -0400 Subject: [PATCH 157/296] Dynamic IR pin for ESP8266 since it bitbangs carrier frequency anyway. --- src/lib/DinoIROut.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib/DinoIROut.cpp b/src/lib/DinoIROut.cpp index a9172c8a..4847de35 100644 --- a/src/lib/DinoIROut.cpp +++ b/src/lib/DinoIROut.cpp @@ -7,7 +7,6 @@ #ifdef ESP8266 #include "IRremoteESP8266.h" #include "Irsend.h" - IRsend infraredOut(2); #else #include "IRremote.h" IRsend infraredOut; @@ -16,6 +15,10 @@ // CMD = 16 // Send an infrared signal. void Dino::irSend(){ + #ifdef ESP8266 + IRsend infraredOut(pin); + #endif + infraredOut.enableIROut(val); for (int i=0; i<(uint8_t)auxMsg[0]; i++){ From 54e2462218bf8bf65a534a93c3217a9746c0d4f3 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 3 Feb 2018 14:18:56 -0400 Subject: [PATCH 158/296] Handle targets/exclusion when building sketch --- lib/dino_cli/generator.rb | 21 +++++++++++++++------ lib/dino_cli/packages.rb | 5 ++--- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/lib/dino_cli/generator.rb b/lib/dino_cli/generator.rb index e3ab73ec..94c6885b 100644 --- a/lib/dino_cli/generator.rb +++ b/lib/dino_cli/generator.rb @@ -108,13 +108,22 @@ def write sketch = File.join(output_dir, sketch_filename) File.open(sketch, 'w') { |f| f.write @sketch } - # Go through the @packages hash and copy all the source files regardless of target. - # Append source file basename to the output dir to get output file path. - # Then write the file contents to the destination path. + # Go through the @packages hash and copy source files. + # Exclude only for target hardware incompatibility. + # Eg. ESP8266 IR library is different and incompatible with AVR version. @packages.each_key do |k| - @packages[k][:files].each do |file| - dest_path = File.join(output_dir, file[:path].split('/')[-1]) - File.open(dest_path, 'w') { |f| f.write file[:contents] } + # Check if the package should be included for this target. + package = @packages[k] + targeted = !package[:target] || package[:target].include?(options[:target]) + excluded = package[:exclude] && package[:exclude].include?(options[:target]) + + # Append source file basename to the output dir to get output file path. + # Then write the file contents to the destination path. + if (targeted && !excluded) + package[:files].each do |file| + dest_path = File.join(output_dir, file[:path].split('/')[-1]) + File.open(dest_path, 'w') { |f| f.write file[:contents] } + end end end diff --git a/lib/dino_cli/packages.rb b/lib/dino_cli/packages.rb index bf156d89..fc486460 100644 --- a/lib/dino_cli/packages.rb +++ b/lib/dino_cli/packages.rb @@ -69,6 +69,7 @@ class DinoCLI::Generator ir_out: { description: "Transmit infrared signals", directive: "DINO_IR_OUT", + exclude: [:esp8266], files: [ "lib/DinoIROut.cpp", "vendor/Arduino-IRremote/boarddefs.h", @@ -80,6 +81,7 @@ class DinoCLI::Generator ir_out_esp8266: { description: "Transmit infrared signals with the ESP8266", directive: "DINO_IR_OUT", + target: [:esp8266], files: [ "lib/DinoIROut.cpp", "vendor/IRremoteESP8266/src/IRremoteESP8266.h", @@ -94,9 +96,6 @@ class DinoCLI::Generator directive: "DINO_ONE_WIRE", files: [ "lib/DinoOneWire.cpp", - "vendor/OneWire/OneWire.cpp", - "vendor/OneWire/OneWire.h", - "vendor/OneWire/util/OneWire_direct_gpio.h", ] }, i2c: { From 57292688f927333c9c63349dbe3d0722690979f7 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 3 Feb 2018 17:16:43 -0400 Subject: [PATCH 159/296] Ruby based OneWire with only the timing functions in C. Remove external library. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only implemented DS18B20 so far, but full bus support should be easy now since it’s pure Ruby. --- .gitmodules | 3 - examples/one_wire/ds18b20.rb | 18 +++-- lib/dino/components/ds18b20.rb | 115 ++++++++++++++++++++++++++- spec/lib/components/ds18b20_spec.rb | 20 +++++ src/lib/Dino.cpp | 13 +-- src/lib/Dino.h | 9 ++- src/lib/DinoOneWire.cpp | 119 ++++++++++++++++++---------- src/vendor/OneWire | 1 - 8 files changed, 240 insertions(+), 58 deletions(-) create mode 100644 spec/lib/components/ds18b20_spec.rb delete mode 160000 src/vendor/OneWire diff --git a/.gitmodules b/.gitmodules index e48ee19d..bb452163 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,9 +7,6 @@ [submodule "src/vendor/arduino-DHT"] path = src/vendor/arduino-DHT url = https://github.com/markruys/arduino-DHT.git -[submodule "src/vendor/OneWire"] - path = src/vendor/OneWire - url = https://github.com/PaulStoffregen/OneWire.git [submodule "src/vendor/IRremoteESP8266"] path = src/vendor/IRremoteESP8266 url = https://github.com/markszabo/IRremoteESP8266.git diff --git a/examples/one_wire/ds18b20.rb b/examples/one_wire/ds18b20.rb index 9a7ae8a4..0f428b1e 100644 --- a/examples/one_wire/ds18b20.rb +++ b/examples/one_wire/ds18b20.rb @@ -5,11 +5,19 @@ require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) +ds18b20 = Dino::Components::DS18B20.new(pin: 16, board: board) -# The temperature and humidity functions of the DHT sensors are -# modelled separately, but both isntances can be set up on the same pin. -temp = Dino::Components::DS18B20.new(pin: 7, board: board) +ds18b20.poll(5) do |reading| + # Start each reading line with a timestamp. + print "#{Time.now.strftime '%Y-%m-%d %H:%M:%S'} - " -temp.read do |temperature| - puts "The temperature is #{temperature} degrees C" + if reading[:crc_error] + puts "CRC check failed for this reading!" + else + # Print converted temperature in degrees C and F and raw 9 bytes from sensor. + print "#{reading[:c]} \xC2\xB0C / #{reading[:f]} \xC2\xB0F / " + puts "Raw: #{reading[:raw].inspect}" + end end + +sleep diff --git a/lib/dino/components/ds18b20.rb b/lib/dino/components/ds18b20.rb index 1651b74e..5df5a5ff 100644 --- a/lib/dino/components/ds18b20.rb +++ b/lib/dino/components/ds18b20.rb @@ -2,11 +2,122 @@ module Dino module Components class DS18B20 include Setup::SinglePin - include Setup::Input include Mixins::Poller + attr_reader :resolution + + def after_initialize(options={}) + super(options) if defined?(super) + # default to 12 bit resolution here. + end + + def resolution= + # send commands to set resolution and store in instance variable + end + + def pre_callback_filter(data) + # Data will be 9 comma delimited bytes as ASCII numbers for now. + bytes = data.split(",").map{|b| b.to_i} + return {crc_error: true} unless crc_check(bytes) + + # Decode temperature from first 2 bytes. + # Returns hash with C and F values. + decode(bytes[1], bytes[0]).merge(raw: bytes, crc_error: false) + end + + # Just store Celsius in @state? + def self_update(hash) + @state = hash[:c] + end + + def crc_check(bytes) + puts bytes.inspect + crc = 0b00000000 + + # Last byte IS the read CRC, so just use the first 8. + bytes.take(8).each do |byte| + # LSB first + for bit in (0..7) + # XOR current bit with the LSB of the CRC. + xor = byte[bit] ^ crc[0] + # XOR CRC bits 3 and 4 with first XOR result, then move right. + crc = crc ^ ((xor * (2 ** 3)) | (xor * (2 ** 4))) + crc = crc >> 1 + # Write first XOR result to the now empty MSB of the CRC. + crc = crc | (xor * (2 ** 7)) # crc[7] = xor + end + end + + # Check against read CRC. + crc == bytes.last + end + + def decode(high_byte, low_byte) + # Make one 16 bit value to work with. + value = high_byte << 8 | low_byte + + # Two's complement, so if MSB is 1, value is -ve degrees C. + negative = (value[15] == 1) + + # Get magnitude. Will reset sign after. + value = (value ^ 0b1111_1111_1111_1111) + 1 if negative + + # Expand the exponents. LSB is 2^-4. + celsius = 0.0 + exp = -4 + for bit in (0..10) + celsius = celsius + (2.0 ** (exp+bit)) if (value[bit] == 1) + end + celsius = -celsius if negative + farenheit = (celsius * 1.8 + 32).round(4) + {c: celsius, f: farenheit} + end def _read - board.ds18b20_read(self.pin) + # bus.synchronize do + reset + skip_rom + convert + # end + + # Wait for A/D conversion in Ruby. Won't block the bus or the board. + # Should scale inversely with resolution. Divide by 2 per bit of resolution. + sleep(0.75) + + # bus.synchronize do + reset + skip_rom + scratch_read + bus_read(9) + # end + end + + def reset + board.write Dino::Message.encode(command: 41, pin: pin) + end + + def bus_read(num_bytes) + board.write Dino::Message.encode(command: 44, pin: pin, value: num_bytes) + end + + def scratch_read + write(0xBE) + end + + def skip_rom + write(0xCC) + end + + def convert + write(0x44) + end + + def write(*bytes) + bytes = bytes.flatten + raise ArgumentError, "wrong number of arguments (given 0, expected at least 1)" if bytes.empty? + + length = bytes.length + bytes = bytes.pack('C*') + board.write Dino::Message.encode(command: 43, pin: pin, value: length, aux_message: bytes) end end end diff --git a/spec/lib/components/ds18b20_spec.rb b/spec/lib/components/ds18b20_spec.rb new file mode 100644 index 00000000..aa5e354d --- /dev/null +++ b/spec/lib/components/ds18b20_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +module Dino + module Components + describe DS18B20 do + include BoardMock + let(:options) { { board: board, pin: 7 } } + subject { DS18B20.new(options) } + + describe '#decode' do + it 'should decode values matching the datasheet and convert C to F' do + expect(subject.decode(0b0000_0111, 0b1101_0000)).to eq(c: 125, f: 257) + expect(subject.decode(0b0000_0000, 0b0000_0000)).to eq(c: 0, f: 32) + expect(subject.decode(0b1111_1111, 0b0101_1110)).to eq(c: -10.125, f: 13.775) + expect(subject.decode(0b1111_1100, 0b1001_0000)).to eq(c: -55, f: -67) + end + end + end + end +end diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index e350c68f..13bc8915 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -129,11 +129,6 @@ void Dino::process() { case 13: dhtRead (); break; #endif - // Implemented in DinoOneWire.cpp - #ifdef DINO_ONE_WIRE - case 15: ds18Read (); break; - #endif - // Implemented in DinoIROut.cpp #ifdef DINO_IR_OUT case 16: irSend (); break; @@ -170,6 +165,14 @@ void Dino::process() { case 35: i2cRead (); break; #endif + // Implemented in DinoOneWire.cpp + #ifdef DINO_ONE_WIRE + case 41: owReset (); break; + case 42: owSearch (); break; + case 43: owWrite (); break; + case 44: owRead (); break; + #endif + // Implemented in this file. case 90: reset (); break; case 95: setRegisterDivider (); break; diff --git a/src/lib/Dino.h b/src/lib/Dino.h index 81c27c96..469e34de 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -73,7 +73,6 @@ class Dino { void handleLCD (); //cmd = 10 void handleSerial (); //cmd = 12 void dhtRead (); //cmd = 13 - void ds18Read (); //cmd = 15 void irSend (); //cmd = 16 void tone (); //cmd = 17 void noTone (); //cmd = 18 @@ -103,6 +102,14 @@ class Dino { void i2cWrite (); //cmd = 34 void i2cRead (); //cmd = 35 + // One Wire + void owReset (); //cmd = 41 + void owSearch (); //cmd = 42 + void owWrite (); //cmd = 43 + void owRead (); //cmd = 44 + void owWriteBit (byte b); + byte owReadBit (); + // API access to timings, resolutions and reset. void reset (); //cmd = 90 void resetState (); diff --git a/src/lib/DinoOneWire.cpp b/src/lib/DinoOneWire.cpp index d157a88a..aaba627c 100644 --- a/src/lib/DinoOneWire.cpp +++ b/src/lib/DinoOneWire.cpp @@ -1,60 +1,97 @@ // -// This file adds to the Dino class only if DINO_ONE_WIRE is defined in Dino.h. +// This file contains low-level functions to get precise (enough) +// timing required for using the Dallas/Maxim 1-Wire protocol. +// Higher level logic is handled in Ruby. // #include "Dino.h" #ifdef DINO_ONE_WIRE -#include "OneWire.h" - -// CMD = 15 -void Dino::ds18Read() { - OneWire ds(pin); - - byte data[12]; - byte addr[8]; +void Dino::owReset(){ + // bool present; + pinMode(pin, OUTPUT); + digitalWrite(pin, LOW); + delayMicroseconds(500); + pinMode(pin, INPUT); + delayMicroseconds(80); + // present = !digitalRead(pin); + delayMicroseconds(420); + // send presence value here +} - if ( !ds.search(addr)) { - ds.reset_search(); - return; - } +void Dino::owSearch(){ +} - if ( OneWire::crc8( addr, 7) != addr[7]) { - // Serial.println("CRC is not valid!"); - return; +// CMD = 43 +// Write to the OneWire bus. +// +// val = number of bytes to write +// auxMsg[0] = first byte of data and so on... +// Limited to 255 bytes. Validate on remote end. +// +void Dino::owWrite(){ + byte b; + for(byte i=0; iprint(pin); stream->print(':'); - for (int i = 0; i < 9; i++) { // we need 9 bytes - data[i] = ds.read(); + // Print each byte read, followed by a comma, or newline for last byte. + for(byte i=0; iprint(b); + stream->print((i == (val-1)) ? '\n' : ','); } +} - ds.reset_search(); +byte Dino::owReadBit(){ + byte b; + // Pull low for at least 1us to start a read time slot, then release. + pinMode(pin, OUTPUT); + digitalWrite(pin, LOW); + delayMicroseconds(1); + pinMode(pin, INPUT); - byte MSB = data[1]; - byte LSB = data[0]; + // Wait for the slave to write to the bus. It should hold for up to 15us. + delayMicroseconds(5); - float tempRead = ((MSB << 8) | LSB); //using two's compliment - float reading = tempRead / 16; - char readingBuff[10]; + // If slave pulled the bus high, the bit a 1, else 0. + b = digitalRead(pin); - if (! isnan(reading)) { - stream->print(pin); - stream->print(':'); - stream->print(reading, 4); - stream->print('\n'); - } + // Wait out the 60us window + recovery time. + delayMicroseconds(55); + return b; } - #endif diff --git a/src/vendor/OneWire b/src/vendor/OneWire deleted file mode 160000 index ac0ea9a7..00000000 --- a/src/vendor/OneWire +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ac0ea9a7fc266b2d709532fcb1269ffe5fcbbb46 From 43e7dea876fecb491b60109d6a8528fe03d57cdb Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 3 Feb 2018 23:50:06 -0400 Subject: [PATCH 160/296] Parasite power detection/support. Extract Bus interface. --- examples/one_wire/ds18b20.rb | 24 +++- lib/dino/components.rb | 2 +- lib/dino/components/ds18b20.rb | 124 -------------------- lib/dino/components/mixins.rb | 1 + lib/dino/components/mixins/board_proxy.rb | 13 +- lib/dino/components/mixins/bus.rb | 27 +++++ lib/dino/components/mixins/poller.rb | 4 +- lib/dino/components/mixins/reader.rb | 4 +- lib/dino/components/onewire.rb | 10 ++ lib/dino/components/onewire/bus.rb | 50 ++++++++ lib/dino/components/onewire/constants.rb | 17 +++ lib/dino/components/onewire/ds18b20.rb | 85 ++++++++++++++ lib/dino/components/onewire/helper.rb | 29 +++++ spec/lib/components/ds18b20_spec.rb | 20 ---- spec/lib/components/onewire/ds18b20_spec.rb | 22 ++++ src/lib/DinoOneWire.cpp | 12 +- 16 files changed, 279 insertions(+), 165 deletions(-) delete mode 100644 lib/dino/components/ds18b20.rb create mode 100644 lib/dino/components/mixins/bus.rb create mode 100644 lib/dino/components/onewire.rb create mode 100644 lib/dino/components/onewire/bus.rb create mode 100644 lib/dino/components/onewire/constants.rb create mode 100644 lib/dino/components/onewire/ds18b20.rb create mode 100644 lib/dino/components/onewire/helper.rb delete mode 100644 spec/lib/components/ds18b20_spec.rb create mode 100644 spec/lib/components/onewire/ds18b20_spec.rb diff --git a/examples/one_wire/ds18b20.rb b/examples/one_wire/ds18b20.rb index 0f428b1e..dd2e889c 100644 --- a/examples/one_wire/ds18b20.rb +++ b/examples/one_wire/ds18b20.rb @@ -5,17 +5,33 @@ require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) -ds18b20 = Dino::Components::DS18B20.new(pin: 16, board: board) +bus = Dino::Components::OneWire::Bus.new(pin:16, board: board) +ds18b20 = Dino::Components::OneWire::DS18B20.new(board: bus) +# Bus can detect if a device is using parasite power, but not WHICH devices. +if bus.parasite_power + puts "Parasite power detected..."; puts +end + +# Blocking read that returns the read value. +temp = ds18b20.read[:celsius] +puts "Single read: #{temp} \xC2\xB0C" + +# Read the most recent value from the component's @state variable. +sleep 0.5 +temp = ds18b20.state[:celsius] +puts "Read from last state: #{temp} \xC2\xB0C" + +# Poll the sensor every 5 seconds with a callback showing C, F and raw bytes. +puts +puts "Start polling..." ds18b20.poll(5) do |reading| - # Start each reading line with a timestamp. print "#{Time.now.strftime '%Y-%m-%d %H:%M:%S'} - " if reading[:crc_error] puts "CRC check failed for this reading!" else - # Print converted temperature in degrees C and F and raw 9 bytes from sensor. - print "#{reading[:c]} \xC2\xB0C / #{reading[:f]} \xC2\xB0F / " + print "#{reading[:celsius]} \xC2\xB0C / #{reading[:farenheit]} \xC2\xB0F / " puts "Raw: #{reading[:raw].inspect}" end end diff --git a/lib/dino/components.rb b/lib/dino/components.rb index e1a62fd0..c4260b74 100644 --- a/lib/dino/components.rb +++ b/lib/dino/components.rb @@ -16,9 +16,9 @@ module Components autoload :Relay, 'dino/components/relay' autoload :SoftwareSerial, 'dino/components/softserial' autoload :DHT, 'dino/components/dht' - autoload :DS18B20, 'dino/components/ds18b20' autoload :IREmitter, 'dino/components/ir_emitter' autoload :Piezo, 'dino/components/piezo' autoload :RotaryEncoder, 'dino/components/rotary_encoder' + autoload :OneWire, 'dino/components/onewire' end end diff --git a/lib/dino/components/ds18b20.rb b/lib/dino/components/ds18b20.rb deleted file mode 100644 index 5df5a5ff..00000000 --- a/lib/dino/components/ds18b20.rb +++ /dev/null @@ -1,124 +0,0 @@ -module Dino - module Components - class DS18B20 - include Setup::SinglePin - include Mixins::Poller - attr_reader :resolution - - def after_initialize(options={}) - super(options) if defined?(super) - # default to 12 bit resolution here. - end - - def resolution= - # send commands to set resolution and store in instance variable - end - - def pre_callback_filter(data) - # Data will be 9 comma delimited bytes as ASCII numbers for now. - bytes = data.split(",").map{|b| b.to_i} - return {crc_error: true} unless crc_check(bytes) - - # Decode temperature from first 2 bytes. - # Returns hash with C and F values. - decode(bytes[1], bytes[0]).merge(raw: bytes, crc_error: false) - end - - # Just store Celsius in @state? - def self_update(hash) - @state = hash[:c] - end - - def crc_check(bytes) - puts bytes.inspect - crc = 0b00000000 - - # Last byte IS the read CRC, so just use the first 8. - bytes.take(8).each do |byte| - # LSB first - for bit in (0..7) - # XOR current bit with the LSB of the CRC. - xor = byte[bit] ^ crc[0] - # XOR CRC bits 3 and 4 with first XOR result, then move right. - crc = crc ^ ((xor * (2 ** 3)) | (xor * (2 ** 4))) - crc = crc >> 1 - # Write first XOR result to the now empty MSB of the CRC. - crc = crc | (xor * (2 ** 7)) # crc[7] = xor - end - end - - # Check against read CRC. - crc == bytes.last - end - - def decode(high_byte, low_byte) - # Make one 16 bit value to work with. - value = high_byte << 8 | low_byte - - # Two's complement, so if MSB is 1, value is -ve degrees C. - negative = (value[15] == 1) - - # Get magnitude. Will reset sign after. - value = (value ^ 0b1111_1111_1111_1111) + 1 if negative - - # Expand the exponents. LSB is 2^-4. - celsius = 0.0 - exp = -4 - for bit in (0..10) - celsius = celsius + (2.0 ** (exp+bit)) if (value[bit] == 1) - end - celsius = -celsius if negative - farenheit = (celsius * 1.8 + 32).round(4) - {c: celsius, f: farenheit} - end - - def _read - # bus.synchronize do - reset - skip_rom - convert - # end - - # Wait for A/D conversion in Ruby. Won't block the bus or the board. - # Should scale inversely with resolution. Divide by 2 per bit of resolution. - sleep(0.75) - - # bus.synchronize do - reset - skip_rom - scratch_read - bus_read(9) - # end - end - - def reset - board.write Dino::Message.encode(command: 41, pin: pin) - end - - def bus_read(num_bytes) - board.write Dino::Message.encode(command: 44, pin: pin, value: num_bytes) - end - - def scratch_read - write(0xBE) - end - - def skip_rom - write(0xCC) - end - - def convert - write(0x44) - end - - def write(*bytes) - bytes = bytes.flatten - raise ArgumentError, "wrong number of arguments (given 0, expected at least 1)" if bytes.empty? - - length = bytes.length - bytes = bytes.pack('C*') - board.write Dino::Message.encode(command: 43, pin: pin, value: length, aux_message: bytes) - end - end - end -end diff --git a/lib/dino/components/mixins.rb b/lib/dino/components/mixins.rb index 6e8afe56..9dfe8b26 100644 --- a/lib/dino/components/mixins.rb +++ b/lib/dino/components/mixins.rb @@ -6,6 +6,7 @@ module Mixins require 'dino/components/mixins/reader' require 'dino/components/mixins/poller' require 'dino/components/mixins/listener' + require 'dino/components/mixins/bus' require 'dino/components/mixins/board_proxy' end end diff --git a/lib/dino/components/mixins/board_proxy.rb b/lib/dino/components/mixins/board_proxy.rb index 961a4753..a256b3be 100644 --- a/lib/dino/components/mixins/board_proxy.rb +++ b/lib/dino/components/mixins/board_proxy.rb @@ -2,22 +2,15 @@ module Dino module Components module Mixins module BoardProxy + include Bus + def after_initialize(options={}) super(options) if defined?(super) @high = 1 @low = 0 - @components = [] end - attr_reader :high, :low, :components - - def add_component(component) - @components << component - end - - def remove_component(component) - @components.delete(component) - end + attr_reader :high, :low def convert_pin(pin) pin = pin.to_i diff --git a/lib/dino/components/mixins/bus.rb b/lib/dino/components/mixins/bus.rb new file mode 100644 index 00000000..b1c80974 --- /dev/null +++ b/lib/dino/components/mixins/bus.rb @@ -0,0 +1,27 @@ +module Dino + module Components + module Mixins + module Bus + # + # Behavior mixin for any component that tracks multiple other components + # attached to it, matching Board, but without the full Board interface. + # See OneWire::Bus class for an example. + # + def after_initialize(options={}) + super(options) if defined?(super) + @components = [] + end + + attr_reader :components + + def add_component(component) + @components << component + end + + def remove_component(component) + @components.delete(component) + end + end + end + end +end diff --git a/lib/dino/components/mixins/poller.rb b/lib/dino/components/mixins/poller.rb index 35b17938..c90e622b 100644 --- a/lib/dino/components/mixins/poller.rb +++ b/lib/dino/components/mixins/poller.rb @@ -5,11 +5,11 @@ module Poller include Reader include Threaded - def poll(interval=1, &block) + def poll(interval=1, *args, &block) stop add_callback(:poll, &block) if block_given? threaded_loop do - _read; sleep interval + _read(*args); sleep interval end end diff --git a/lib/dino/components/mixins/reader.rb b/lib/dino/components/mixins/reader.rb index 03ad8aec..4c1a4038 100644 --- a/lib/dino/components/mixins/reader.rb +++ b/lib/dino/components/mixins/reader.rb @@ -4,13 +4,13 @@ module Mixins module Reader include Callbacks - def read(&block) + def read(*args, &block) add_callback(:read, &block) if block_given? value = nil add_callback(:read) { |data| value = data } - _read + _read(*args) loop { break if !@callbacks[:read] } value diff --git a/lib/dino/components/onewire.rb b/lib/dino/components/onewire.rb new file mode 100644 index 00000000..3a4bd8c4 --- /dev/null +++ b/lib/dino/components/onewire.rb @@ -0,0 +1,10 @@ +module Dino + module Components + module OneWire + require 'dino/components/onewire/constants' + require 'dino/components/onewire/helper' + require 'dino/components/onewire/bus' + require 'dino/components/onewire/ds18b20' + end + end +end diff --git a/lib/dino/components/onewire/bus.rb b/lib/dino/components/onewire/bus.rb new file mode 100644 index 00000000..ccdac631 --- /dev/null +++ b/lib/dino/components/onewire/bus.rb @@ -0,0 +1,50 @@ +module Dino + module Components + module OneWire + class Bus < Basic::DigitalOutput + include Mixins::Bus + include Mixins::Reader + + attr_reader :parasite_power, :mutex + + def after_initialize(options = {}) + super(options) if defined?(super) + @mutex = Mutex.new + read_power_supply + end + + def read_power_supply + sleep 0.1 + reset + write(SKIP_ROM, READ_POWER_SUPPLY) + byte = read(1) + @parasite_power = (byte.to_i[0] == 0) ? true : false + end + + def reset + board.write Dino::Message.encode(command: 41, pin: pin) + end + + def _read(num_bytes) + board.write Dino::Message.encode(command: 44, pin: pin, value: num_bytes) + end + + def write(*bytes) + bytes = bytes.flatten + raise ArgumentError, "wrong number of arguments (given 0, expected at least 1)" if bytes.empty? + + length = bytes.length + raise Exception.new('max 127 bytes for single OneWire write') if length > 127 + + # Set flag if last byte is a command requiring parasite power after it. + if parasite_power && [CONVERT_T, COPY_SCRATCH].include?(bytes.last) + length = length | 0b10000000 + end + + bytes = bytes.pack('C*') + board.write Dino::Message.encode(command: 43, pin: pin, value: length, aux_message: bytes) + end + end + end + end +end diff --git a/lib/dino/components/onewire/constants.rb b/lib/dino/components/onewire/constants.rb new file mode 100644 index 00000000..0e62c976 --- /dev/null +++ b/lib/dino/components/onewire/constants.rb @@ -0,0 +1,17 @@ +module Dino + module Components + module OneWire + READ_POWER_SUPPLY = 0xB4 + CONVERT_T = 0x44 + SEARCH_ROM = 0xF0 + READ_ROM = 0x33 + SKIP_ROM = 0xCC + MATCH_ROM = 0x55 + ALARM_SEARCH = 0xEC + READ_SCRATCH = 0xBE + WRITE_SCRATCH = 0x4E + COPY_SCRATCH = 0x48 + RECALL_EEPROM = 0xB8 + end + end +end diff --git a/lib/dino/components/onewire/ds18b20.rb b/lib/dino/components/onewire/ds18b20.rb new file mode 100644 index 00000000..936b32ca --- /dev/null +++ b/lib/dino/components/onewire/ds18b20.rb @@ -0,0 +1,85 @@ +module Dino + module Components + module OneWire + class DS18B20 + include Setup::Base + include Mixins::Poller + attr_reader :resolution, :parasite_power + + alias :bus :board + + def after_initialize(options={}) + super(options) if defined?(super) + # Should set only if given as parameter. + # If not, read from sensor and leave as is. + self.resolution = 12 + end + + def resolution + # Read scratchpad and resolution from sensor here. + end + + def resolution=(bits) + raise 'Invalid resolution for DS18B20 sensor' if (bits > 12 || bits < 9) + @resolution = bits + # Send commands to set resolution here. + @convert_time = 0.75 / (13 - bits) + end + + def _read + bus.mutex.synchronize do + bus.reset + bus.write(SKIP_ROM, CONVERT_T) + sleep @convert_time if bus.parasite_power + end + + sleep @convert_time unless bus.parasite_power + data = nil + + bus.mutex.synchronize do + bus.reset + bus.write(SKIP_ROM, READ_SCRATCH) + data = bus.read(9) + end + + # Once we got data, free the bus and run callbacks ourselves. + self.update(data) + end + + # + # Data comes in as 9 comma delimited bytes in ASCII numbers. + # First 2 bytes are a coded little-endian number containing degrees C. + # Check CRC first. If good, decode and pass on degrees C, F and raw data. + # + def pre_callback_filter(data) + bytes = data.split(",").map{|b| b.to_i} + return {crc_error: true} unless Helper.crc_check(bytes) + + decode(bytes[1], bytes[0]).merge(raw: bytes) + end + + # + # Temperature is the first 16 bits (2 bytes of 9 read), little-endian. + # It's a sign-extended two's complement 11-bit decimal, where LSB is the + # 2^-4 exponent, up through 2^6 for bit 11. The 5 MSBs repeat the sign. + # + def decode(high_byte, low_byte) + # Concatenate to 16-bit and reverse two's complement if necessary. + value = high_byte << 8 | low_byte + negative = (value[15] == 1) + value = (value ^ 0xFFFF) + 1 if negative + + # Expand the exponents, restore sign, and convert to Farenheit. + celsius = 0.0; exp = -4 + for bit in (0..10) + celsius = celsius + (2.0 ** (exp+bit)) if (value[bit] == 1) + end + celsius = -celsius if negative + farenheit = (celsius * 1.8 + 32).round(4) + + {celsius: celsius, farenheit: farenheit} + end + end + end + end +end diff --git a/lib/dino/components/onewire/helper.rb b/lib/dino/components/onewire/helper.rb new file mode 100644 index 00000000..5fedcfc0 --- /dev/null +++ b/lib/dino/components/onewire/helper.rb @@ -0,0 +1,29 @@ +module Dino + module Components + module OneWire + class Helper + # + # CRC is a single byte. Start with it set to 0. Move through first 8 + # bytes in the order read, but bitwise LSBFIRST. For each bit: + # 1) XOR the bit with the current LSB of CRC + # 2) XOR the result of #1 with the bits at indices 3 and 4 of CRC. + # 3) Bitshift the CRC right by 1 + # 4) Write the result of #1 to the now empty MSB of the CRC + # After 64 bits, the CRC is valid if it matches the 9th of the read data. + # + def self.crc_check(bytes) + crc = 0b00000000 + bytes.take(bytes.length - 1).each do |byte| + for bit in (0..7) + xor = byte[bit] ^ crc[0] + crc = crc ^ ((xor * (2 ** 3)) | (xor * (2 ** 4))) + crc = crc >> 1 + crc = crc | (xor * (2 ** 7)) + end + end + crc == bytes.last + end + end + end + end +end diff --git a/spec/lib/components/ds18b20_spec.rb b/spec/lib/components/ds18b20_spec.rb deleted file mode 100644 index aa5e354d..00000000 --- a/spec/lib/components/ds18b20_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - describe DS18B20 do - include BoardMock - let(:options) { { board: board, pin: 7 } } - subject { DS18B20.new(options) } - - describe '#decode' do - it 'should decode values matching the datasheet and convert C to F' do - expect(subject.decode(0b0000_0111, 0b1101_0000)).to eq(c: 125, f: 257) - expect(subject.decode(0b0000_0000, 0b0000_0000)).to eq(c: 0, f: 32) - expect(subject.decode(0b1111_1111, 0b0101_1110)).to eq(c: -10.125, f: 13.775) - expect(subject.decode(0b1111_1100, 0b1001_0000)).to eq(c: -55, f: -67) - end - end - end - end -end diff --git a/spec/lib/components/onewire/ds18b20_spec.rb b/spec/lib/components/onewire/ds18b20_spec.rb new file mode 100644 index 00000000..4a0a3099 --- /dev/null +++ b/spec/lib/components/onewire/ds18b20_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +module Dino + module Components + module OneWire + describe DS18B20 do + include BoardMock + let(:options) { { board: board, pin: 7 } } + subject { DS18B20.new(options) } + + describe '#decode' do + it 'should decode values matching the datasheet and convert C to F' do + expect(subject.decode(0b0000_0111, 0b1101_0000)).to eq(celsius: 125, farenheit: 257) + expect(subject.decode(0b0000_0000, 0b0000_0000)).to eq(celsius: 0, farenheit: 32) + expect(subject.decode(0b1111_1111, 0b0101_1110)).to eq(celsius: -10.125, farenheit: 13.775) + expect(subject.decode(0b1111_1100, 0b1001_0000)).to eq(celsius: -55, farenheit: -67) + end + end + end + end + end +end diff --git a/src/lib/DinoOneWire.cpp b/src/lib/DinoOneWire.cpp index aaba627c..a1f98836 100644 --- a/src/lib/DinoOneWire.cpp +++ b/src/lib/DinoOneWire.cpp @@ -24,11 +24,14 @@ void Dino::owSearch(){ // CMD = 43 // Write to the OneWire bus. // -// val = number of bytes to write +// val = number of bytes to write + parasite power condition OR-ed into MSB. // auxMsg[0] = first byte of data and so on... -// Limited to 255 bytes. Validate on remote end. +// Limited to 127 bytes. Validate on remote end. // void Dino::owWrite(){ + bool parasite = bitRead(val, 7); + bitClear(val, 7); + byte b; for(byte i=0; i Date: Sun, 4 Feb 2018 00:06:58 -0400 Subject: [PATCH 161/296] Cleanup one wire file names. Clearer code for parasite power detect. --- lib/dino/components.rb | 2 +- lib/dino/components/one_wire.rb | 10 +++ lib/dino/components/one_wire/bus.rb | 54 +++++++++++++ lib/dino/components/one_wire/constants.rb | 17 ++++ lib/dino/components/one_wire/ds18b20.rb | 85 ++++++++++++++++++++ lib/dino/components/one_wire/helper.rb | 29 +++++++ lib/dino/components/onewire.rb | 10 --- spec/lib/components/one_wire/ds18b20_spec.rb | 22 +++++ 8 files changed, 218 insertions(+), 11 deletions(-) create mode 100644 lib/dino/components/one_wire.rb create mode 100644 lib/dino/components/one_wire/bus.rb create mode 100644 lib/dino/components/one_wire/constants.rb create mode 100644 lib/dino/components/one_wire/ds18b20.rb create mode 100644 lib/dino/components/one_wire/helper.rb delete mode 100644 lib/dino/components/onewire.rb create mode 100644 spec/lib/components/one_wire/ds18b20_spec.rb diff --git a/lib/dino/components.rb b/lib/dino/components.rb index c4260b74..8a58195f 100644 --- a/lib/dino/components.rb +++ b/lib/dino/components.rb @@ -19,6 +19,6 @@ module Components autoload :IREmitter, 'dino/components/ir_emitter' autoload :Piezo, 'dino/components/piezo' autoload :RotaryEncoder, 'dino/components/rotary_encoder' - autoload :OneWire, 'dino/components/onewire' + autoload :OneWire, 'dino/components/one_wire' end end diff --git a/lib/dino/components/one_wire.rb b/lib/dino/components/one_wire.rb new file mode 100644 index 00000000..3929599c --- /dev/null +++ b/lib/dino/components/one_wire.rb @@ -0,0 +1,10 @@ +module Dino + module Components + module OneWire + require 'dino/components/one_wire/constants' + require 'dino/components/one_wire/helper' + require 'dino/components/one_wire/bus' + require 'dino/components/one_wire/ds18b20' + end + end +end diff --git a/lib/dino/components/one_wire/bus.rb b/lib/dino/components/one_wire/bus.rb new file mode 100644 index 00000000..bc5aeeb2 --- /dev/null +++ b/lib/dino/components/one_wire/bus.rb @@ -0,0 +1,54 @@ +module Dino + module Components + module OneWire + class Bus + include Setup::SinglePin + include Mixins::Bus + include Mixins::Reader + + attr_reader :parasite_power, :mutex + + def after_initialize(options = {}) + super(options) if defined?(super) + @mutex = Mutex.new + read_power_supply + end + + def read_power_supply + self.mode = :out + board.digital_write(self.pin, board.low) + sleep 0.1 + + reset + write(SKIP_ROM, READ_POWER_SUPPLY) + byte = read(1) + @parasite_power = (byte.to_i[0] == 0) ? true : false + end + + def reset + board.write Dino::Message.encode(command: 41, pin: pin) + end + + def _read(num_bytes) + board.write Dino::Message.encode(command: 44, pin: pin, value: num_bytes) + end + + def write(*bytes) + bytes = bytes.flatten + raise ArgumentError, "wrong number of arguments (given 0, expected at least 1)" if bytes.empty? + + length = bytes.length + raise Exception.new('max 127 bytes for single OneWire write') if length > 127 + + # Set flag if last byte is a command requiring parasite power after it. + if parasite_power && [CONVERT_T, COPY_SCRATCH].include?(bytes.last) + length = length | 0b10000000 + end + + bytes = bytes.pack('C*') + board.write Dino::Message.encode(command: 43, pin: pin, value: length, aux_message: bytes) + end + end + end + end +end diff --git a/lib/dino/components/one_wire/constants.rb b/lib/dino/components/one_wire/constants.rb new file mode 100644 index 00000000..0e62c976 --- /dev/null +++ b/lib/dino/components/one_wire/constants.rb @@ -0,0 +1,17 @@ +module Dino + module Components + module OneWire + READ_POWER_SUPPLY = 0xB4 + CONVERT_T = 0x44 + SEARCH_ROM = 0xF0 + READ_ROM = 0x33 + SKIP_ROM = 0xCC + MATCH_ROM = 0x55 + ALARM_SEARCH = 0xEC + READ_SCRATCH = 0xBE + WRITE_SCRATCH = 0x4E + COPY_SCRATCH = 0x48 + RECALL_EEPROM = 0xB8 + end + end +end diff --git a/lib/dino/components/one_wire/ds18b20.rb b/lib/dino/components/one_wire/ds18b20.rb new file mode 100644 index 00000000..936b32ca --- /dev/null +++ b/lib/dino/components/one_wire/ds18b20.rb @@ -0,0 +1,85 @@ +module Dino + module Components + module OneWire + class DS18B20 + include Setup::Base + include Mixins::Poller + attr_reader :resolution, :parasite_power + + alias :bus :board + + def after_initialize(options={}) + super(options) if defined?(super) + # Should set only if given as parameter. + # If not, read from sensor and leave as is. + self.resolution = 12 + end + + def resolution + # Read scratchpad and resolution from sensor here. + end + + def resolution=(bits) + raise 'Invalid resolution for DS18B20 sensor' if (bits > 12 || bits < 9) + @resolution = bits + # Send commands to set resolution here. + @convert_time = 0.75 / (13 - bits) + end + + def _read + bus.mutex.synchronize do + bus.reset + bus.write(SKIP_ROM, CONVERT_T) + sleep @convert_time if bus.parasite_power + end + + sleep @convert_time unless bus.parasite_power + data = nil + + bus.mutex.synchronize do + bus.reset + bus.write(SKIP_ROM, READ_SCRATCH) + data = bus.read(9) + end + + # Once we got data, free the bus and run callbacks ourselves. + self.update(data) + end + + # + # Data comes in as 9 comma delimited bytes in ASCII numbers. + # First 2 bytes are a coded little-endian number containing degrees C. + # Check CRC first. If good, decode and pass on degrees C, F and raw data. + # + def pre_callback_filter(data) + bytes = data.split(",").map{|b| b.to_i} + return {crc_error: true} unless Helper.crc_check(bytes) + + decode(bytes[1], bytes[0]).merge(raw: bytes) + end + + # + # Temperature is the first 16 bits (2 bytes of 9 read), little-endian. + # It's a sign-extended two's complement 11-bit decimal, where LSB is the + # 2^-4 exponent, up through 2^6 for bit 11. The 5 MSBs repeat the sign. + # + def decode(high_byte, low_byte) + # Concatenate to 16-bit and reverse two's complement if necessary. + value = high_byte << 8 | low_byte + negative = (value[15] == 1) + value = (value ^ 0xFFFF) + 1 if negative + + # Expand the exponents, restore sign, and convert to Farenheit. + celsius = 0.0; exp = -4 + for bit in (0..10) + celsius = celsius + (2.0 ** (exp+bit)) if (value[bit] == 1) + end + celsius = -celsius if negative + farenheit = (celsius * 1.8 + 32).round(4) + + {celsius: celsius, farenheit: farenheit} + end + end + end + end +end diff --git a/lib/dino/components/one_wire/helper.rb b/lib/dino/components/one_wire/helper.rb new file mode 100644 index 00000000..5fedcfc0 --- /dev/null +++ b/lib/dino/components/one_wire/helper.rb @@ -0,0 +1,29 @@ +module Dino + module Components + module OneWire + class Helper + # + # CRC is a single byte. Start with it set to 0. Move through first 8 + # bytes in the order read, but bitwise LSBFIRST. For each bit: + # 1) XOR the bit with the current LSB of CRC + # 2) XOR the result of #1 with the bits at indices 3 and 4 of CRC. + # 3) Bitshift the CRC right by 1 + # 4) Write the result of #1 to the now empty MSB of the CRC + # After 64 bits, the CRC is valid if it matches the 9th of the read data. + # + def self.crc_check(bytes) + crc = 0b00000000 + bytes.take(bytes.length - 1).each do |byte| + for bit in (0..7) + xor = byte[bit] ^ crc[0] + crc = crc ^ ((xor * (2 ** 3)) | (xor * (2 ** 4))) + crc = crc >> 1 + crc = crc | (xor * (2 ** 7)) + end + end + crc == bytes.last + end + end + end + end +end diff --git a/lib/dino/components/onewire.rb b/lib/dino/components/onewire.rb deleted file mode 100644 index 3a4bd8c4..00000000 --- a/lib/dino/components/onewire.rb +++ /dev/null @@ -1,10 +0,0 @@ -module Dino - module Components - module OneWire - require 'dino/components/onewire/constants' - require 'dino/components/onewire/helper' - require 'dino/components/onewire/bus' - require 'dino/components/onewire/ds18b20' - end - end -end diff --git a/spec/lib/components/one_wire/ds18b20_spec.rb b/spec/lib/components/one_wire/ds18b20_spec.rb new file mode 100644 index 00000000..4a0a3099 --- /dev/null +++ b/spec/lib/components/one_wire/ds18b20_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +module Dino + module Components + module OneWire + describe DS18B20 do + include BoardMock + let(:options) { { board: board, pin: 7 } } + subject { DS18B20.new(options) } + + describe '#decode' do + it 'should decode values matching the datasheet and convert C to F' do + expect(subject.decode(0b0000_0111, 0b1101_0000)).to eq(celsius: 125, farenheit: 257) + expect(subject.decode(0b0000_0000, 0b0000_0000)).to eq(celsius: 0, farenheit: 32) + expect(subject.decode(0b1111_1111, 0b0101_1110)).to eq(celsius: -10.125, farenheit: 13.775) + expect(subject.decode(0b1111_1100, 0b1001_0000)).to eq(celsius: -55, farenheit: -67) + end + end + end + end + end +end From ea206093e82f516d119c3af701466990c5dce7d6 Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 4 Feb 2018 00:09:26 -0400 Subject: [PATCH 162/296] Missing deletions from last commit? --- lib/dino/components/onewire/bus.rb | 50 ------------ lib/dino/components/onewire/constants.rb | 17 ----- lib/dino/components/onewire/ds18b20.rb | 85 --------------------- lib/dino/components/onewire/helper.rb | 29 ------- spec/lib/components/onewire/ds18b20_spec.rb | 22 ------ 5 files changed, 203 deletions(-) delete mode 100644 lib/dino/components/onewire/bus.rb delete mode 100644 lib/dino/components/onewire/constants.rb delete mode 100644 lib/dino/components/onewire/ds18b20.rb delete mode 100644 lib/dino/components/onewire/helper.rb delete mode 100644 spec/lib/components/onewire/ds18b20_spec.rb diff --git a/lib/dino/components/onewire/bus.rb b/lib/dino/components/onewire/bus.rb deleted file mode 100644 index ccdac631..00000000 --- a/lib/dino/components/onewire/bus.rb +++ /dev/null @@ -1,50 +0,0 @@ -module Dino - module Components - module OneWire - class Bus < Basic::DigitalOutput - include Mixins::Bus - include Mixins::Reader - - attr_reader :parasite_power, :mutex - - def after_initialize(options = {}) - super(options) if defined?(super) - @mutex = Mutex.new - read_power_supply - end - - def read_power_supply - sleep 0.1 - reset - write(SKIP_ROM, READ_POWER_SUPPLY) - byte = read(1) - @parasite_power = (byte.to_i[0] == 0) ? true : false - end - - def reset - board.write Dino::Message.encode(command: 41, pin: pin) - end - - def _read(num_bytes) - board.write Dino::Message.encode(command: 44, pin: pin, value: num_bytes) - end - - def write(*bytes) - bytes = bytes.flatten - raise ArgumentError, "wrong number of arguments (given 0, expected at least 1)" if bytes.empty? - - length = bytes.length - raise Exception.new('max 127 bytes for single OneWire write') if length > 127 - - # Set flag if last byte is a command requiring parasite power after it. - if parasite_power && [CONVERT_T, COPY_SCRATCH].include?(bytes.last) - length = length | 0b10000000 - end - - bytes = bytes.pack('C*') - board.write Dino::Message.encode(command: 43, pin: pin, value: length, aux_message: bytes) - end - end - end - end -end diff --git a/lib/dino/components/onewire/constants.rb b/lib/dino/components/onewire/constants.rb deleted file mode 100644 index 0e62c976..00000000 --- a/lib/dino/components/onewire/constants.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Dino - module Components - module OneWire - READ_POWER_SUPPLY = 0xB4 - CONVERT_T = 0x44 - SEARCH_ROM = 0xF0 - READ_ROM = 0x33 - SKIP_ROM = 0xCC - MATCH_ROM = 0x55 - ALARM_SEARCH = 0xEC - READ_SCRATCH = 0xBE - WRITE_SCRATCH = 0x4E - COPY_SCRATCH = 0x48 - RECALL_EEPROM = 0xB8 - end - end -end diff --git a/lib/dino/components/onewire/ds18b20.rb b/lib/dino/components/onewire/ds18b20.rb deleted file mode 100644 index 936b32ca..00000000 --- a/lib/dino/components/onewire/ds18b20.rb +++ /dev/null @@ -1,85 +0,0 @@ -module Dino - module Components - module OneWire - class DS18B20 - include Setup::Base - include Mixins::Poller - attr_reader :resolution, :parasite_power - - alias :bus :board - - def after_initialize(options={}) - super(options) if defined?(super) - # Should set only if given as parameter. - # If not, read from sensor and leave as is. - self.resolution = 12 - end - - def resolution - # Read scratchpad and resolution from sensor here. - end - - def resolution=(bits) - raise 'Invalid resolution for DS18B20 sensor' if (bits > 12 || bits < 9) - @resolution = bits - # Send commands to set resolution here. - @convert_time = 0.75 / (13 - bits) - end - - def _read - bus.mutex.synchronize do - bus.reset - bus.write(SKIP_ROM, CONVERT_T) - sleep @convert_time if bus.parasite_power - end - - sleep @convert_time unless bus.parasite_power - data = nil - - bus.mutex.synchronize do - bus.reset - bus.write(SKIP_ROM, READ_SCRATCH) - data = bus.read(9) - end - - # Once we got data, free the bus and run callbacks ourselves. - self.update(data) - end - - # - # Data comes in as 9 comma delimited bytes in ASCII numbers. - # First 2 bytes are a coded little-endian number containing degrees C. - # Check CRC first. If good, decode and pass on degrees C, F and raw data. - # - def pre_callback_filter(data) - bytes = data.split(",").map{|b| b.to_i} - return {crc_error: true} unless Helper.crc_check(bytes) - - decode(bytes[1], bytes[0]).merge(raw: bytes) - end - - # - # Temperature is the first 16 bits (2 bytes of 9 read), little-endian. - # It's a sign-extended two's complement 11-bit decimal, where LSB is the - # 2^-4 exponent, up through 2^6 for bit 11. The 5 MSBs repeat the sign. - # - def decode(high_byte, low_byte) - # Concatenate to 16-bit and reverse two's complement if necessary. - value = high_byte << 8 | low_byte - negative = (value[15] == 1) - value = (value ^ 0xFFFF) + 1 if negative - - # Expand the exponents, restore sign, and convert to Farenheit. - celsius = 0.0; exp = -4 - for bit in (0..10) - celsius = celsius + (2.0 ** (exp+bit)) if (value[bit] == 1) - end - celsius = -celsius if negative - farenheit = (celsius * 1.8 + 32).round(4) - - {celsius: celsius, farenheit: farenheit} - end - end - end - end -end diff --git a/lib/dino/components/onewire/helper.rb b/lib/dino/components/onewire/helper.rb deleted file mode 100644 index 5fedcfc0..00000000 --- a/lib/dino/components/onewire/helper.rb +++ /dev/null @@ -1,29 +0,0 @@ -module Dino - module Components - module OneWire - class Helper - # - # CRC is a single byte. Start with it set to 0. Move through first 8 - # bytes in the order read, but bitwise LSBFIRST. For each bit: - # 1) XOR the bit with the current LSB of CRC - # 2) XOR the result of #1 with the bits at indices 3 and 4 of CRC. - # 3) Bitshift the CRC right by 1 - # 4) Write the result of #1 to the now empty MSB of the CRC - # After 64 bits, the CRC is valid if it matches the 9th of the read data. - # - def self.crc_check(bytes) - crc = 0b00000000 - bytes.take(bytes.length - 1).each do |byte| - for bit in (0..7) - xor = byte[bit] ^ crc[0] - crc = crc ^ ((xor * (2 ** 3)) | (xor * (2 ** 4))) - crc = crc >> 1 - crc = crc | (xor * (2 ** 7)) - end - end - crc == bytes.last - end - end - end - end -end diff --git a/spec/lib/components/onewire/ds18b20_spec.rb b/spec/lib/components/onewire/ds18b20_spec.rb deleted file mode 100644 index 4a0a3099..00000000 --- a/spec/lib/components/onewire/ds18b20_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module OneWire - describe DS18B20 do - include BoardMock - let(:options) { { board: board, pin: 7 } } - subject { DS18B20.new(options) } - - describe '#decode' do - it 'should decode values matching the datasheet and convert C to F' do - expect(subject.decode(0b0000_0111, 0b1101_0000)).to eq(celsius: 125, farenheit: 257) - expect(subject.decode(0b0000_0000, 0b0000_0000)).to eq(celsius: 0, farenheit: 32) - expect(subject.decode(0b1111_1111, 0b0101_1110)).to eq(celsius: -10.125, farenheit: 13.775) - expect(subject.decode(0b1111_1100, 0b1001_0000)).to eq(celsius: -55, farenheit: -67) - end - end - end - end - end -end From 16e3be4142757857164b0771c4da6d2f3f26862f Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 5 Feb 2018 00:04:16 -0400 Subject: [PATCH 163/296] Add OneWire auto search and identify --- examples/one_wire/ds18b20.rb | 35 ++++--- lib/dino/components/mixins/reader.rb | 6 +- lib/dino/components/one_wire/bus.rb | 64 ++++++++++- lib/dino/components/one_wire/ds18b20.rb | 48 +++++++-- lib/dino/components/one_wire/helper.rb | 105 ++++++++++++++++++- spec/lib/components/one_wire/ds18b20_spec.rb | 2 +- src/lib/DinoOneWire.cpp | 44 ++++++++ 7 files changed, 274 insertions(+), 30 deletions(-) diff --git a/examples/one_wire/ds18b20.rb b/examples/one_wire/ds18b20.rb index dd2e889c..a2948264 100644 --- a/examples/one_wire/ds18b20.rb +++ b/examples/one_wire/ds18b20.rb @@ -6,27 +6,30 @@ board = Dino::Board.new(Dino::TxRx::Serial.new) bus = Dino::Components::OneWire::Bus.new(pin:16, board: board) -ds18b20 = Dino::Components::OneWire::DS18B20.new(board: bus) -# Bus can detect if a device is using parasite power, but not WHICH devices. +# The bus does parasite power detection on startup. +# It can tell that parasite power is in use, but not by WHICH devices. if bus.parasite_power puts "Parasite power detected..."; puts end -# Blocking read that returns the read value. -temp = ds18b20.read[:celsius] -puts "Single read: #{temp} \xC2\xB0C" +# The bus automatically searches when initialized. It finds the address of +# every device, identifies the device type and matching Ruby class, storing here. +puts "Found #{bus.found_devices.count} devices on the bus:" +puts bus.found_devices.inspect; puts -# Read the most recent value from the component's @state variable. -sleep 0.5 -temp = ds18b20.state[:celsius] -puts "Read from last state: #{temp} \xC2\xB0C" +# We can use the search results to setup instances of the device classes. +ds18b20s = [] +bus.found_devices.each do |d| + if d[:class] == Dino::Components::OneWire::DS18B20 + ds18b20s << Dino::Components::OneWire::DS18B20.new(board: bus, address: d[:address]) + end +end -# Poll the sensor every 5 seconds with a callback showing C, F and raw bytes. -puts -puts "Start polling..." -ds18b20.poll(5) do |reading| +# Format a reading for printing on a line. +def print_reading(reading, sensor, i) print "#{Time.now.strftime '%Y-%m-%d %H:%M:%S'} - " + print "index: #{i}, serial# :#{sensor.serial} " if reading[:crc_error] puts "CRC check failed for this reading!" @@ -36,4 +39,8 @@ end end -sleep +# Read the temp from each sensor in a simple loop. +loop do + ds18b20s.each_with_index { |s,i| print_reading(s.read, s, i) } + sleep 5 +end diff --git a/lib/dino/components/mixins/reader.rb b/lib/dino/components/mixins/reader.rb index 4c1a4038..8749258c 100644 --- a/lib/dino/components/mixins/reader.rb +++ b/lib/dino/components/mixins/reader.rb @@ -11,11 +11,15 @@ def read(*args, &block) add_callback(:read) { |data| value = data } _read(*args) - loop { break if !@callbacks[:read] } + block_until_read value end + def block_until_read + loop { break if !@callbacks[:read] } + end + def _read raise NotImplementedError .new("#{self.class.name}#_read is not defined.") diff --git a/lib/dino/components/one_wire/bus.rb b/lib/dino/components/one_wire/bus.rb index bc5aeeb2..88aa6cfa 100644 --- a/lib/dino/components/one_wire/bus.rb +++ b/lib/dino/components/one_wire/bus.rb @@ -6,15 +6,77 @@ class Bus include Mixins::Bus include Mixins::Reader - attr_reader :parasite_power, :mutex + attr_reader :found_devices, :parasite_power, :mutex def after_initialize(options = {}) super(options) if defined?(super) @mutex = Mutex.new + @found_devices = [] read_power_supply + search end + def search + branch_mask = 0 + high_discrepancy = 0 + + loop do + self.add_callback(:read) do |result| + # + # We get a device hash (class and ROM address) as a parsed result, + # plus the bit index of the highest discrepancy from the last run. + # + device, high_discrepancy = Helper.parse_search_result(result) + @found_devices << device + end + _search(branch_mask) + block_until_read + + # + # Since we're zero-indexed unlike the datasheet, this is when the + # search ends. There's no discrepancy left even at the lowest bit. + # + break if high_discrepancy == - 1 + + # + # If high_discrepancy was not a forced 1 last time, do it next time. + # This way we go as deep as possible into each new branch first. + # + if branch_mask[high_discrepancy] == 0 + branch_mask = branch_mask | (2 ** high_discrepancy) + end + + # + # Clear bits above high_discrepancy so we don't repeat branches. + # When high_discrepancy < MSB of branch_mask, this moves us + # one node closer to the root and finishing the search. + # + unset_mask = 0xFFFFFFFFFFFFFFFF >> (63 - high_discrepancy) + branch_mask = branch_mask & unset_mask + end + + # Just to be safe... + @found_devices = @found_devices.uniq + end + + # + # Reset the bus, then send the search command, along with a 64-bit + # mask of bits to write 1 for. + # + def _search(high_mask) + reset + write(SEARCH_ROM) + board.write Dino::Message.encode command: 42, + pin: pin, + aux_message: [high_mask].pack(' 12 || bits < 9) @resolution = bits @@ -26,24 +35,43 @@ def resolution=(bits) @convert_time = 0.75 / (13 - bits) end - def _read + # + # If we're the only device on the bus, save time by skipping ROM match. + # + def identify + bus.reset + if bus.found_devices == 1 + bus.write(SKIP_ROM) + else + bus.write(MATCH_ROM) + bus.write(Helper.address_to_bytes(self.address)) + end + end + + def convert bus.mutex.synchronize do - bus.reset - bus.write(SKIP_ROM, CONVERT_T) + identify + bus.write(CONVERT_T) sleep @convert_time if bus.parasite_power end - sleep @convert_time unless bus.parasite_power - data = nil + end + def read_scratch + data = nil bus.mutex.synchronize do - bus.reset - bus.write(SKIP_ROM, READ_SCRATCH) + identify + bus.write(READ_SCRATCH) data = bus.read(9) end + data + end - # Once we got data, free the bus and run callbacks ourselves. - self.update(data) + def _read + convert + reading = read_scratch + # This runs callbacks in our thread instead of the callback thread... + self.update(reading) end # diff --git a/lib/dino/components/one_wire/helper.rb b/lib/dino/components/one_wire/helper.rb index 5fedcfc0..86efe1ab 100644 --- a/lib/dino/components/one_wire/helper.rb +++ b/lib/dino/components/one_wire/helper.rb @@ -3,15 +3,40 @@ module Components module OneWire class Helper # - # CRC is a single byte. Start with it set to 0. Move through first 8 - # bytes in the order read, but bitwise LSBFIRST. For each bit: + # Convert a 64-bit ROM address in Integer form to an array of bytes. + # + def self.address_to_bytes(address) + [address].pack('> 8 + address.to_s(16) + end + + # + # CRC is the last byte of any message. Start with it set to 0. Move through + # the n-1 data bytes LSBYTE FIRST, but bitwise LSBFIRST. For each bit: # 1) XOR the bit with the current LSB of CRC # 2) XOR the result of #1 with the bits at indices 3 and 4 of CRC. # 3) Bitshift the CRC right by 1 # 4) Write the result of #1 to the now empty MSB of the CRC # After 64 bits, the CRC is valid if it matches the 9th of the read data. # - def self.crc_check(bytes) + def self.crc_check(data) + # + # Sensor data will usually be an array of bytes, but for checking + # things like ROM addresses, we may receive a 64-bit Integer. + # + if data.class == Integer + bytes = address_to_bytes(data) + else + bytes = data + end + crc = 0b00000000 bytes.take(bytes.length - 1).each do |byte| for bit in (0..7) @@ -23,6 +48,80 @@ def self.crc_check(bytes) end crc == bytes.last end + + # + # This separates a search result, checks result for device disconnection, + # matches the family byte of the address to a Component class if one exists, + # and looks for the highest index discrepancy to continue the search. + # + def self.parse_search_result(str) + address, complement = self.split_search_result(str) + + # + # If any address-complement bit-pair is 1-1, either no device is + # present on the bus, or a device was disconnected during the search. + # + if (address & complement) > 0 + raise "OneWire device not connected or disconnected during search" + end + + # + # LS byte of the address holds the family code. + # Lookup against implemented classes. Nil if no match. + # + klass = self.family_lookup(address & 0x00000000000000FF) + + # + # XOR-ing address with complement gives a 0 bit at each discrepancy. + # XOR again with 64 1s to flip to 1s. Ruby truncates leading 0 bits, + # so MSB is the highest discrepancy. Result is -1 for no discrepancy. + # + # Note: The board only sends back discrepancies which we did not + # set to 1 on the last search! This does not track all known + # discrepancies in the address space, but gives us what we need: + # The highest discrepancy that we didn't write a 1 for last time. + # + new_discrepancies = (address ^ complement) ^ 0xFFFFFFFFFFFFFFFF + high_discrepancy = new_discrepancies.bit_length - 1 + + [{class: klass, address: address}, high_discrepancy] + end + + # + # A search result is a comma separated list of 8 byte-pairs. + # Each pair is an address byte, dash (-) separator, and complement byte. + # Pairs are ordered LS byte first. Reverse to MS for easier bitshift. + # + def self.split_search_result(str) + byte_pairs = str.split(",") + address = 0 + complement = 0 + + byte_pairs.reverse.each do |pair| + address_byte, complement_byte = pair.split("-").map(&:to_i) + address = (address << 8) | address_byte + complement = (complement << 8) | complement_byte + end + + if crc_check(address) + return [address, complement] + else + raise "CRC error for device address on OneWire bus" + end + end + + # + # Define FAMILY_CODE in a slave class to get it identified during search. + # + def self.family_lookup(family_code) + OneWire.constants.each do |const| + obj = OneWire.const_get(const) + if (obj.is_a? Class) && (obj.const_defined? "FAMILY_CODE") + return obj if obj::FAMILY_CODE == family_code + end + end + return nil + end end end end diff --git a/spec/lib/components/one_wire/ds18b20_spec.rb b/spec/lib/components/one_wire/ds18b20_spec.rb index 4a0a3099..55dc3cc1 100644 --- a/spec/lib/components/one_wire/ds18b20_spec.rb +++ b/spec/lib/components/one_wire/ds18b20_spec.rb @@ -5,7 +5,7 @@ module Components module OneWire describe DS18B20 do include BoardMock - let(:options) { { board: board, pin: 7 } } + let(:options) { { board: board, pin: 7, address: 0xFFFFFFFFFFFFFFFF}} subject { DS18B20.new(options) } describe '#decode' do diff --git a/src/lib/DinoOneWire.cpp b/src/lib/DinoOneWire.cpp index a1f98836..41406df7 100644 --- a/src/lib/DinoOneWire.cpp +++ b/src/lib/DinoOneWire.cpp @@ -6,6 +6,10 @@ #include "Dino.h" #ifdef DINO_ONE_WIRE + +// CMD = 41 +// Reset the OneWire bus and optionally return a presence value if val > 0. +// void Dino::owReset(){ // bool present; pinMode(pin, OUTPUT); @@ -18,7 +22,45 @@ void Dino::owReset(){ // send presence value here } +// CMD = 42 +// Read a 64-bit address and complement, echoing the address bits unless there's +// a discrepancy, then we check the branch mask in auxMsg to decide what to do. +// void Dino::owSearch(){ + byte addr; + byte comp; + + // Start with the pin that the bus is on and the colon. + stream->print(pin); stream->print(':'); + + // Print each byte read, followed by a comma, or newline for last byte. + for(byte i=0; i<8; i++){ + for(byte j=0; j<8; j++){ + bitWrite(addr, j, owReadBit()); + bitWrite(comp, j, owReadBit()); + + // If there is a discrepancy, first 8 bytes of auxMsg is a branch mask. + // These are bits we must write 1 for since we're searching that branch. + // Also set the address bit to 1, but don't touch the complement. + // This 'corrupts' the check for discrepancies we already new about, + // but we're tracking that remotely anyway. + // + if(bitRead(auxMsg[i], j) == 1){ + owWriteBit(1); + bitWrite(addr, j, 1); + + // If we're don't already know about the discrepancy, behave normally + // so it shows up on the next search. + // + } else { + owWriteBit(bitRead(addr, j)); + } + } + stream->print(addr); + stream->print("-"); + stream->print(comp); + stream->print((i == 7) ? '\n' : ','); + } } // CMD = 43 @@ -29,6 +71,7 @@ void Dino::owSearch(){ // Limited to 127 bytes. Validate on remote end. // void Dino::owWrite(){ + // Check and clear parasite flag masked into array length. bool parasite = bitRead(val, 7); bitClear(val, 7); @@ -40,6 +83,7 @@ void Dino::owWrite(){ } } + // Drive bus high to feed the parasite capacitor after writing if necessary. if (parasite) { pinMode(pin, OUTPUT); digitalWrite(pin, HIGH); From 6f1eff777e2d1905499b90a0de435670bc3d415b Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 5 Feb 2018 12:52:34 -0400 Subject: [PATCH 164/296] Use a known working jruby version for travis. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 171ae6bb..0627979d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,6 @@ before_install: rvm: - 2.4.1 - 2.0.0 - - jruby-19mode + - jruby-9.1.15.0 script: bundle exec rspec spec From 7d12609584e66f6492acb65b69a52bf462bf9fc0 Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 5 Feb 2018 13:03:39 -0400 Subject: [PATCH 165/296] Test 1-Wire search algorithm by simulating the bus in Ruby. --- lib/dino/components/one_wire/bus.rb | 22 ++- lib/dino/components/one_wire/helper.rb | 10 +- spec/lib/components/one_wire/ds18b20_spec.rb | 4 +- .../components/one_wire/enumerator_spec.rb | 138 ++++++++++++++++++ 4 files changed, 160 insertions(+), 14 deletions(-) create mode 100644 spec/lib/components/one_wire/enumerator_spec.rb diff --git a/lib/dino/components/one_wire/bus.rb b/lib/dino/components/one_wire/bus.rb index 88aa6cfa..eac27e41 100644 --- a/lib/dino/components/one_wire/bus.rb +++ b/lib/dino/components/one_wire/bus.rb @@ -61,14 +61,14 @@ def search # # Reset the bus, then send the search command, along with a 64-bit - # mask of bits to write 1 for. + # mask of bits to write 1 to force the search into a specific branch. # - def _search(high_mask) + def _search(branch_mask) reset write(SEARCH_ROM) board.write Dino::Message.encode command: 42, pin: pin, - aux_message: [high_mask].pack(' 127 + if length > 127 + raise ArgumentError, 'too many bytes given (expected at most 127)' + end - # Set flag if last byte is a command requiring parasite power after it. + # Set flag in high bit of length if last command needs parasite power. if parasite_power && [CONVERT_T, COPY_SCRATCH].include?(bytes.last) length = length | 0b10000000 end - bytes = bytes.pack('C*') - board.write Dino::Message.encode(command: 43, pin: pin, value: length, aux_message: bytes) + board.write Dino::Message.encode command: 43, + pin: pin, + value: length, + aux_message: bytes.pack('C*') end end end diff --git a/lib/dino/components/one_wire/helper.rb b/lib/dino/components/one_wire/helper.rb index 86efe1ab..6f2b4c42 100644 --- a/lib/dino/components/one_wire/helper.rb +++ b/lib/dino/components/one_wire/helper.rb @@ -3,17 +3,19 @@ module Components module OneWire class Helper # - # Convert a 64-bit ROM address in Integer form to an array of bytes. + # Convert 64-bit Integer ROM address to array of 8 LSByte first bytes. # def self.address_to_bytes(address) [address].pack('> 8 + address = address & 0x00FFFFFFFFFFFFFF + address = address >> 8 address.to_s(16) end @@ -83,7 +85,7 @@ def self.parse_search_result(str) # new_discrepancies = (address ^ complement) ^ 0xFFFFFFFFFFFFFFFF high_discrepancy = new_discrepancies.bit_length - 1 - + [{class: klass, address: address}, high_discrepancy] end diff --git a/spec/lib/components/one_wire/ds18b20_spec.rb b/spec/lib/components/one_wire/ds18b20_spec.rb index 55dc3cc1..ab697f92 100644 --- a/spec/lib/components/one_wire/ds18b20_spec.rb +++ b/spec/lib/components/one_wire/ds18b20_spec.rb @@ -5,8 +5,8 @@ module Components module OneWire describe DS18B20 do include BoardMock - let(:options) { { board: board, pin: 7, address: 0xFFFFFFFFFFFFFFFF}} - subject { DS18B20.new(options) } + let(:bus) { double.as_null_object } + subject { DS18B20.new(board: bus, address: 0xFFFFFFFFFFFFFFFF) } describe '#decode' do it 'should decode values matching the datasheet and convert C to F' do diff --git a/spec/lib/components/one_wire/enumerator_spec.rb b/spec/lib/components/one_wire/enumerator_spec.rb new file mode 100644 index 00000000..a4111d0c --- /dev/null +++ b/spec/lib/components/one_wire/enumerator_spec.rb @@ -0,0 +1,138 @@ +require 'spec_helper' + +module Dino + module Components + module OneWire + # + # State machine simulating a bus during address search. Initialize with n for + # n devices with random (CRC-invalid) addresses. Call #reset before each search. + # + class BusSimulator + def initialize(device_count) + @devices = [] + device_count.times do + @devices << { rom: rand(2**64), in_search: true } + end + @devices = @devices.uniq + @index = -1 + end + + def addresses + @addresses ||= @devices.map { |d| d[:rom] } + end + + def reset + @devices.each { |d| d[:in_search] = true } + @index = -1 + end + + def read_address_bit + bit = 1 + @index = @index + 1 + @devices.each do |d| + if d[:in_search] + bit = bit & d[:rom][@index] + end + end + bit + end + + def read_complement_bit + bit = 1 + @devices.each do |d| + if d[:in_search] + comp = d[:rom][@index] ^ 1 + bit = bit & comp + end + end + bit + end + + def write_bit(bit) + @devices.each do |d| + d[:in_search] = false unless d[:rom][@index] == bit + end + end + end + + # + # Class wrapper for Ruby version of the C++ serach function running on the board. + # Hopefully that doesn't change... + # + class BoardSimulator + def initialize(simulator) + @simulator = simulator + end + + def reset + @simulator.reset + end + + def search(branch_mask) + output_string = "" + (0..7).each do |i| + addr = 0 + comp = 0 + (0..7).each do |j| + # Read bit and complement from simulator. + addr_bit = @simulator.read_address_bit + comp_bit = @simulator.read_complement_bit + + # Set them in variable. + addr = addr | (addr_bit * (2 ** j)) + comp = comp | (comp_bit * (2 ** j)) + + # Override address bit to a 1 in variable if 1 in mask. + # Write whatever addr_bit ends up being back to the simulator. + if branch_mask[(i*8) + j] == 1 + @simulator.write_bit(1) + addr = addr | (1 * (2 ** j)) + else + @simulator.write_bit(addr_bit) + end + end + + output_string << addr.to_s << "-" << comp.to_s + output_string << "," if (i != 7 ) # No \n. TxRx strips it IRL. + end + output_string + end + end + + # + # Monkeypatch Bus to search the board sim instead of a real board. + # + class Bus + def initialize(options={}) + @board_sim = BoardSimulator.new(options[:simulator]) + super(options) + end + + def _search(branch_mask) + @board_sim.reset + result = @board_sim.search(branch_mask) + # Manually call #update to simulate callback thread. + self.update(result) + end + end + + describe Bus do + include BoardMock + describe '#search' do + it 'should find all the addresses' do + # Ignore CRC since the simulator uses random addresses. + allow(OneWire::Helper).to receive(:crc_check).and_return true + allow_any_instance_of(Bus).to receive(:read_power_supply).and_return false + + # This gets slow really fast... 256 seems fine. + sim = BusSimulator.new(256) + bus = Bus.new(board: board, pin: 7, simulator: sim) + + found_addresses = bus.found_devices.map { |d| d[:address] } + expect(found_addresses.sort).to eq(sim.addresses.sort) + end + end + end + end + end +end From ec113b46f09372a2132cd8773249e1f322a766d1 Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 13 Feb 2018 15:55:41 -0400 Subject: [PATCH 166/296] Extract command API from components into a module. Now Components only call methods on their Board rather than composing, validating, and writing messages themselves. Also finished 1-Wire implementation but incomplete test coverage. --- examples/one_wire/ds18b20.rb | 24 ++-- examples/register/shift_in.rb | 4 +- lib/dino.rb | 1 + lib/dino/api.rb | 13 +++ lib/dino/api/core.rb | 76 ++++++++++++ lib/dino/api/dht.rb | 12 ++ lib/dino/api/helper.rb | 51 +++++++++ lib/dino/api/infrared.rb | 19 +++ lib/dino/api/one_wire.rb | 38 ++++++ lib/dino/api/servo.rb | 19 +++ lib/dino/api/shift_io.rb | 45 ++++++++ lib/dino/api/spi.rb | 39 +++++++ lib/dino/api/tone.rb | 18 +++ lib/dino/board.rb | 85 ++------------ lib/dino/components/dht.rb | 10 +- lib/dino/components/ir_emitter.rb | 40 ++----- lib/dino/components/mixins/board_proxy.rb | 2 +- lib/dino/components/mixins/bus.rb | 2 +- lib/dino/components/mixins/callbacks.rb | 2 +- lib/dino/components/one_wire.rb | 2 + lib/dino/components/one_wire/bus.rb | 103 ++++------------- .../components/one_wire/bus_enumerator.rb | 106 +++++++++++++++++ lib/dino/components/one_wire/ds18b20.rb | 108 ++++++++---------- lib/dino/components/one_wire/helper.rb | 95 +-------------- lib/dino/components/one_wire/slave.rb | 76 ++++++++++++ lib/dino/components/piezo.rb | 4 +- lib/dino/components/register/shift_in.rb | 50 ++++---- lib/dino/components/register/shift_out.rb | 14 +-- lib/dino/components/register/spi_in.rb | 33 ++---- lib/dino/components/register/spi_out.rb | 25 ++-- lib/dino/components/rotary_encoder.rb | 9 +- lib/dino/components/servo.rb | 4 +- lib/dino/components/setup/base.rb | 13 +-- lib/dino/components/setup/input.rb | 1 + lib/dino/components/setup/multi_pin.rb | 21 ++-- lib/dino/components/setup/output.rb | 1 + lib/dino/components/setup/single_pin.rb | 5 +- spec/lib/components/mixins/callbacks_spec.rb | 1 + spec/lib/components/mixins/listener_spec.rb | 1 + spec/lib/components/mixins/poller_spec.rb | 1 + spec/lib/components/mixins/reader_spec.rb | 1 + spec/lib/components/mixins/threaded_spec.rb | 6 +- spec/lib/components/one_wire/ds18b20_spec.rb | 16 ++- .../components/one_wire/enumerator_spec.rb | 12 +- spec/lib/components/register/shift_in_spec.rb | 12 +- spec/lib/components/servo_spec.rb | 2 +- src/lib/DinoCoreIO.cpp | 4 +- src/lib/DinoOneWire.cpp | 50 ++++---- 48 files changed, 757 insertions(+), 519 deletions(-) create mode 100644 lib/dino/api.rb create mode 100644 lib/dino/api/core.rb create mode 100644 lib/dino/api/dht.rb create mode 100644 lib/dino/api/helper.rb create mode 100644 lib/dino/api/infrared.rb create mode 100644 lib/dino/api/one_wire.rb create mode 100644 lib/dino/api/servo.rb create mode 100644 lib/dino/api/shift_io.rb create mode 100644 lib/dino/api/spi.rb create mode 100644 lib/dino/api/tone.rb create mode 100644 lib/dino/components/one_wire/bus_enumerator.rb create mode 100644 lib/dino/components/one_wire/slave.rb diff --git a/examples/one_wire/ds18b20.rb b/examples/one_wire/ds18b20.rb index a2948264..5745aecb 100644 --- a/examples/one_wire/ds18b20.rb +++ b/examples/one_wire/ds18b20.rb @@ -7,34 +7,44 @@ board = Dino::Board.new(Dino::TxRx::Serial.new) bus = Dino::Components::OneWire::Bus.new(pin:16, board: board) -# The bus does parasite power detection on startup. +# The bus detects parasite power automatically when initialized. # It can tell that parasite power is in use, but not by WHICH devices. if bus.parasite_power puts "Parasite power detected..."; puts end -# The bus automatically searches when initialized. It finds the address of -# every device, identifies the device type and matching Ruby class, storing here. -puts "Found #{bus.found_devices.count} devices on the bus:" +# Call #device_present to reset the bus and return presence pulse as a boolean. +if bus.device_present + puts "Devices present on bus..."; puts +else + puts "No devices present on bus... Quitting..." + return +end + +# Calling #search finds connected devices and stores them in #found_devices. +# Each hash contains a device's ROM address and matching Ruby class if one exists. +bus.search +count = bus.found_devices.count +puts "Found #{count} device#{'s' if count > 1} on the bus:" puts bus.found_devices.inspect; puts # We can use the search results to setup instances of the device classes. ds18b20s = [] bus.found_devices.each do |d| if d[:class] == Dino::Components::OneWire::DS18B20 - ds18b20s << Dino::Components::OneWire::DS18B20.new(board: bus, address: d[:address]) + ds18b20s << d[:class].new(bus: bus, address: d[:address]) end end # Format a reading for printing on a line. def print_reading(reading, sensor, i) print "#{Time.now.strftime '%Y-%m-%d %H:%M:%S'} - " - print "index: #{i}, serial# :#{sensor.serial} " + print "Serial(HEX): #{sensor.serial_number} | Res: #{sensor.resolution} bits | " if reading[:crc_error] puts "CRC check failed for this reading!" else - print "#{reading[:celsius]} \xC2\xB0C / #{reading[:farenheit]} \xC2\xB0F / " + print "#{reading[:celsius]} \xC2\xB0C | #{reading[:farenheit]} \xC2\xB0F | " puts "Raw: #{reading[:raw].inspect}" end end diff --git a/examples/register/shift_in.rb b/examples/register/shift_in.rb index 64ce1521..03576c03 100644 --- a/examples/register/shift_in.rb +++ b/examples/register/shift_in.rb @@ -7,7 +7,7 @@ # The Button object is created by passing the register instead of the board, and # the register's parallel output pin that the button is connected to. # -# Note: preclock_high must be set to true if using TI CD4021B register or similar. +# Note: rising_clock must be set to true if using TI CD4021B register or similar. # This should apply to any register which outputs on a rising clock edge. # Change as needed. # @@ -18,7 +18,7 @@ shift_register = Dino::Components::Register::ShiftIn.new board: board, pins: {latch: 10, data: 12, clock: 13}, - preclock_high: true, + rising_clock: true, bytes: 1 button = Dino::Components::Button.new(pin: 0, board: shift_register) diff --git a/lib/dino.rb b/lib/dino.rb index 9a6ed2ec..a77f369f 100644 --- a/lib/dino.rb +++ b/lib/dino.rb @@ -2,6 +2,7 @@ require 'dino/version' require 'dino/message' +require 'dino/api' require 'dino/tx_rx' require 'dino/board' require 'dino/components' diff --git a/lib/dino/api.rb b/lib/dino/api.rb new file mode 100644 index 00000000..61fac8ff --- /dev/null +++ b/lib/dino/api.rb @@ -0,0 +1,13 @@ +module Dino + module API + require 'dino/api/helper' + require 'dino/api/core' + require 'dino/api/dht' + require 'dino/api/infrared' + require 'dino/api/one_wire' + require 'dino/api/servo' + require 'dino/api/shift_io' + require 'dino/api/spi' + require 'dino/api/tone' + end +end diff --git a/lib/dino/api/core.rb b/lib/dino/api/core.rb new file mode 100644 index 00000000..8ef13a32 --- /dev/null +++ b/lib/dino/api/core.rb @@ -0,0 +1,76 @@ +module Dino + module API + module Core + include Helper + + DIVIDERS = [1, 2, 4, 8, 16, 32, 64, 128] + + # CMD = 0 + def set_pin_mode(pin, mode) + pin, value = convert_pin(pin), mode == :out ? 0 : 1 + write Dino::Message.encode command: 0, + pin: convert_pin(pin), + value: value + end + + # CMD = 1 + def digital_write(pin,value) + write Message.encode command: 1, pin: convert_pin(pin), value: value + end + + # CMD = 2 + def digital_read(pin) + write Message.encode command: 2, pin: convert_pin(pin) + end + + # CMD = 3 + def analog_write(pin,value) + write Message.encode command: 3, pin: convert_pin(pin), value: value + end + + # CMD = 4 + def analog_read(pin) + write Message.encode command: 4, pin: convert_pin(pin) + end + + def set_pullup(pin, pullup) + pin = convert_pin(pin) + pullup ? digital_write(pin, @high) : digital_write(pin, @low) + end + + # CMD = 7 + def set_listener(pin, state=:off, options={}) + mode = options[:mode] || :digital + divider = options[:divider] || 8 + + unless [:digital, :analog].include? mode + raise "Mode must be either digital or analog" + end + unless DIVIDERS.include? divider + raise "Listener divider must be in #{DIVIDERS.inspect}" + end + + # Create a bit mask for the settings we want to use. Gets sent in value. + mask = 0 + mask |= 0b10000000 if (state == :on) + mask |= 0b01000000 if (mode == :analog) + mask |= Math.log2(divider).to_i + + write Message.encode(command: 7, pin: convert_pin(pin), value: mask) + end + + # Convenience methods by wrapping set_listener with old defaults. + def digital_listen(pin, divider=4) + set_listener(pin, :on, mode: :digital, divider: divider) + end + + def analog_listen(pin, divider=16) + set_listener(pin, :on, mode: :analog, divider: divider) + end + + def stop_listener(pin) + set_listener(pin, :off) + end + end + end +end diff --git a/lib/dino/api/dht.rb b/lib/dino/api/dht.rb new file mode 100644 index 00000000..413db961 --- /dev/null +++ b/lib/dino/api/dht.rb @@ -0,0 +1,12 @@ +module Dino + module API + module DHT + include Helper + + # CMD = 13 + def dht_read(pin) + write Message.encode command: 13, pin: convert_pin(pin) + end + end + end +end diff --git a/lib/dino/api/helper.rb b/lib/dino/api/helper.rb new file mode 100644 index 00000000..982d66c7 --- /dev/null +++ b/lib/dino/api/helper.rb @@ -0,0 +1,51 @@ +module Dino + module API + module Helper + # + # Microcontrollers have different aux message limits based on available RAM. + # Board should set @aux_limit from handshake. Default to safe minimum. + # + def aux_limit + @aux_limit || 527 # Should default to 39, but this isn't in handshake yet. + end + + def write(message) + raise NotImplementedError + .new("#{self.class.name}#write not defined in Board subclass") + end + + def pack(type, data, options={}) + # Always pack as little endian. + template = case type + when :uint64 then 'Q<*' + when :uint32 then 'L<*' + when :uint16 then 'S<*' + when :uint8 then 'C*' + else raise ArgumentError "unsupported pack format '#{type}'" + end + + # Can pass a single integer to get packed if we always [] then flatten. + str = [data].flatten.pack(template) + + # Pad right with null bytes if asked. + if options[:pad] && options[:pad] > str.length + (options[:pad] - str.length).times do + str = str + "\x00" + end + end + + # Minimum 1 whether or not :min is given as an option. + if str.length < (options[:min] || 1) + raise ArgumentError, "too few bytes given (expected at least #{options[:min]})" + end + + # Max should probably always be set to avoid overruning aux message RAM. + if options[:max] && str.length > options[:max] + raise ArgumentError, "too many bytes given (expected at most #{options[:max]})" + end + + str + end + end + end +end diff --git a/lib/dino/api/infrared.rb b/lib/dino/api/infrared.rb new file mode 100644 index 00000000..bdaa0ff5 --- /dev/null +++ b/lib/dino/api/infrared.rb @@ -0,0 +1,19 @@ +module Dino + module API + module Infrared + include Helper + + def infrared_send(pin, frequency, pulses) + # Need to start using length - 1, but doesn't work on board yet. + # 0 = 1 pulse, 255 = 256 pulses. + length = pack :uint8, pulses.length, max: 1 + bytes = pack :uint16, pulses, min: 1, max: 512 + + write Message.encode command: 16, + pin: convert_pin(pin), + value: frequency, + aux_message: length + bytes + end + end + end +end diff --git a/lib/dino/api/one_wire.rb b/lib/dino/api/one_wire.rb new file mode 100644 index 00000000..4b253534 --- /dev/null +++ b/lib/dino/api/one_wire.rb @@ -0,0 +1,38 @@ +module Dino + module API + module OneWire + include Helper + + def one_wire_reset(pin, value=0) + write Message.encode command: 41, + pin: convert_pin(pin), + value: value + end + + def one_wire_search(pin, branch_mask) + write Message.encode command: 42, + pin: convert_pin(pin), + aux_message: pack(:uint64, branch_mask, max: 8) + end + + def one_wire_write(pin, parasite_power, *data) + bytes = pack :uint8, data, min: 1, max: 127 # Should be 128 with 0 = 1. + + # Set high bit of length if the bus must drive high after write. + length = bytes.length + length = length | 0b10000000 if parasite_power + + write Message.encode command: 43, + pin: convert_pin(pin), + value: length, + aux_message: bytes + end + + def one_wire_read(pin, num_bytes) + write Message.encode command: 44, + pin: convert_pin(pin), + value: num_bytes + end + end + end +end diff --git a/lib/dino/api/servo.rb b/lib/dino/api/servo.rb new file mode 100644 index 00000000..baae5b0e --- /dev/null +++ b/lib/dino/api/servo.rb @@ -0,0 +1,19 @@ +module Dino + module API + module Servo + include Helper + + def servo_toggle(pin, value=:off) + write Message.encode command: 8, + pin: convert_pin(pin), + value: (value == :off) ? 0 : 1 + end + + def servo_write(pin, value=0) + write Message.encode command: 9, + pin: convert_pin(pin), + value: value + end + end + end +end diff --git a/lib/dino/api/shift_io.rb b/lib/dino/api/shift_io.rb new file mode 100644 index 00000000..f1f2b3de --- /dev/null +++ b/lib/dino/api/shift_io.rb @@ -0,0 +1,45 @@ +module Dino + module API + module ShiftIO + include Helper + + def shift_settings(data, clock, preclock_high=false) + preclock_high = preclock_high ? 1 : 0 + pack :uint8, [convert_pin(data), convert_pin(clock), preclock_high] + end + + def shift_write(latch, data, clock, byte_array, options={}) + settings = shift_settings(data, clock) + limit = aux_limit - settings.length + bytes = pack :uint8, + byte_array, + max: (limit < 256) ? limit : 256 + + write Message.encode command: 21, + pin: convert_pin(latch), + value: bytes.length, # Should be length-1 so 0 = 1 byte, 255 = 256 bytes + aux_message: settings + bytes + end + + def shift_read(latch, data, clock, num_bytes, options={}) + settings = shift_settings(data, clock, options[:preclock_high]) + write Message.encode command: 22, + pin: convert_pin(latch), + value: num_bytes, # Should be num-1 so 0 = 1 byte, 255 = 256 bytes + aux_message: settings + end + + def shift_listen(latch, data, clock, num_bytes, options={}) + settings = shift_settings(data, clock, options[:preclock_high]) + write Message.encode command: 23, + pin: convert_pin(latch), + value: num_bytes, # Should be num-1 so 0 = 1 byte, 255 = 256 bytes + aux_message: settings + end + + def shift_stop(latch) + write Message.encode command: 24, pin: convert_pin(latch) + end + end + end +end diff --git a/lib/dino/api/spi.rb b/lib/dino/api/spi.rb new file mode 100644 index 00000000..7aadeecb --- /dev/null +++ b/lib/dino/api/spi.rb @@ -0,0 +1,39 @@ +module Dino + module API + module SPI + include Helper + + def spi_settings(mode, frequency) + pack(:uint8, [0, mode]) + pack(:uint32, [frequency]) + end + + # Listener can store up to 8 bytes to get written each time, read up to 256. + def spi_write(pin, mode, frequency, bytes) + settings = spi_settings(mode, frequency) + bytes = pack(:uint8, bytes) + write Message.encode command: 26, + pin: pin, + value: bytes.length, + aux_message: settings + bytes + end + + def spi_read(pin, mode, frequency, num_bytes) + write Message.encode command: 27, + pin: pin, + value: num_bytes, + aux_message: spi_settings(mode, frequency) + end + + def spi_listen(pin, mode, frequency, num_bytes) + write Message.encode command: 28, + pin: pin, + value: num_bytes, + aux_message: spi_settings(mode, frequency) + end + + def spi_stop(pin) + write Message.encode command: 29, pin: pin + end + end + end +end diff --git a/lib/dino/api/tone.rb b/lib/dino/api/tone.rb new file mode 100644 index 00000000..4b59bd42 --- /dev/null +++ b/lib/dino/api/tone.rb @@ -0,0 +1,18 @@ +module Dino + module API + module Tone + include Helper + + def tone(pin, value, duration) + write Dino::Message.encode command: 17, + pin: convert_pin(pin), + value: value, + aux_message: duration + end + + def no_tone(pin) + write Message.encode command: 18, pin: convert_pin(pin) + end + end + end +end diff --git a/lib/dino/board.rb b/lib/dino/board.rb index 1bb583cd..d07deaec 100644 --- a/lib/dino/board.rb +++ b/lib/dino/board.rb @@ -1,7 +1,14 @@ module Dino class Board + include API::Core + include API::Servo + include API::ShiftIO + include API::SPI + include API::Infrared + include API::OneWire + include API::Tone + attr_reader :high, :low, :analog_high, :components, :analog_zero, :dac_zero - DIVIDERS = [1, 2, 4, 8, 16, 32, 64, 128] def initialize(io, options={}) @io, @components = io, [] @@ -37,82 +44,6 @@ def remove_component(component) @components.delete(component) end - def set_pin_mode(pin, mode) - pin, value = convert_pin(pin), mode == :out ? 0 : 1 - write Dino::Message.encode(command: 0, pin: pin, value: value) - end - - def set_pullup(pin, pullup) - pin = convert_pin(pin) - pullup ? digital_write(pin, @high) : digital_write(pin, @low) - end - - PIN_COMMANDS = { - # set_pin_mode: '0' - digital_write: '1', - digital_read: '2', - analog_write: '3', - analog_read: '4', - # unused: '5', - # unused: '6', - set_listener: '7', - servo_toggle: '8', - servo_write: '9', - # LCD '10' - # Unused '11' - # SoftSerial '12' - dht_read: '13', - # HCSR04 '14' - ds18b20_read: '15', - # IR send: '16' - tone: '17', - no_tone: '18', - } - - PIN_COMMANDS.each_key do |command| - define_method(command) do |pin, value=nil| - write Dino::Message.encode(command: PIN_COMMANDS[command], pin: convert_pin(pin), value: value) - end - end - - # Redefinition tone to accept duration. - def tone(pin, value, duration) - write Dino::Message.encode(command: PIN_COMMANDS[:tone], pin: convert_pin(pin), value: value, aux_message: duration) - end - - def set_listener(pin, state=:off, options={}) - mode = options[:mode] || :digital - divider = options[:divider] || 8 - - unless [:digital, :analog].include? mode - raise "Mode must be either digital or analog" - end - unless DIVIDERS.include? divider - raise "Listener divider must be in #{DIVIDERS.inspect}" - end - - # Create a bit mask for the settings we want to use. Gets sent in value. - mask = 0 - mask |= 0b10000000 if (state == :on) - mask |= 0b01000000 if (mode == :analog) - mask |= Math.log2(divider).to_i - - write Dino::Message.encode(command: PIN_COMMANDS[:set_listener], pin: convert_pin(pin), value: mask) - end - - # Implement the simpler methods by wrapping set_listener with old defaults. - def digital_listen(pin, divider=4) - set_listener(pin, :on, mode: :digital, divider: divider) - end - - def analog_listen(pin, divider=16) - set_listener(pin, :on, mode: :analog, divider: divider) - end - - def stop_listener(pin) - set_listener(pin, :off) - end - DIGITAL_REGEX = /\A\d+\z/i ANALOG_REGEX = /\A(a)\d+\z/i DAC_REGEX = /\A(dac)\d+\z/i diff --git a/lib/dino/components/dht.rb b/lib/dino/components/dht.rb index 72a22d2d..c804d12b 100644 --- a/lib/dino/components/dht.rb +++ b/lib/dino/components/dht.rb @@ -6,7 +6,7 @@ class DHT include Mixins::Poller def after_initialize(options={}) - super(options) if defined?(super) + super(options) @state = {temperature: nil, humidity: nil} end @@ -14,12 +14,10 @@ def _read board.dht_read(self.pin) end - # Process raw data from the board before running Callbacks#update. - # super will write to @state after running callbacks. - def update(data) + # Process raw data from the board before running #update. + def pre_callback_filter(data) t, h = data.split(",") - reading = { temperature: t.to_f, humidity: h.to_f } - super(reading) + { temperature: t.to_f, humidity: h.to_f } end end end diff --git a/lib/dino/components/ir_emitter.rb b/lib/dino/components/ir_emitter.rb index 930b8823..39e57aa3 100644 --- a/lib/dino/components/ir_emitter.rb +++ b/lib/dino/components/ir_emitter.rb @@ -2,40 +2,20 @@ module Dino module Components class IREmitter < Basic::DigitalOutput def send(pulses=[], options={}) - raise Exception.new('infrared signals are limited to a total of 255 pulses (marks + ticks)') if pulses.length > 255 - pulses.each do |p| - raise Exception.new('invalid infrared signal, please ensure all microsecond durations are integers') unless p.is_a? Integer - raise Exception.new('pulse lengths are limited to 65536 microseconds') if p > 65536 + if pulses.length > 256 || pulses.length < 1 + raise ArgumentError, 'wrong number of IR pulses (expected 1 to 256)' end - # Default to 38kHz - frequency = options[:frequency] || 38 + pulses.each_with_index do |pulse, index| + raise ArgumentError, 'non Numeric data in IR signal' unless pulse.is_a? Numeric + pulses[index] = pulse.round unless pulse.is_a? Integer + raise ArgumentError 'pulse too long (max 65536 microsec)' if pulse > 65536 + end - message = Dino::Message.encode( - command: 16, - # Setting the IR emitter pin is currently unsupported. - # Although the value gets passed through, it always uses the default pin - # for your specfic board/chip, as defined by the library (in bold) at: - # https://github.com/z3t0/Arduino-IRremote#hardware-specifications - pin: pin, - value: frequency, - aux_message: pack(pulses) - ) - board.write(message) - end + # Default to 38kHz. + frequency = options[:frequency] || 38 - # Pack pulse lengths (in microseconds) into a string (byte array really) such that: - # 1) Each pulse length is converted to a little-endian unsigned 16-bit integer. - # 2) Each pulse occupies 2 consecutive bytes of the byte array. - # 3) The first pulse is at index 1 of the array, and subsequent pulses - # start on consecutive odd-numbered indices. - # 4) The 0th byte of the array contains the total number of pulses (NOT bytes). - # - # This keeps compatbility with the aux format normally used for sending data as - # ASCII, but sends higher density binary data whch the IR library needs. - # - def pack(pulses=[]) - "#{[pulses.count].pack('C')}#{pulses.pack('v*')}" + board.infrared_send(pin, frequency, pulses) end end end diff --git a/lib/dino/components/mixins/board_proxy.rb b/lib/dino/components/mixins/board_proxy.rb index a256b3be..5b79590d 100644 --- a/lib/dino/components/mixins/board_proxy.rb +++ b/lib/dino/components/mixins/board_proxy.rb @@ -5,7 +5,7 @@ module BoardProxy include Bus def after_initialize(options={}) - super(options) if defined?(super) + super(options) @high = 1 @low = 0 end diff --git a/lib/dino/components/mixins/bus.rb b/lib/dino/components/mixins/bus.rb index b1c80974..d44c16df 100644 --- a/lib/dino/components/mixins/bus.rb +++ b/lib/dino/components/mixins/bus.rb @@ -8,7 +8,7 @@ module Bus # See OneWire::Bus class for an example. # def after_initialize(options={}) - super(options) if defined?(super) + super(options) @components = [] end diff --git a/lib/dino/components/mixins/callbacks.rb b/lib/dino/components/mixins/callbacks.rb index 1f9757e3..1893bb93 100644 --- a/lib/dino/components/mixins/callbacks.rb +++ b/lib/dino/components/mixins/callbacks.rb @@ -3,7 +3,7 @@ module Components module Mixins module Callbacks def after_initialize(options={}) - super(options) if defined?(super) + super(options) @callbacks = {} @callback_mutex = Mutex.new end diff --git a/lib/dino/components/one_wire.rb b/lib/dino/components/one_wire.rb index 3929599c..823ef49a 100644 --- a/lib/dino/components/one_wire.rb +++ b/lib/dino/components/one_wire.rb @@ -4,6 +4,8 @@ module OneWire require 'dino/components/one_wire/constants' require 'dino/components/one_wire/helper' require 'dino/components/one_wire/bus' + require 'dino/components/one_wire/bus_enumerator' + require 'dino/components/one_wire/slave' require 'dino/components/one_wire/ds18b20' end end diff --git a/lib/dino/components/one_wire/bus.rb b/lib/dino/components/one_wire/bus.rb index eac27e41..89f956e6 100644 --- a/lib/dino/components/one_wire/bus.rb +++ b/lib/dino/components/one_wire/bus.rb @@ -9,112 +9,49 @@ class Bus attr_reader :found_devices, :parasite_power, :mutex def after_initialize(options = {}) - super(options) if defined?(super) + super(options) @mutex = Mutex.new @found_devices = [] read_power_supply - search - end - - def search - branch_mask = 0 - high_discrepancy = 0 - - loop do - self.add_callback(:read) do |result| - # - # We get a device hash (class and ROM address) as a parsed result, - # plus the bit index of the highest discrepancy from the last run. - # - device, high_discrepancy = Helper.parse_search_result(result) - @found_devices << device - end - _search(branch_mask) - block_until_read - - # - # Since we're zero-indexed unlike the datasheet, this is when the - # search ends. There's no discrepancy left even at the lowest bit. - # - break if high_discrepancy == - 1 - - # - # If high_discrepancy was not a forced 1 last time, do it next time. - # This way we go as deep as possible into each new branch first. - # - if branch_mask[high_discrepancy] == 0 - branch_mask = branch_mask | (2 ** high_discrepancy) - end - - # - # Clear bits above high_discrepancy so we don't repeat branches. - # When high_discrepancy < MSB of branch_mask, this moves us - # one node closer to the root and finishing the search. - # - unset_mask = 0xFFFFFFFFFFFFFFFF >> (63 - high_discrepancy) - branch_mask = branch_mask & unset_mask - end - - # Just to be safe... - @found_devices = @found_devices.uniq - end - - # - # Reset the bus, then send the search command, along with a 64-bit - # mask of bits to write 1 to force the search into a specific branch. - # - def _search(branch_mask) - reset - write(SEARCH_ROM) - board.write Dino::Message.encode command: 42, - pin: pin, - aux_message: [branch_mask].pack(' 127 - raise ArgumentError, 'too many bytes given (expected at most 127)' - end + board.one_wire_reset(pin, 1) + block_until_read + present + end - # Set flag in high bit of length if last command needs parasite power. - if parasite_power && [CONVERT_T, COPY_SCRATCH].include?(bytes.last) - length = length | 0b10000000 - end + def _read(num_bytes) + board.one_wire_read(pin, num_bytes) + end - board.write Dino::Message.encode command: 43, - pin: pin, - value: length, - aux_message: bytes.pack('C*') + def write(*bytes) + pp = parasite_power && [CONVERT_T, COPY_SCRATCH].include?(bytes.last) + board.one_wire_write(pin, pp, bytes) end end end diff --git a/lib/dino/components/one_wire/bus_enumerator.rb b/lib/dino/components/one_wire/bus_enumerator.rb new file mode 100644 index 00000000..fba17e5e --- /dev/null +++ b/lib/dino/components/one_wire/bus_enumerator.rb @@ -0,0 +1,106 @@ +module Dino + module Components + module OneWire + class Bus + # 1 bits in mask set discrepancies to 1. 0 leaves bit as read from bus. + def _search(branch_mask) + reset + write(SEARCH_ROM) + board.one_wire_search(pin, branch_mask) + end + + def search + @found_devices = [] + branch_mask = 0 + high_discrepancy = 0 + + loop do + self.add_callback(:read) do |result| + device, high_discrepancy = parse_search_result(result) + @found_devices << device + end + + _search(branch_mask) + block_until_read + + # No untested discrepancies left. End the search. + break if high_discrepancy == - 1 + + # If high_discrepancy was not forced 1 last iteration, do it next. + # i.e. Go as deep as possible into each branch found then back out. + # + if branch_mask[high_discrepancy] == 0 + branch_mask = branch_mask | (2 ** high_discrepancy) + end + + # Clear bits above high_discrepancy so we don't repeat branches. + # When high_discrepancy < MSB of branch_mask, this moves us + # one node out, closer to the root, and finishing the search. + # + unset_mask = 0xFFFFFFFFFFFFFFFF >> (63 - high_discrepancy) + branch_mask = branch_mask & unset_mask + end + end + + def parse_search_result(result) + address, complement = split_search_result(result) + + raise "CRC error during OneWire search" unless Helper.crc_check(address) + + if (address & complement) > 0 + raise "OneWire device not connected or disconnected during search" + end + + # + # XOR address with complement to give a 0 at each discrepancy. + # XOR again with 64 1s to flip, so each discrepancy is a 1 instead. + # + new_discrepancies = (address ^ complement) ^ 0xFFFFFFFFFFFFFFFF + + # + # Note: Discrepancies forced to 1 in an iteration will not show up as + # discrepancies when parsing the result from that iteration. The board + # will have read 0-0 from the bus, but we told it to override the + # address bit to a 1, so we get back (1-0), as if no discrepancy. + # + # This is OK. We only want the highest discrepancy we did not set to 1. + # + high_discrepancy = new_discrepancies.bit_length - 1 + + # LSByte of address is product family. Check for existing class. + klass = family_lookup(address & 0x00000000000000FF) + + [{class: klass, address: address}, high_discrepancy] + end + + # Search result is a comma separated list of 8 byte-pairs. Each pair is + # formatted "addressByte-compByte". Pairs are ordered LS byte first. + # + def split_search_result(str) + byte_pairs = str.split(",") + address = 0 + complement = 0 + + byte_pairs.reverse.each do |pair| + address_byte, complement_byte = pair.split("-").map(&:to_i) + address = (address << 8) | address_byte + complement = (complement << 8) | complement_byte + end + + [address, complement] + end + + # Set FAMILY_CODE in slave class to get it identified during search. + def family_lookup(family_code) + OneWire.constants.each do |const| + obj = OneWire.const_get(const) + if (obj.is_a? Class) && (obj.const_defined? "FAMILY_CODE") + return obj if obj::FAMILY_CODE == family_code + end + end + return nil + end + end + end + end +end diff --git a/lib/dino/components/one_wire/ds18b20.rb b/lib/dino/components/one_wire/ds18b20.rb index 10009819..8c285d73 100644 --- a/lib/dino/components/one_wire/ds18b20.rb +++ b/lib/dino/components/one_wire/ds18b20.rb @@ -1,112 +1,94 @@ module Dino module Components module OneWire - class DS18B20 - include Setup::Base + class DS18B20 < Slave include Mixins::Poller - FAMILY_CODE = 0x28 - attr_reader :resolution, :address - alias :bus :board - - def after_initialize(options={}) - super(options) if defined?(super) - # Should set only if given as parameter. - # If not, read from sensor and leave as is. - self.resolution = 12 - - raise "missing ROM address for 1-Wire device, try searching first" unless options[:address] - @address = options[:address] - end - + # Scratch read specifically for the resolution if not set yet. def resolution - # Read scratchpad and resolution from sensor here. - end - - def serial - Helper.address_to_serial(self.address) + @resolution ||= decode_resolution read_scratch(9) end def resolution=(bits) - raise 'Invalid resolution for DS18B20 sensor' if (bits > 12 || bits < 9) + raise ArgumentError 'Invalid DS18B20 resolution' if (bits > 12 || bits < 9) + @resolution = bits - # Send commands to set resolution here. - @convert_time = 0.75 / (13 - bits) + scratch = read_scratch(9).split(",").map(&:to_i) + + unless decode_resolution(scratch) == @resolution + settings = scratch[2..4] + offset = @resolution - 9 + settings[2] = 0b00011111 | (offset << 5) + write_scratch(settings) + copy_scratch + end + + set_convert_time end - # - # If we're the only device on the bus, save time by skipping ROM match. - # - def identify - bus.reset - if bus.found_devices == 1 - bus.write(SKIP_ROM) - else - bus.write(MATCH_ROM) - bus.write(Helper.address_to_bytes(self.address)) - end + def set_convert_time + @convert_time = 0.75 / (2 ** (12 - @resolution)) end def convert - bus.mutex.synchronize do - identify + @convert_time ||= 0.75 + atomically do + match bus.write(CONVERT_T) sleep @convert_time if bus.parasite_power end sleep @convert_time unless bus.parasite_power end - def read_scratch - data = nil - bus.mutex.synchronize do - identify - bus.write(READ_SCRATCH) - data = bus.read(9) - end - data - end - def _read convert - reading = read_scratch # This runs callbacks in our thread instead of the callback thread... - self.update(reading) + self.update(read_scratch(9)) end - # - # Data comes in as 9 comma delimited bytes in ASCII numbers. - # First 2 bytes are a coded little-endian number containing degrees C. - # Check CRC first. If good, decode and pass on degrees C, F and raw data. - # def pre_callback_filter(data) + # Data is 9 comma delimited numbers in ASCII, representing bytes. bytes = data.split(",").map{|b| b.to_i} return {crc_error: true} unless Helper.crc_check(bytes) - decode(bytes[1], bytes[0]).merge(raw: bytes) + # Update resolution and conversion time each read. + @resolution = decode_resolution(bytes) + set_convert_time + + decode_temp(bytes).merge(raw: bytes) end # # Temperature is the first 16 bits (2 bytes of 9 read), little-endian. - # It's a sign-extended two's complement 11-bit decimal, where LSB is the - # 2^-4 exponent, up through 2^6 for bit 11. The 5 MSBs repeat the sign. + # It's a sign-extended two's complement 12-bit decimal, where LSB is the + # 2^-4 exponent, up through 2^6 for next 10 bits. 5 MSBs repeat the sign. # - def decode(high_byte, low_byte) - # Concatenate to 16-bit and reverse two's complement if necessary. - value = high_byte << 8 | low_byte + def decode_temp(bytes) + # Get magnitude without sign. + value = bytes[1] << 8 | bytes[0] negative = (value[15] == 1) value = (value ^ 0xFFFF) + 1 if negative - # Expand the exponents, restore sign, and convert to Farenheit. - celsius = 0.0; exp = -4 + celsius = 0.0 + exp = -4 for bit in (0..10) - celsius = celsius + (2.0 ** (exp+bit)) if (value[bit] == 1) + if (value[bit] == 1) + celsius = celsius + (2.0 ** (exp+bit)) + end end + celsius = -celsius if negative farenheit = (celsius * 1.8 + 32).round(4) {celsius: celsius, farenheit: farenheit} end + + def decode_resolution(bytes) + config_byte = bytes[4] + offset = config_byte >> 5 + offset + 9 + end end end end diff --git a/lib/dino/components/one_wire/helper.rb b/lib/dino/components/one_wire/helper.rb index 6f2b4c42..8f1aa32a 100644 --- a/lib/dino/components/one_wire/helper.rb +++ b/lib/dino/components/one_wire/helper.rb @@ -2,32 +2,15 @@ module Dino module Components module OneWire class Helper + # # Convert 64-bit Integer ROM address to array of 8 LSByte first bytes. # def self.address_to_bytes(address) - [address].pack('> 8 - address.to_s(16) - end - # - # CRC is the last byte of any message. Start with it set to 0. Move through - # the n-1 data bytes LSBYTE FIRST, but bitwise LSBFIRST. For each bit: - # 1) XOR the bit with the current LSB of CRC - # 2) XOR the result of #1 with the bits at indices 3 and 4 of CRC. - # 3) Bitshift the CRC right by 1 - # 4) Write the result of #1 to the now empty MSB of the CRC - # After 64 bits, the CRC is valid if it matches the 9th of the read data. - # def self.crc_check(data) # # Sensor data will usually be an array of bytes, but for checking @@ -50,80 +33,6 @@ def self.crc_check(data) end crc == bytes.last end - - # - # This separates a search result, checks result for device disconnection, - # matches the family byte of the address to a Component class if one exists, - # and looks for the highest index discrepancy to continue the search. - # - def self.parse_search_result(str) - address, complement = self.split_search_result(str) - - # - # If any address-complement bit-pair is 1-1, either no device is - # present on the bus, or a device was disconnected during the search. - # - if (address & complement) > 0 - raise "OneWire device not connected or disconnected during search" - end - - # - # LS byte of the address holds the family code. - # Lookup against implemented classes. Nil if no match. - # - klass = self.family_lookup(address & 0x00000000000000FF) - - # - # XOR-ing address with complement gives a 0 bit at each discrepancy. - # XOR again with 64 1s to flip to 1s. Ruby truncates leading 0 bits, - # so MSB is the highest discrepancy. Result is -1 for no discrepancy. - # - # Note: The board only sends back discrepancies which we did not - # set to 1 on the last search! This does not track all known - # discrepancies in the address space, but gives us what we need: - # The highest discrepancy that we didn't write a 1 for last time. - # - new_discrepancies = (address ^ complement) ^ 0xFFFFFFFFFFFFFFFF - high_discrepancy = new_discrepancies.bit_length - 1 - - [{class: klass, address: address}, high_discrepancy] - end - - # - # A search result is a comma separated list of 8 byte-pairs. - # Each pair is an address byte, dash (-) separator, and complement byte. - # Pairs are ordered LS byte first. Reverse to MS for easier bitshift. - # - def self.split_search_result(str) - byte_pairs = str.split(",") - address = 0 - complement = 0 - - byte_pairs.reverse.each do |pair| - address_byte, complement_byte = pair.split("-").map(&:to_i) - address = (address << 8) | address_byte - complement = (complement << 8) | complement_byte - end - - if crc_check(address) - return [address, complement] - else - raise "CRC error for device address on OneWire bus" - end - end - - # - # Define FAMILY_CODE in a slave class to get it identified during search. - # - def self.family_lookup(family_code) - OneWire.constants.each do |const| - obj = OneWire.const_get(const) - if (obj.is_a? Class) && (obj.const_defined? "FAMILY_CODE") - return obj if obj::FAMILY_CODE == family_code - end - end - return nil - end end end end diff --git a/lib/dino/components/one_wire/slave.rb b/lib/dino/components/one_wire/slave.rb new file mode 100644 index 00000000..1b0b1ab2 --- /dev/null +++ b/lib/dino/components/one_wire/slave.rb @@ -0,0 +1,76 @@ +module Dino + module Components + module OneWire + class Slave + include Setup::Base + attr_reader :address + alias :bus :board + + # + # Remove the MSByte (CRC) and LSByte (family code) from slave's ROM + # address to get a printable 48-bit serial number in hex. + # + def serial_number + address = @address & 0x00FFFFFFFFFFFFFF + address = address >> 8 + address.to_s(16).rjust(12, "0") + end + + def initialize(options={}) + options[:board] ||= options[:bus] + super(options) + end + + def after_initialize(options={}) + super(options) + unless options[:address] + raise ArgumentError, 'missing 1-Wire slave ROM address; try searching first' + end + @address = options[:address] + end + + def read_scratch(num_bytes) + atomically do + match + bus.write(READ_SCRATCH) + bus.read(num_bytes) + end + end + + def write_scratch(bytes) + atomically do + match + bus.write(WRITE_SCRATCH) + bus.write(bytes) + end + end + + def copy_scratch + atomically do + match + bus.write(COPY_SCRATCH) + sleep 0.02 + bus.reset if bus.parasite_power + end + end + + def atomically(&block) + bus.mutex.synchronize do + block.call + end + end + + def match + bus.reset + # Skip ROM match if only one device on the bus. + if bus.found_devices == 1 + bus.write(SKIP_ROM) + else + bus.write(MATCH_ROM) + bus.write(Helper.address_to_bytes(self.address)) + end + end + end + end + end +end diff --git a/lib/dino/components/piezo.rb b/lib/dino/components/piezo.rb index 4896646b..c1f558e2 100644 --- a/lib/dino/components/piezo.rb +++ b/lib/dino/components/piezo.rb @@ -12,11 +12,11 @@ def after_initialize(options={}) # Duration is in mills def tone(value, duration = 500) - board.tone pin, value, duration + board.tone(pin, value, duration) end def no_tone - board.no_tone pin + board.no_tone(pin) end end end diff --git a/lib/dino/components/register/shift_in.rb b/lib/dino/components/register/shift_in.rb index a9344a63..226106ab 100644 --- a/lib/dino/components/register/shift_in.rb +++ b/lib/dino/components/register/shift_in.rb @@ -7,55 +7,51 @@ class ShiftIn # Model registers that use the arduino shift functions as multi-pin # components, specifying clock, data and latch pins. # - # options = {board: my_board, pins: {clock: clock_pin, latch: latch_pin, data: data_pin} + # options = board: my_board, + # pins: {clock: clock_pin, latch: latch_pin, data: data_pin} # include Setup::MultiPin proxy_pins clock: Basic::DigitalOutput, data: Basic::DigitalInput, latch: Register::Select - # - # Data will arrive on the latch pin, similar to an analog read. - # Bubble it up to the register object (self), then deal with it there. - # def after_initialize(options={}) - super(options) if defined?(super) + super(options) + self.rising_clock = options[:rising_clock] + bubble_callbacks + end - # - # Certain registers which use rising edges for clock signals produce - # errors with the native Arduino shiftIn function unless you set the clock - # pin high before reading. Setting this instance var to 1 will include - # that instruction on every call to read or listen. - # - @preclock_high = options[:preclock_high] ? 1 : 0 + # + # Some registers use rising edges for clock signals. Unless we pull clock + # pin high before each read, bits in the value will be out of position. + # Set this once and future calls to #read and #listen will do it. + # + attr_reader :rising_clock - bubble_callbacks + def rising_clock=(value) + @rising_clock = [0, nil, false].include?(value) ? false : true end + # Reads come through the latch pin. Bubble them up to ourselves. def bubble_callbacks proxies[:latch].add_callback do |byte| self.update(byte) end end - # - # Read using the native shiftIn function of the Arduino library. - # - def read - # Pack the extra parameters we need to send in the aux message then send. - aux = [data.pin, clock.pin, @preclock_high].pack('C*') - board.write Dino::Message.encode(command: 22, pin: latch.pin, value: @bytes, aux_message: aux) + def read(num_bytes=@bytes) + board.shift_read latch.pin, data.pin, clock.pin, num_bytes, + preclock_high: rising_clock end - def listen - # Pack the extra parameters we need to send in the aux message then send. - aux = [data.pin, clock.pin, @preclock_high].pack('C*') - board.write Dino::Message.encode(command: 23, pin: latch.pin, value: @bytes, aux_message: aux) + # Untested + def listen(num_bytes=@bytes) + board.shift_listen latch.pin, data.pin, clock.pin, num_bytes, + preclock_high: rising_clock end def stop - # Just need to send the latch pin to stop listening. - board.write Dino::Message.encode(command: 24, pin: latch.pin) + board.shift_stop(latch.pin) end end end diff --git a/lib/dino/components/register/shift_out.rb b/lib/dino/components/register/shift_out.rb index c8e2d005..b3de0fc4 100644 --- a/lib/dino/components/register/shift_out.rb +++ b/lib/dino/components/register/shift_out.rb @@ -7,24 +7,16 @@ class ShiftOut # Model registers that use the arduino shift functions as multi-pin # components, specifying clock, data and latch pins. # - # options = {board: my_board, pins: {clock: clock_pin, latch: latch_pin, data: data_pin} + # options = board: my_board, + # pins: {clock: clock_pin, latch: latch_pin, data: data_pin} # include Setup::MultiPin proxy_pins clock: Basic::DigitalOutput, data: Basic::DigitalOutput, latch: Register::Select - - # - # Write using the native shiftOut function of the Arduino library. - # def write(*bytes) - aux = bytes.flatten - length = aux.count - - # Prepend parameters we need to send in the aux message then pack and send. - aux = [data.pin, clock.pin, 0].concat(aux).pack('C*') - board.write Dino::Message.encode(command: 21, pin: latch.pin, value: length, aux_message: aux) + board.shift_write(latch.pin, data.pin, clock.pin, bytes) end end end diff --git a/lib/dino/components/register/spi_in.rb b/lib/dino/components/register/spi_in.rb index 87d11099..fa4836e5 100644 --- a/lib/dino/components/register/spi_in.rb +++ b/lib/dino/components/register/spi_in.rb @@ -1,46 +1,37 @@ module Dino module Components module Register + # + # Model SPI registers as single pin. Data comes back on the select pin, + # so just inherit from Select. + # + # options = {board: my_board, pin: slave_select_pin} + # class SPIIn < Select include Input - # - # Model SPI registers as single pin. Data comes back on the select pin, - # so just inherit from Select. - # - # options = {board: my_board, pin: slave_select_pin} - # + attr_reader :spi_mode, :frequency + def after_initialize(options={}) super(options) if defined?(super) - # Save SPI device settings in instance variables. - @spi_mode = options[:spi_mode] || 0 + @spi_mode = options[:spi_mode] || 0 # No default value for clock frequency. raise 'SPI clock rate (Hz) required in :frequency option' unless options[:frequency] @frequency = options[:frequency] end - # - # Read using a call to the native SPI library. - # def read - # Pack the extra parameters we need to send in the aux message then send. - start_address = 0 - aux = "#{[start_address, @spi_mode].pack('C*')}#{[@frequency].pack('V')}" - board.write Dino::Message.encode(command: 27, pin: pin, value: @bytes, aux_message: aux) + board.spi_read(pin, spi_mode, frequency, @bytes) end def listen - # Pack the extra parameters we need to send in the aux message then send. - start_address = 0 - aux = "#{[start_address, @spi_mode].pack('C*')}#{[@frequency].pack('V')}" - board.write Dino::Message.encode(command: 28, pin: pin, value: @bytes, aux_message: aux) + board.spi_listen(pin, spi_mode, frequency, @bytes) end def stop - # Just need to send the select pin to stop listening. - board.write Dino::Message.encode(command: 29, pin: pin) + board.spi_stop(pin) end end end diff --git a/lib/dino/components/register/spi_out.rb b/lib/dino/components/register/spi_out.rb index 96645e9f..292776d0 100644 --- a/lib/dino/components/register/spi_out.rb +++ b/lib/dino/components/register/spi_out.rb @@ -1,17 +1,18 @@ module Dino module Components module Register + # + # Model SPI registers as single pin. Data comes back on the select pin, + # so just inherit from Select. + # + # options = {board: my_board, pin: slave_select_pin} + # class SPIOut < Select include Output - # - # Model SPI registers as single pin. Data comes back on the select pin, - # so just inherit from Select. - # - # options = {board: my_board, pin: slave_select_pin} - # + + attr_reader :spi_mode, :frequency def after_initialize(options={}) - # Save SPI device settings in instance variables. @spi_mode = options[:spi_mode] || 0 # No default value for clock frequency. @@ -21,16 +22,8 @@ def after_initialize(options={}) super(options) if defined?(super) end - # - # Write using a call to the native SPI library. - # def write(*bytes) - # Pack the extra parameters we need to send in the aux message then send. - aux = bytes.flatten - length = aux.count - aux = "#{[@spi_mode].pack('C')}#{[@frequency].pack('V')}#{aux.pack('C*')}" - - board.write Dino::Message.encode(command: 26, pin: pin, value: length, aux_message: aux) + board.spi_write(pin, spi_mode, frequency, bytes.flatten) end end end diff --git a/lib/dino/components/rotary_encoder.rb b/lib/dino/components/rotary_encoder.rb index 45293166..09fc222e 100644 --- a/lib/dino/components/rotary_encoder.rb +++ b/lib/dino/components/rotary_encoder.rb @@ -11,7 +11,7 @@ class RotaryEncoder alias :position :state def after_initialize(options={}) - super(options) if defined?(super) + super(options) # Default to listening every tick (1ms / 1kHz) divider = options[:divider] || 1 @@ -40,17 +40,16 @@ def start # # Return a hash with the new :position and pass through :change, overriding # the data param we took, which would have passed directly to callbacks. - # Callbacks can use either :position (degrees) or :change (steps). + # Callbacks can use either :position (in degrees) or :change (in steps). # def pre_callback_filter(data) - data = { change: data, - position: (state + (data * degrees_per_step)) % 360 } + { change: data, position: (state + (data * degrees_per_step)) % 360 } end # # Callbacks run now, receiving only the value of #pre_callback_filter # # After callbacks, set @state to the position calculated earlier. - # This method also receives the value of #pre_callback_filter. + # This method also receives the result of #pre_callback_filter. # def update_self(data) @state = data[:position] diff --git a/lib/dino/components/servo.rb b/lib/dino/components/servo.rb index f6902cf6..9e2a77f5 100644 --- a/lib/dino/components/servo.rb +++ b/lib/dino/components/servo.rb @@ -3,9 +3,9 @@ module Components class Servo include Setup::SinglePin include Mixins::Threaded - + def after_initialize(options={}) - board.servo_toggle(pin, 1) + board.servo_toggle(pin, :on) end def position=(value) diff --git a/lib/dino/components/setup/base.rb b/lib/dino/components/setup/base.rb index acf53c6b..053569f6 100644 --- a/lib/dino/components/setup/base.rb +++ b/lib/dino/components/setup/base.rb @@ -8,7 +8,6 @@ def initialize(options={}) initialize_board(options) initialize_pins(options) register - after_initialize(options) end @@ -30,18 +29,14 @@ def unregister end # - # Setup::Base just requires a board and adds the component to the list of components. - # Mix in other modules from Setup or define this method in your class to initialize the pin(s). + # Setup::Base only requires a board. Mix in modules from Setup or define + # this method in your class to use pins. # def initialize_pins(options={}) ; end alias :initialize_pin :initialize_pins - # - # Define #after_initialize in your component class. - # - # @note This method should be implemented in the including class. - # - def after_initialize(options={}) ; end + # Override in components. Call super when inheriting or mixing in. + def after_initialize(options={}); end end end end diff --git a/lib/dino/components/setup/input.rb b/lib/dino/components/setup/input.rb index f2dfbfb6..1349f438 100644 --- a/lib/dino/components/setup/input.rb +++ b/lib/dino/components/setup/input.rb @@ -2,6 +2,7 @@ module Dino module Components module Setup module Input + include SinglePin attr_reader :pullup def pullup=(pullup) diff --git a/lib/dino/components/setup/multi_pin.rb b/lib/dino/components/setup/multi_pin.rb index cfd1aab0..9a0baa2d 100644 --- a/lib/dino/components/setup/multi_pin.rb +++ b/lib/dino/components/setup/multi_pin.rb @@ -11,9 +11,7 @@ module MultiPin include Base attr_reader :pin, :pins, :pullups, :proxies - # # Return a hash with the state of each proxy component. - # def proxy_states hash = {} proxies.each_key do |key| @@ -32,11 +30,10 @@ def self.included(base) module ClassMethods # - # Requiring a pin simply raises an error if not specified in options[:pins]. + # Requiring a pin simply raises an error if missing from options[:pins]. # It will not automatically set up a subcomponent or do anything else. # This is useful for calling Arduino-native libraries, where we need # to say what pins we are using, but don't need to do more in Ruby. - # See the LCD component for an example. # def require_pins(*args) required_pins = self.class_eval('@@required_pins') rescue [] @@ -49,17 +46,15 @@ def require_pins(*args) # Proxying a pin models it as a single-pin component and requires it. # It can be made optional by adding `optional: true` to the hash. # - # On initializing a multi-pin component, its proxy instances are - # created and stored in the @proxies variable. Call #proxies for access. - # - # Lastly, convenience methods for each proxy are defined on the singleton - # class of the multi-pin instance. + # When instancing a multi-pin component, its proxy instances are + # created and stored in @proxies, readable via `#proxies`. # - # For example, you can call `rgb_led.red` for the AnalogOutput - # instance that controls the red part of that RGB led. + # A convenience method for each proxy is defined on the singleton + # class of the multi-pin instance. For example, `rgb_led.red` returns + # the AnalogOutput instance for the red part of that specific RGB LED. # - # These method names correspond to the hash keys (pin names) defined in - # the class definition call to `proxy_pins`. See RgbLed class for examples. + # Method names match the hash keys (pin names) used when calling + # '::proxy_pins' in the class definition. See RgbLed class for examples. # def proxy_pins(options={}) if options[:optional] diff --git a/lib/dino/components/setup/output.rb b/lib/dino/components/setup/output.rb index 4ef8bca3..aaba505d 100644 --- a/lib/dino/components/setup/output.rb +++ b/lib/dino/components/setup/output.rb @@ -2,6 +2,7 @@ module Dino module Components module Setup module Output + include SinglePin protected def initialize_pins(options={}) diff --git a/lib/dino/components/setup/single_pin.rb b/lib/dino/components/setup/single_pin.rb index 6088b3e0..09e2f4ed 100644 --- a/lib/dino/components/setup/single_pin.rb +++ b/lib/dino/components/setup/single_pin.rb @@ -9,9 +9,6 @@ module SinglePin attr_writer :pin - # - # Require a single pin for single pin components. - # def initialize_pins(options={}) raise 'a pin is required for this component' unless options[:pin] self.pin = board.convert_pin(options[:pin]) @@ -19,7 +16,7 @@ def initialize_pins(options={}) def mode=(mode) @mode = mode - board.set_pin_mode(self.pin, mode) + board.set_pin_mode(pin, mode) end end end diff --git a/spec/lib/components/mixins/callbacks_spec.rb b/spec/lib/components/mixins/callbacks_spec.rb index 54941442..188bbf75 100644 --- a/spec/lib/components/mixins/callbacks_spec.rb +++ b/spec/lib/components/mixins/callbacks_spec.rb @@ -6,6 +6,7 @@ module Mixins describe Callbacks do class CallbackComponent + include Setup::Base include Callbacks def initialize; after_initialize; end end diff --git a/spec/lib/components/mixins/listener_spec.rb b/spec/lib/components/mixins/listener_spec.rb index 7dc92b86..fc8e5c5d 100644 --- a/spec/lib/components/mixins/listener_spec.rb +++ b/spec/lib/components/mixins/listener_spec.rb @@ -6,6 +6,7 @@ module Mixins describe Poller do class ListenComponent + include Setup::Base include Listener def _listen(divider=nil); end def _stop_listen; end diff --git a/spec/lib/components/mixins/poller_spec.rb b/spec/lib/components/mixins/poller_spec.rb index e87ddc7f..cfafcaeb 100644 --- a/spec/lib/components/mixins/poller_spec.rb +++ b/spec/lib/components/mixins/poller_spec.rb @@ -6,6 +6,7 @@ module Mixins describe Poller do class PollComponent + include Setup::Base include Poller def _read; end def initialize; after_initialize; end diff --git a/spec/lib/components/mixins/reader_spec.rb b/spec/lib/components/mixins/reader_spec.rb index 8f92eb23..66b7e63f 100644 --- a/spec/lib/components/mixins/reader_spec.rb +++ b/spec/lib/components/mixins/reader_spec.rb @@ -6,6 +6,7 @@ module Mixins describe Reader do class ReadComponent + include Setup::Base include Reader def _read; end def initialize; after_initialize; end diff --git a/spec/lib/components/mixins/threaded_spec.rb b/spec/lib/components/mixins/threaded_spec.rb index bcd689f6..51296368 100644 --- a/spec/lib/components/mixins/threaded_spec.rb +++ b/spec/lib/components/mixins/threaded_spec.rb @@ -4,8 +4,10 @@ module Dino module Components module Mixins describe Threaded do + include BoardMock class ThreadedComponent + include Setup::Base include Threaded def foo(str="test") @bar = str @@ -14,7 +16,7 @@ def foo(str="test") interrupt_with :foo end - subject { ThreadedComponent.new } + subject { ThreadedComponent.new(board: board) } describe 'ClassMethods' do it 'should add methods to interrupt the thread using #interrupt_with' do @@ -78,7 +80,7 @@ def foo(str="test") describe '#enable_interrupts' do it 'should override the given method on the singleton class only' do - second_part = ThreadedComponent.new + second_part = ThreadedComponent.new(board: board) before_class = second_part.method(:foo) before_instance = subject.method(:foo) diff --git a/spec/lib/components/one_wire/ds18b20_spec.rb b/spec/lib/components/one_wire/ds18b20_spec.rb index ab697f92..0be629a7 100644 --- a/spec/lib/components/one_wire/ds18b20_spec.rb +++ b/spec/lib/components/one_wire/ds18b20_spec.rb @@ -6,14 +6,18 @@ module OneWire describe DS18B20 do include BoardMock let(:bus) { double.as_null_object } - subject { DS18B20.new(board: bus, address: 0xFFFFFFFFFFFFFFFF) } + subject { DS18B20.new(bus: bus, address: 0xFFFFFFFFFFFFFFFF) } - describe '#decode' do + describe '#decode_temp' do it 'should decode values matching the datasheet and convert C to F' do - expect(subject.decode(0b0000_0111, 0b1101_0000)).to eq(celsius: 125, farenheit: 257) - expect(subject.decode(0b0000_0000, 0b0000_0000)).to eq(celsius: 0, farenheit: 32) - expect(subject.decode(0b1111_1111, 0b0101_1110)).to eq(celsius: -10.125, farenheit: 13.775) - expect(subject.decode(0b1111_1100, 0b1001_0000)).to eq(celsius: -55, farenheit: -67) + expect(subject.decode_temp([0b1101_0000,0b0000_0111])) + .to eq(celsius: 125, farenheit: 257) + expect(subject.decode_temp([0b0000_0000,0b0000_0000])) + .to eq(celsius: 0, farenheit: 32) + expect(subject.decode_temp([0b0101_1110,0b1111_1111])) + .to eq(celsius: -10.125, farenheit: 13.775) + expect(subject.decode_temp([0b1001_0000,0b1111_1100])) + .to eq(celsius: -55, farenheit: -67) end end end diff --git a/spec/lib/components/one_wire/enumerator_spec.rb b/spec/lib/components/one_wire/enumerator_spec.rb index a4111d0c..14dc675b 100644 --- a/spec/lib/components/one_wire/enumerator_spec.rb +++ b/spec/lib/components/one_wire/enumerator_spec.rb @@ -83,7 +83,7 @@ def search(branch_mask) comp = comp | (comp_bit * (2 ** j)) # Override address bit to a 1 in variable if 1 in mask. - # Write whatever addr_bit ends up being back to the simulator. + # Write whatever addr_bit ends up being back to the bus simulator. if branch_mask[(i*8) + j] == 1 @simulator.write_bit(1) addr = addr | (1 * (2 ** j)) @@ -104,7 +104,7 @@ def search(branch_mask) # class Bus def initialize(options={}) - @board_sim = BoardSimulator.new(options[:simulator]) + @board_sim = options[:board_sim] super(options) end @@ -125,11 +125,13 @@ def _search(branch_mask) allow_any_instance_of(Bus).to receive(:read_power_supply).and_return false # This gets slow really fast... 256 seems fine. - sim = BusSimulator.new(256) - bus = Bus.new(board: board, pin: 7, simulator: sim) + bus_sim = BusSimulator.new(256) + board_sim = BoardSimulator.new(bus_sim) + bus = Bus.new(board: board, pin: 7, board_sim: board_sim) + bus.search found_addresses = bus.found_devices.map { |d| d[:address] } - expect(found_addresses.sort).to eq(sim.addresses.sort) + expect(found_addresses.sort).to eq(bus_sim.addresses.sort) end end end diff --git a/spec/lib/components/register/shift_in_spec.rb b/spec/lib/components/register/shift_in_spec.rb index ac6c3aa1..407eb581 100644 --- a/spec/lib/components/register/shift_in_spec.rb +++ b/spec/lib/components/register/shift_in_spec.rb @@ -26,13 +26,13 @@ module Register expect(subject.instance_variable_get(:@bytes)).to eq(2) end - it 'should default the preclock_high variable to 0' do - expect(subject.instance_variable_get(:@preclock_high)).to eq(0) + it 'should default the rising_clock variable to 0' do + expect(subject.instance_variable_get(:@rising_clock)).to eq(false) end - it 'should set @preclock_high to 1 if given anything other than 0' do - subject = ShiftIn.new(options.merge(preclock_high: :yes)) - expect(subject.instance_variable_get(:@preclock_high)).to eq(1) + it 'should set @rising_clock to true if given anything other than false' do + subject = ShiftIn.new(options.merge(rising_clock: :yes)) + expect(subject.instance_variable_get(:@rising_clock)).to eq(true) end end @@ -51,7 +51,7 @@ module Register end it 'should request clock pin to go high before reading if set' do - subject = ShiftIn.new(options.merge(preclock_high: 1)) + subject = ShiftIn.new(options.merge(rising_clock: 1)) expect(board).to receive(:write).with "22.8.1.#{[11,12,1].pack('C*')}\n" subject.read end diff --git a/spec/lib/components/servo_spec.rb b/spec/lib/components/servo_spec.rb index 839b8ac2..e52e1644 100644 --- a/spec/lib/components/servo_spec.rb +++ b/spec/lib/components/servo_spec.rb @@ -9,7 +9,7 @@ module Components describe '#initialize' do it 'should toggle the servo library on for the pin' do - expect(board).to receive(:servo_toggle).with(options[:pin], 1) + expect(board).to receive(:servo_toggle).with(options[:pin], :on) subject end end diff --git a/src/lib/DinoCoreIO.cpp b/src/lib/DinoCoreIO.cpp index acd18790..7fb24df1 100644 --- a/src/lib/DinoCoreIO.cpp +++ b/src/lib/DinoCoreIO.cpp @@ -75,8 +75,8 @@ void Dino::coreResponse(int p, int v){ // CMD = 07 // Set a listener ON or OFF, or change its type, or divider. -// Takes settings as binary stored in val from the parser and applies -// them to an existing listener if pin was already used, or first inactive. +// Takes settings as mask stored in val and applies to existing listener +// if pin was already used, or first inactive. See Dino.h for mask structure. void Dino::setListener(){ boolean found = false; // Check if previously assigned a listener to this pin and re-use. diff --git a/src/lib/DinoOneWire.cpp b/src/lib/DinoOneWire.cpp index 41406df7..70a56aeb 100644 --- a/src/lib/DinoOneWire.cpp +++ b/src/lib/DinoOneWire.cpp @@ -8,56 +8,58 @@ // CMD = 41 -// Reset the OneWire bus and optionally return a presence value if val > 0. +// Reset the OneWire bus and return a presence value only if requested. // void Dino::owReset(){ - // bool present; + byte present; pinMode(pin, OUTPUT); digitalWrite(pin, LOW); delayMicroseconds(500); pinMode(pin, INPUT); delayMicroseconds(80); - // present = !digitalRead(pin); + present = digitalRead(pin); delayMicroseconds(420); - // send presence value here + if(val>0) coreResponse(pin, present); } // CMD = 42 -// Read a 64-bit address and complement, echoing the address bits unless there's -// a discrepancy, then we check the branch mask in auxMsg to decide what to do. +// Read a 64-bit address and complement, echoing each address bit to the bus, +// unless we are searching a branch that has that bit set to 1, then echo 1. // void Dino::owSearch(){ byte addr; byte comp; - // Start with the pin that the bus is on and the colon. + // Start streaming the message. stream->print(pin); stream->print(':'); - // Print each byte read, followed by a comma, or newline for last byte. for(byte i=0; i<8; i++){ for(byte j=0; j<8; j++){ bitWrite(addr, j, owReadBit()); bitWrite(comp, j, owReadBit()); - // If there is a discrepancy, first 8 bytes of auxMsg is a branch mask. - // These are bits we must write 1 for since we're searching that branch. - // Also set the address bit to 1, but don't touch the complement. - // This 'corrupts' the check for discrepancies we already new about, - // but we're tracking that remotely anyway. + // First 8 bytes of auxMsg is a 64-bit branch mask. Any bit set to 1 says + // that we're searching a branch with that bit set to 1, and must force + // it to be 1 on this pass. Write 1 to both the address bit and the bus. + // + // We also do not change the complement bit from 0, Even though the bus + // said 0/0, we are sending back 1/0, hiding discrepancies we are testing, + // only sending those that appeared this time, which is what we care about. // if(bitRead(auxMsg[i], j) == 1){ owWriteBit(1); bitWrite(addr, j, 1); - // If we're don't already know about the discrepancy, behave normally - // so it shows up on the next search. + // Whether there was no "1-branch" marked for this bit, or there is no + // discrepancy at all, just echo address bit to the bus. We compare + // addr/comp remotely to find discrepancies for future passes. // } else { owWriteBit(bitRead(addr, j)); } } stream->print(addr); - stream->print("-"); + stream->print('-'); stream->print(comp); stream->print((i == 7) ? '\n' : ','); } @@ -66,12 +68,12 @@ void Dino::owSearch(){ // CMD = 43 // Write to the OneWire bus. // -// val = number of bytes to write + parasite power condition OR-ed into MSB. +// val = number of bytes to write + parasite power condition flag in MSB. // auxMsg[0] = first byte of data and so on... -// Limited to 127 bytes. Validate on remote end. +// Limited to 127 bytes. Validate remotely. // void Dino::owWrite(){ - // Check and clear parasite flag masked into array length. + // Check and clear parasite flag. bool parasite = bitRead(val, 7); bitClear(val, 7); @@ -113,9 +115,11 @@ void Dino::owWriteBit(byte b){ // Read bytes from the OneWire bus. // // val = number of bytes to read +// void Dino::owRead(){ byte b; - // Start with the pin that the bus is on and the colon. + + // Start streaming the message. stream->print(pin); stream->print(':'); // Print each byte read, followed by a comma, or newline for last byte. @@ -130,7 +134,7 @@ void Dino::owRead(){ byte Dino::owReadBit(){ byte b; - // Pull low for at least 1us to start a read time slot, then release. + // Pull low for at least 1us to start a read slot, then release. pinMode(pin, OUTPUT); digitalWrite(pin, LOW); delayMicroseconds(1); @@ -139,10 +143,10 @@ byte Dino::owReadBit(){ // Wait for the slave to write to the bus. It should hold for up to 15us. delayMicroseconds(5); - // If slave pulled the bus high, the bit a 1, else 0. + // If slave pulled the bus high, the bit is a 1, else 0. b = digitalRead(pin); - // Wait out the 60us window + recovery time. + // Wait out the 60us read slot + recovery time. delayMicroseconds(55); return b; } From 5725af1efa1ec5d02a8042d78ee2d665d41fe243 Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 13 Feb 2018 16:08:26 -0400 Subject: [PATCH 167/296] =?UTF-8?q?Don=E2=80=99t=20use=20Fixnum#bit=5Fleng?= =?UTF-8?q?th=20to=20keep=20Ruby=202.0=20compatibility.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dino/components/one_wire/bus_enumerator.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/dino/components/one_wire/bus_enumerator.rb b/lib/dino/components/one_wire/bus_enumerator.rb index fba17e5e..0b69da91 100644 --- a/lib/dino/components/one_wire/bus_enumerator.rb +++ b/lib/dino/components/one_wire/bus_enumerator.rb @@ -65,7 +65,9 @@ def parse_search_result(result) # # This is OK. We only want the highest discrepancy we did not set to 1. # - high_discrepancy = new_discrepancies.bit_length - 1 + # high_discrepancy = new_discrepancies.bit_length - 1 + high_discrepancy = -1 + (0..63).each { |i| high_discrepancy = i if new_discrepancies[i] == 1 } # LSByte of address is product family. Check for existing class. klass = family_lookup(address & 0x00000000000000FF) From f4b01e0f38579e679dd06e3c491ca1dab6caba6b Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 13 Feb 2018 16:21:50 -0400 Subject: [PATCH 168/296] Trying to fix jruby tests --- spec/lib/components/mixins/threaded_spec.rb | 47 ++++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/spec/lib/components/mixins/threaded_spec.rb b/spec/lib/components/mixins/threaded_spec.rb index 51296368..1538acc3 100644 --- a/spec/lib/components/mixins/threaded_spec.rb +++ b/spec/lib/components/mixins/threaded_spec.rb @@ -45,67 +45,72 @@ def foo(str="test") it 'should store in @thread' do thread = Thread.current - async = Proc.new { thread = Thread.current } - subject.threaded(&async) - while(!subject.instance_variable_get :@thread) do; end - expect(subject.instance_variable_get :@thread).to_not eq(thread) + component = subject + component.threaded {} + while(!component.instance_variable_get :@thread) do; end + expect(component.instance_variable_get :@thread).to_not eq(thread) end end describe '#threaded_loop' do it 'should loop the block in the thread' do + component = subject main_thread = Thread.current async_thread = Thread.current async = Proc.new { async_thread = Thread.current} expect(async).to receive(:call).and_call_original - expect(subject).to receive(:loop) do |&block| + expect(component).to receive(:loop) do |&block| expect(block).to eq(async) end.and_yield - subject.threaded_loop(&async) + component.threaded_loop(&async) while(main_thread == async_thread) do; end - subject.stop_thread + component.stop_thread expect(main_thread).to_not eq(async_thread) end end describe '#stop_thread' do it 'should kill the thread' do - subject.threaded { sleep } - expect(subject.instance_variable_get :@thread).to receive(:kill) - subject.stop_thread + component = subject + component.threaded { sleep } + expect(component.instance_variable_get :@thread).to receive(:kill) + component.stop_thread end end describe '#enable_interrupts' do it 'should override the given method on the singleton class only' do + first_part = subject second_part = ThreadedComponent.new(board: board) before_class = second_part.method(:foo) - before_instance = subject.method(:foo) + before_instance = first_part.method(:foo) - subject.enable_interrupts + first_part.enable_interrupts after_class = second_part.method(:foo) - after_instance = subject.method(:foo) + after_instance = first_part.method(:foo) expect(after_class).to eq(before_class) expect(after_instance).to_not eq(before_instance) end it 'should pass arguments through to the original method' do - original = subject.method(:foo) - subject.enable_interrupts - subject.foo("dino") - expect(original).to_not eq(subject.method(:foo)) - expect(subject.bar).to eq("dino") + component = subject + original = component.method(:foo) + component.enable_interrupts + component.foo("dino") + expect(original).to_not eq(component.method(:foo)) + expect(component.bar).to eq("dino") end end describe 'calling an interrupt' do it 'should stop the thread' do - subject.threaded { sleep } - expect(subject).to receive(:stop_thread) - subject.foo + component = subject + component.threaded { sleep } + expect(component).to receive(:stop_thread) + component.foo end end end From 1a09b98d5bc227c799e83dfbfab59e52f544645f Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 13 Feb 2018 16:32:49 -0400 Subject: [PATCH 169/296] =?UTF-8?q?Trying=20to=20get=202.0.0=20and=20jruby?= =?UTF-8?q?=20to=20agree=E2=80=A6.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spec/lib/components/mixins/threaded_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/components/mixins/threaded_spec.rb b/spec/lib/components/mixins/threaded_spec.rb index 1538acc3..eace7eac 100644 --- a/spec/lib/components/mixins/threaded_spec.rb +++ b/spec/lib/components/mixins/threaded_spec.rb @@ -60,7 +60,7 @@ def foo(str="test") async = Proc.new { async_thread = Thread.current} expect(async).to receive(:call).and_call_original - expect(component).to receive(:loop) do |&block| + allow_any_instance_of(ThreadedComponent).to receive(:loop) do |&block| expect(block).to eq(async) end.and_yield From f2587bef17da595c5151f790d58bbfd76d43575b Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 13 Feb 2018 16:38:19 -0400 Subject: [PATCH 170/296] Maybe no sleep in wait loops is the real problem? --- spec/lib/components/mixins/threaded_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/lib/components/mixins/threaded_spec.rb b/spec/lib/components/mixins/threaded_spec.rb index eace7eac..ec586f91 100644 --- a/spec/lib/components/mixins/threaded_spec.rb +++ b/spec/lib/components/mixins/threaded_spec.rb @@ -47,7 +47,7 @@ def foo(str="test") thread = Thread.current component = subject component.threaded {} - while(!component.instance_variable_get :@thread) do; end + while(!component.instance_variable_get :@thread) do; sleep 0.05; end expect(component.instance_variable_get :@thread).to_not eq(thread) end end @@ -60,12 +60,12 @@ def foo(str="test") async = Proc.new { async_thread = Thread.current} expect(async).to receive(:call).and_call_original - allow_any_instance_of(ThreadedComponent).to receive(:loop) do |&block| + expect(component).to receive(:loop) do |&block| expect(block).to eq(async) end.and_yield component.threaded_loop(&async) - while(main_thread == async_thread) do; end + while(main_thread == async_thread) do; sleep 0.05; end component.stop_thread expect(main_thread).to_not eq(async_thread) end From 234fd1f4c90913a8980b71bbbdc2d578aeb9c7f3 Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 13 Feb 2018 16:51:06 -0400 Subject: [PATCH 171/296] =?UTF-8?q?RSpec=20is=20so=20great=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- spec/lib/components/mixins/threaded_spec.rb | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/spec/lib/components/mixins/threaded_spec.rb b/spec/lib/components/mixins/threaded_spec.rb index ec586f91..3b761946 100644 --- a/spec/lib/components/mixins/threaded_spec.rb +++ b/spec/lib/components/mixins/threaded_spec.rb @@ -54,20 +54,19 @@ def foo(str="test") describe '#threaded_loop' do it 'should loop the block in the thread' do - component = subject - main_thread = Thread.current - async_thread = Thread.current - async = Proc.new { async_thread = Thread.current} + @mutex = Mutex.new + @count = 0 - expect(async).to receive(:call).and_call_original - expect(component).to receive(:loop) do |&block| - expect(block).to eq(async) - end.and_yield + component = subject + component.threaded_loop do + @mutex.synchronize do + @count = @count + 1 + end + end - component.threaded_loop(&async) - while(main_thread == async_thread) do; sleep 0.05; end + while(@mutex.synchronize{ @count } < 2) do; sleep 0.05; end component.stop_thread - expect(main_thread).to_not eq(async_thread) + expect(@count).to be > 1 end end From d102c029ec4ba10812ed88f7073303a3b2a4a569 Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 13 Feb 2018 19:34:43 -0400 Subject: [PATCH 172/296] Comment cleanup, 1-Wire fixes, tests. --- lib/dino/components/dht.rb | 1 - lib/dino/components/ir_emitter.rb | 2 - lib/dino/components/mixins/bus.rb | 3 + lib/dino/components/one_wire/bus.rb | 5 +- .../components/one_wire/bus_enumerator.rb | 32 ++--- lib/dino/components/one_wire/ds18b20.rb | 23 ++-- lib/dino/components/one_wire/helper.rb | 9 -- lib/dino/components/one_wire/slave.rb | 30 +++-- lib/dino/components/rotary_encoder.rb | 2 - lib/dino/components/setup/base.rb | 5 +- lib/dino/tx_rx/flow_control.rb | 3 +- lib/dino_cli/generator.rb | 5 +- spec/lib/components/one_wire/ds18b20_spec.rb | 42 +++++- spec/lib/components/one_wire/slave_spec.rb | 125 ++++++++++++++++++ 14 files changed, 212 insertions(+), 75 deletions(-) create mode 100644 spec/lib/components/one_wire/slave_spec.rb diff --git a/lib/dino/components/dht.rb b/lib/dino/components/dht.rb index c804d12b..cf3f91d9 100644 --- a/lib/dino/components/dht.rb +++ b/lib/dino/components/dht.rb @@ -14,7 +14,6 @@ def _read board.dht_read(self.pin) end - # Process raw data from the board before running #update. def pre_callback_filter(data) t, h = data.split(",") { temperature: t.to_f, humidity: h.to_f } diff --git a/lib/dino/components/ir_emitter.rb b/lib/dino/components/ir_emitter.rb index 39e57aa3..687c3d17 100644 --- a/lib/dino/components/ir_emitter.rb +++ b/lib/dino/components/ir_emitter.rb @@ -12,9 +12,7 @@ def send(pulses=[], options={}) raise ArgumentError 'pulse too long (max 65536 microsec)' if pulse > 65536 end - # Default to 38kHz. frequency = options[:frequency] || 38 - board.infrared_send(pin, frequency, pulses) end end diff --git a/lib/dino/components/mixins/bus.rb b/lib/dino/components/mixins/bus.rb index d44c16df..d597958f 100644 --- a/lib/dino/components/mixins/bus.rb +++ b/lib/dino/components/mixins/bus.rb @@ -7,9 +7,12 @@ module Bus # attached to it, matching Board, but without the full Board interface. # See OneWire::Bus class for an example. # + attr_reader :mutex + def after_initialize(options={}) super(options) @components = [] + @mutex = Mutex.new end attr_reader :components diff --git a/lib/dino/components/one_wire/bus.rb b/lib/dino/components/one_wire/bus.rb index 89f956e6..8ae0eef1 100644 --- a/lib/dino/components/one_wire/bus.rb +++ b/lib/dino/components/one_wire/bus.rb @@ -6,17 +6,16 @@ class Bus include Mixins::Bus include Mixins::Reader - attr_reader :found_devices, :parasite_power, :mutex + attr_reader :found_devices, :parasite_power def after_initialize(options = {}) super(options) - @mutex = Mutex.new @found_devices = [] read_power_supply end # Without driving low first, results are inconsistent. - # Only the first (LS) bit matters, but it's easier to read a whole byte. + # Only first (LS) bit matters, but we can only read whole bytes. # def read_power_supply board.set_pin_mode(self.pin, :out) diff --git a/lib/dino/components/one_wire/bus_enumerator.rb b/lib/dino/components/one_wire/bus_enumerator.rb index 0b69da91..2d7eb45a 100644 --- a/lib/dino/components/one_wire/bus_enumerator.rb +++ b/lib/dino/components/one_wire/bus_enumerator.rb @@ -2,7 +2,7 @@ module Dino module Components module OneWire class Bus - # 1 bits in mask set discrepancies to 1. 0 leaves bit as read from bus. + # 1 bits in mask force discrepancies to 1. 0 leaves bit as read from bus. def _search(branch_mask) reset write(SEARCH_ROM) @@ -23,15 +23,13 @@ def search _search(branch_mask) block_until_read - # No untested discrepancies left. End the search. + # No unsearched discrepancies left. break if high_discrepancy == - 1 - # If high_discrepancy was not forced 1 last iteration, do it next. + # Force highest new discrepancy to 1 on the next search. # i.e. Go as deep as possible into each branch found then back out. # - if branch_mask[high_discrepancy] == 0 - branch_mask = branch_mask | (2 ** high_discrepancy) - end + branch_mask = branch_mask | (2 ** high_discrepancy) # Clear bits above high_discrepancy so we don't repeat branches. # When high_discrepancy < MSB of branch_mask, this moves us @@ -51,25 +49,15 @@ def parse_search_result(result) raise "OneWire device not connected or disconnected during search" end - # - # XOR address with complement to give a 0 at each discrepancy. - # XOR again with 64 1s to flip, so each discrepancy is a 1 instead. - # - new_discrepancies = (address ^ complement) ^ 0xFFFFFFFFFFFFFFFF - - # - # Note: Discrepancies forced to 1 in an iteration will not show up as - # discrepancies when parsing the result from that iteration. The board - # will have read 0-0 from the bus, but we told it to override the - # address bit to a 1, so we get back (1-0), as if no discrepancy. - # - # This is OK. We only want the highest discrepancy we did not set to 1. - # + # Gives 0 at every discrepancy we didn't write 1 for on this search. + new_discrepancies = address ^ complement + + # Newer rubies: # high_discrepancy = new_discrepancies.bit_length - 1 high_discrepancy = -1 - (0..63).each { |i| high_discrepancy = i if new_discrepancies[i] == 1 } + (0..63).each { |i| high_discrepancy = i if new_discrepancies[i] == 0 } - # LSByte of address is product family. Check for existing class. + # LSByte of address is product family. klass = family_lookup(address & 0x00000000000000FF) [{class: klass, address: address}, high_discrepancy] diff --git a/lib/dino/components/one_wire/ds18b20.rb b/lib/dino/components/one_wire/ds18b20.rb index 8c285d73..6fc8196c 100644 --- a/lib/dino/components/one_wire/ds18b20.rb +++ b/lib/dino/components/one_wire/ds18b20.rb @@ -2,10 +2,9 @@ module Dino module Components module OneWire class DS18B20 < Slave - include Mixins::Poller + FAMILY_CODE = 0x28 - # Scratch read specifically for the resolution if not set yet. def resolution @resolution ||= decode_resolution read_scratch(9) end @@ -32,7 +31,9 @@ def set_convert_time end def convert - @convert_time ||= 0.75 + @resolution ||= 12 + set_convert_time + atomically do match bus.write(CONVERT_T) @@ -43,18 +44,15 @@ def convert def _read convert - # This runs callbacks in our thread instead of the callback thread... - self.update(read_scratch(9)) + read_scratch(9) end def pre_callback_filter(data) # Data is 9 comma delimited numbers in ASCII, representing bytes. - bytes = data.split(",").map{|b| b.to_i} + bytes = data.split(",").map{ |b| b.to_i } return {crc_error: true} unless Helper.crc_check(bytes) - # Update resolution and conversion time each read. @resolution = decode_resolution(bytes) - set_convert_time decode_temp(bytes).merge(raw: bytes) end @@ -65,15 +63,14 @@ def pre_callback_filter(data) # 2^-4 exponent, up through 2^6 for next 10 bits. 5 MSBs repeat the sign. # def decode_temp(bytes) - # Get magnitude without sign. - value = bytes[1] << 8 | bytes[0] - negative = (value[15] == 1) - value = (value ^ 0xFFFF) + 1 if negative + magnitude = bytes[1] << 8 | bytes[0] + negative = (magnitude[15] == 1) + magnitude = (magnitude ^ 0xFFFF) + 1 if negative celsius = 0.0 exp = -4 for bit in (0..10) - if (value[bit] == 1) + if (magnitude[bit] == 1) celsius = celsius + (2.0 ** (exp+bit)) end end diff --git a/lib/dino/components/one_wire/helper.rb b/lib/dino/components/one_wire/helper.rb index 8f1aa32a..b4fcbd29 100644 --- a/lib/dino/components/one_wire/helper.rb +++ b/lib/dino/components/one_wire/helper.rb @@ -2,20 +2,11 @@ module Dino module Components module OneWire class Helper - - # - # Convert 64-bit Integer ROM address to array of 8 LSByte first bytes. - # def self.address_to_bytes(address) [address].pack('Q<').split("").map(&:ord) end - def self.crc_check(data) - # - # Sensor data will usually be an array of bytes, but for checking - # things like ROM addresses, we may receive a 64-bit Integer. - # if data.class == Integer bytes = address_to_bytes(data) else diff --git a/lib/dino/components/one_wire/slave.rb b/lib/dino/components/one_wire/slave.rb index 1b0b1ab2..3e170be1 100644 --- a/lib/dino/components/one_wire/slave.rb +++ b/lib/dino/components/one_wire/slave.rb @@ -3,19 +3,10 @@ module Components module OneWire class Slave include Setup::Base + include Mixins::Poller attr_reader :address alias :bus :board - # - # Remove the MSByte (CRC) and LSByte (family code) from slave's ROM - # address to get a printable 48-bit serial number in hex. - # - def serial_number - address = @address & 0x00FFFFFFFFFFFFFF - address = address >> 8 - address.to_s(16).rjust(12, "0") - end - def initialize(options={}) options[:board] ||= options[:bus] super(options) @@ -31,13 +22,15 @@ def after_initialize(options={}) def read_scratch(num_bytes) atomically do + # Bubble a single bus callback up to self, bound by this lock. + bus.add_callback(:read) { |data| self.update(data) } match bus.write(READ_SCRATCH) bus.read(num_bytes) end end - def write_scratch(bytes) + def write_scratch(*bytes) atomically do match bus.write(WRITE_SCRATCH) @@ -62,14 +55,25 @@ def atomically(&block) def match bus.reset - # Skip ROM match if only one device on the bus. - if bus.found_devices == 1 + if bus.found_devices.count < 2 bus.write(SKIP_ROM) else bus.write(MATCH_ROM) bus.write(Helper.address_to_bytes(self.address)) end end + + def serial_number + @serial_number ||= extract_serial + end + + def extract_serial + # Remove CRC. + address = @address & 0x00FFFFFFFFFFFFFF + # Remove family code. + address = address >> 8 + address.to_s(16).rjust(12, "0") + end end end end diff --git a/lib/dino/components/rotary_encoder.rb b/lib/dino/components/rotary_encoder.rb index 09fc222e..14d03aff 100644 --- a/lib/dino/components/rotary_encoder.rb +++ b/lib/dino/components/rotary_encoder.rb @@ -13,12 +13,10 @@ class RotaryEncoder def after_initialize(options={}) super(options) - # Default to listening every tick (1ms / 1kHz) divider = options[:divider] || 1 clock.listen(divider) data.listen(divider) - # Setup to track position in degrees. @steps = options[:steps] || 30 @degrees_per_step = (360 / steps).to_f @state = 0.0 diff --git a/lib/dino/components/setup/base.rb b/lib/dino/components/setup/base.rb index 053569f6..d194c255 100644 --- a/lib/dino/components/setup/base.rb +++ b/lib/dino/components/setup/base.rb @@ -28,9 +28,8 @@ def unregister board.remove_component(self) end - # - # Setup::Base only requires a board. Mix in modules from Setup or define - # this method in your class to use pins. + # Setup::Base only requires a board. + # Include modules from Setup or override this to use pins. # def initialize_pins(options={}) ; end alias :initialize_pin :initialize_pins diff --git a/lib/dino/tx_rx/flow_control.rb b/lib/dino/tx_rx/flow_control.rb index 8436c38e..1010dcb1 100644 --- a/lib/dino/tx_rx/flow_control.rb +++ b/lib/dino/tx_rx/flow_control.rb @@ -11,7 +11,6 @@ def initialize(*args) reset_flow_control end - # Split messages to use available buffer, and wrap #write in a mutex. def write(message) add_write_call @write_mutex.synchronize do @@ -68,7 +67,7 @@ def read_and_parse if line && line.match(/\AACK:/) # A HandshakeAttempt is observing. Also pass self so it can stop. changed && notify_observers(self, line.split(":", 2)[1]) - # Empty @transit_bytes. ACK: means the board reset its counter too. + # ACK: means the board reset its counter. Reset ours. @transit_mutex.synchronize { @transit_bytes = 0 } elsif line && line.match(/\ARCV:/) remove_transit_bytes(line.split(/:/)[1].to_i) diff --git a/lib/dino_cli/generator.rb b/lib/dino_cli/generator.rb index 94c6885b..d2004536 100644 --- a/lib/dino_cli/generator.rb +++ b/lib/dino_cli/generator.rb @@ -10,11 +10,9 @@ def initialize(options={}) end def append_target - # Default to generating the mega sketch. options[:target] = :mega unless options[:target] # Preserve the source sketch name, since we need to copy that file. options[:src_sketch_name] = options[:sketch_name].dup - # Append the target to the output sketch name/folder when not using :mega. options[:sketch_name] << "_#{options[:target]}" unless options[:target] == :mega end @@ -28,10 +26,9 @@ def self.run!(options={}) end def read - # Start by just copying the PACKAGES hash. @packages = PACKAGES - # Now replace each filepath with a hash containing the filepath and contents. + # Replace each filepath with a hash containing the filepath and contents. @packages.each_key do |k| @packages[k][:files].map! do |f| contents = File.read(File.join(options[:src_dir], f)) diff --git a/spec/lib/components/one_wire/ds18b20_spec.rb b/spec/lib/components/one_wire/ds18b20_spec.rb index 0be629a7..277d7e94 100644 --- a/spec/lib/components/one_wire/ds18b20_spec.rb +++ b/spec/lib/components/one_wire/ds18b20_spec.rb @@ -5,7 +5,7 @@ module Components module OneWire describe DS18B20 do include BoardMock - let(:bus) { double.as_null_object } + let(:bus) { double(mutex: Mutex.new).as_null_object } subject { DS18B20.new(bus: bus, address: 0xFFFFFFFFFFFFFFFF) } describe '#decode_temp' do @@ -20,6 +20,46 @@ module OneWire .to eq(celsius: -55, farenheit: -67) end end + + describe '#decode_resolution' do + it 'should get the resolution from the entire scratchpad' do + expect(subject.decode_resolution([0,0,0,0,0b01100000])).to eq(12) + expect(subject.decode_resolution([0,0,0,0,0b01000000])).to eq(11) + expect(subject.decode_resolution([0,0,0,0,0b00100000])).to eq(10) + expect(subject.decode_resolution([0,0,0,0,0b00000000])).to eq(9) + end + end + + describe '#convert' do + it 'should be atomic' do + expect(subject).to receive(:atomically).exactly(1).times + subject.convert + end + + it 'should match first' do + expect(subject).to receive(:atomically).exactly(1).times + subject.convert + end + + it 'should send the command' do + expect(bus).to receive(:write).with(0x44) + subject.convert + end + + it 'should set the sleep time to default on first conversion' do + subject.convert + expect(subject.instance_variable_get(:@convert_time)).to eq(0.75) + end + + it 'should sleep for the conversion time' do + subject.convert + expect(subject).to receive(:sleep).with(subject.instance_variable_get(:@convert_time)) + subject.convert + end + + it 'should sleep inside the mutex lock if parasite power in use' + it 'should sleep outside the mutex lock if parasite power not in use' + end end end end diff --git a/spec/lib/components/one_wire/slave_spec.rb b/spec/lib/components/one_wire/slave_spec.rb new file mode 100644 index 00000000..aa96c5fa --- /dev/null +++ b/spec/lib/components/one_wire/slave_spec.rb @@ -0,0 +1,125 @@ +require 'spec_helper' + +module Dino + module Components + module OneWire + describe Slave do + include BoardMock + let(:bus) { double(mutex: Mutex.new).as_null_object } + subject { Slave.new(bus: bus, address: 0xFFFFFFFFFFFFFFFF) } + + describe '#after_initialize' do + it 'should require an address' do + expect { Slave.new(bus: bus) }.to raise_error + end + end + + describe '#atomically' do + it 'should happen inside the mutex lock' do + expect(bus.mutex).to receive(:synchronize) + subject.atomically {} + end + + it 'should take a block and call it exactly once' do + block = Proc.new {} + expect(block).to receive(:call).exactly(1).times + subject.atomically(&block) + end + end + + describe '#match' do + it 'should NOT be atomic' do + expect(subject).to receive(:atomically).exactly(0).times + subject.match + end + + it 'should reset the bus' do + expect(bus).to receive(:reset) + subject.match + end + + it 'should skip rom if it is the only device on the bus' do + expect(bus).to receive(:write).with(0xCC) + subject.match + end + + it 'should try to match ROM, then send its ROM if not alone on the bus' do + # Just needs to be an array of anything > 1. + bus.stub(:found_devices) { [1,2] } + expect(bus).to receive(:write).with(0x55) + expect(bus).to receive(:write).with([255,255,255,255,255,255,255,255]) + device = Slave.new(bus: bus, address: 0xFFFFFFFFFFFFFFFF) + device.match + end + end + + describe '#copy_scratch' do + it 'should be atomic' do + expect(subject).to receive(:atomically).exactly(1).times + subject.copy_scratch + end + + it 'should call #match' do + expect(subject).to receive(:match).exactly(1).times + subject.copy_scratch + end + + it 'should send the command' do + expect(bus).to receive(:write).with(0x48) + subject.copy_scratch + end + + it 'should reset the bus after if parasite power is in use' do + bus.stub(:parasite_power) { true } + expect(bus).to receive(:reset).exactly(2).times + subject.copy_scratch + end + end + + describe '#read_scratch' do + it 'should be atomic' do + expect(subject).to receive(:atomically).exactly(1).times + subject.read_scratch(9) + end + + it 'should call #match' do + expect(subject).to receive(:match).exactly(1).times + subject.read_scratch(9) + end + + it 'should send the command' do + expect(bus).to receive(:write).with(0xBE) + subject.read_scratch(9) + end + + it 'should read the right number of bytes' do + expect(bus).to receive(:read).with(9) + subject.read_scratch(9) + end + end + + describe '#write_scratch' do + it 'should be atomic' do + expect(subject).to receive(:atomically).exactly(1).times + subject.write_scratch([1]) + end + + it 'should call #match' do + expect(subject).to receive(:match).exactly(1).times + subject.write_scratch(1) + end + + it 'should send the command' do + expect(bus).to receive(:write).with(0x4E) + subject.write_scratch(1) + end + + it 'should write the data' do + expect(bus).to receive(:write).with([1,2,3]) + subject.write_scratch(1,2,3) + end + end + end + end + end +end From 1694632863700cc5c1c014395336527a11f686b6 Mon Sep 17 00:00:00 2001 From: vickash Date: Thu, 15 Feb 2018 11:57:00 -0400 Subject: [PATCH 173/296] More 1-Wire refactoring --- lib/dino/components/one_wire/ds18b20.rb | 54 +++++++++---------------- lib/dino/components/one_wire/slave.rb | 31 +++++++++----- 2 files changed, 39 insertions(+), 46 deletions(-) diff --git a/lib/dino/components/one_wire/ds18b20.rb b/lib/dino/components/one_wire/ds18b20.rb index 6fc8196c..cedac9d0 100644 --- a/lib/dino/components/one_wire/ds18b20.rb +++ b/lib/dino/components/one_wire/ds18b20.rb @@ -2,28 +2,28 @@ module Dino module Components module OneWire class DS18B20 < Slave - FAMILY_CODE = 0x28 + def scratch + @state ? @state[:raw] : read_scratch(9) + end + def resolution - @resolution ||= decode_resolution read_scratch(9) + @resolution ||= decode_resolution(scratch) end def resolution=(bits) - raise ArgumentError 'Invalid DS18B20 resolution' if (bits > 12 || bits < 9) - - @resolution = bits - scratch = read_scratch(9).split(",").map(&:to_i) - - unless decode_resolution(scratch) == @resolution - settings = scratch[2..4] - offset = @resolution - 9 - settings[2] = 0b00011111 | (offset << 5) - write_scratch(settings) - copy_scratch + unless (9..12).include?(bits) + raise ArgumentError, 'Invalid DS18B20 resolution, expected 9 to 12' end - set_convert_time + return bits if decode_resolution(scratch) == bits + + eeprom = scratch[2..4] + eeprom[2] = 0b00011111 | ((bits - 9) << 5) + write_scratch(eeprom) + copy_scratch + @resolution = bits end def set_convert_time @@ -44,14 +44,11 @@ def convert def _read convert - read_scratch(9) + read_scratch(9) { |data| self.update(data) } end - def pre_callback_filter(data) - # Data is 9 comma delimited numbers in ASCII, representing bytes. - bytes = data.split(",").map{ |b| b.to_i } - return {crc_error: true} unless Helper.crc_check(bytes) - + def pre_callback_filter(bytes) + return { crc_error: true } unless Helper.crc_check(bytes) @resolution = decode_resolution(bytes) decode_temp(bytes).merge(raw: bytes) @@ -59,23 +56,10 @@ def pre_callback_filter(data) # # Temperature is the first 16 bits (2 bytes of 9 read), little-endian. - # It's a sign-extended two's complement 12-bit decimal, where LSB is the - # 2^-4 exponent, up through 2^6 for next 10 bits. 5 MSBs repeat the sign. + # It's a signed, 2's complement, little-endian decimal with 2^-4 exp. # def decode_temp(bytes) - magnitude = bytes[1] << 8 | bytes[0] - negative = (magnitude[15] == 1) - magnitude = (magnitude ^ 0xFFFF) + 1 if negative - - celsius = 0.0 - exp = -4 - for bit in (0..10) - if (magnitude[bit] == 1) - celsius = celsius + (2.0 ** (exp+bit)) - end - end - - celsius = -celsius if negative + celsius = bytes[0..1].pack('C*').unpack('s<')[0] * (2.0 ** -4) farenheit = (celsius * 1.8 + 32).round(4) {celsius: celsius, farenheit: farenheit} diff --git a/lib/dino/components/one_wire/slave.rb b/lib/dino/components/one_wire/slave.rb index 3e170be1..bceef808 100644 --- a/lib/dino/components/one_wire/slave.rb +++ b/lib/dino/components/one_wire/slave.rb @@ -14,19 +14,26 @@ def initialize(options={}) def after_initialize(options={}) super(options) + unless options[:address] - raise ArgumentError, 'missing 1-Wire slave ROM address; try searching first' + raise ArgumentError, + 'missing 1-Wire slave ROM address; try Bus#search first' end @address = options[:address] end - def read_scratch(num_bytes) + def read_scratch(num_bytes, &block) atomically do - # Bubble a single bus callback up to self, bound by this lock. - bus.add_callback(:read) { |data| self.update(data) } + # Bubble bus callback while still in the lock. + if block_given? + bus.add_callback(:read) do |data| + block.call data.split(",").map(&:to_i) + end + end + match bus.write(READ_SCRATCH) - bus.read(num_bytes) + bus.read(num_bytes).split(",").map(&:to_i) end end @@ -42,7 +49,7 @@ def copy_scratch atomically do match bus.write(COPY_SCRATCH) - sleep 0.02 + sleep 0.05 bus.reset if bus.parasite_power end end @@ -59,19 +66,21 @@ def match bus.write(SKIP_ROM) else bus.write(MATCH_ROM) - bus.write(Helper.address_to_bytes(self.address)) + bus.write(address_bytes) end end + def address_bytes + Helper.address_to_bytes(self.address) + end + def serial_number @serial_number ||= extract_serial end def extract_serial - # Remove CRC. - address = @address & 0x00FFFFFFFFFFFFFF - # Remove family code. - address = address >> 8 + # Remove CRC & family code. + address = (@address & 0x00FFFFFFFFFFFFFF) >> 8 address.to_s(16).rjust(12, "0") end end From 5c9ff4c26d76b5e2136cfe8d7566698d6a3de7b2 Mon Sep 17 00:00:00 2001 From: vickash Date: Thu, 15 Feb 2018 16:49:14 -0400 Subject: [PATCH 174/296] Default SPI access to 2-way, listeners read only. Selecting MSBFIRST or LSBFIRST bit order now works. --- lib/dino/api/helper.rb | 3 +- lib/dino/api/spi.rb | 55 ++++++++----- lib/dino/components/register/input.rb | 2 +- lib/dino/components/register/spi_in.rb | 4 +- lib/dino/components/register/spi_out.rb | 2 +- src/lib/Dino.cpp | 7 +- src/lib/Dino.h | 9 +-- src/lib/DinoSPI.cpp | 103 ++++++++++++------------ 8 files changed, 99 insertions(+), 86 deletions(-) diff --git a/lib/dino/api/helper.rb b/lib/dino/api/helper.rb index 982d66c7..a534fabd 100644 --- a/lib/dino/api/helper.rb +++ b/lib/dino/api/helper.rb @@ -34,8 +34,7 @@ def pack(type, data, options={}) end end - # Minimum 1 whether or not :min is given as an option. - if str.length < (options[:min] || 1) + if options[:min] && str.length < options[:min] raise ArgumentError, "too few bytes given (expected at least #{options[:min]})" end diff --git a/lib/dino/api/spi.rb b/lib/dino/api/spi.rb index 7aadeecb..430f0ac3 100644 --- a/lib/dino/api/spi.rb +++ b/lib/dino/api/spi.rb @@ -3,36 +3,53 @@ module API module SPI include Helper - def spi_settings(mode, frequency) - pack(:uint8, [0, mode]) + pack(:uint32, [frequency]) + def spi_header(options) + options[:mode] ||= 0 + options[:frequency] ||= 3000000 + options[:bit_order] ||= :lsbfirst + raise ArgumentError, + 'invalid SPI mode' unless (0..3).include? options[:mode] + + # Flag unused high bit of mode if we need to transfer MSBFIRST. + settings = options[:mode] + settings = settings | 0b10000000 if options[:bit_order] == :msbfirst + + uint8 = pack(:uint8, [settings, options[:read], options[:write].length]) + uint8 + pack(:uint32, options[:frequency]) end - # Listener can store up to 8 bytes to get written each time, read up to 256. - def spi_write(pin, mode, frequency, bytes) - settings = spi_settings(mode, frequency) - bytes = pack(:uint8, bytes) + # CMD = 26 + def spi_transfer(pin, options={}) + options[:read] ||= 0 + if options[:write] + options[:write] = [options[:write]].flatten + else + options[:write] = [] + end + + return if (options[:read] == 0) && (options[:write].empty?) + + header = spi_header(options) write Message.encode command: 26, pin: pin, - value: bytes.length, - aux_message: settings + bytes + aux_message: header + pack(:uint8, options[:write]) end - def spi_read(pin, mode, frequency, num_bytes) - write Message.encode command: 27, - pin: pin, - value: num_bytes, - aux_message: spi_settings(mode, frequency) - end + # CMD = 27 + def spi_listen(pin, options={}) + raise ArgumentError, + 'no SPI bytes to read' unless (options[:read] > 0) + options[:write] = [] - def spi_listen(pin, mode, frequency, num_bytes) - write Message.encode command: 28, + header = spi_header(options) + write Message.encode command: 27, pin: pin, - value: num_bytes, - aux_message: spi_settings(mode, frequency) + aux_message: header end + # CMD = 28 def spi_stop(pin) - write Message.encode command: 29, pin: pin + write Message.encode command: 28, pin: pin end end end diff --git a/lib/dino/components/register/input.rb b/lib/dino/components/register/input.rb index 58e22496..bd92a8c2 100644 --- a/lib/dino/components/register/input.rb +++ b/lib/dino/components/register/input.rb @@ -91,7 +91,7 @@ def digital_read(pin) read unless any_listening end - def digital_listen(pin) + def digital_listen(pin, divider) listen unless any_listening @listening_pins[pin] = true end diff --git a/lib/dino/components/register/spi_in.rb b/lib/dino/components/register/spi_in.rb index fa4836e5..3bcfc41b 100644 --- a/lib/dino/components/register/spi_in.rb +++ b/lib/dino/components/register/spi_in.rb @@ -23,11 +23,11 @@ def after_initialize(options={}) end def read - board.spi_read(pin, spi_mode, frequency, @bytes) + board.spi_transfer(pin, mode: spi_mode, frequency: frequency, read: @bytes) end def listen - board.spi_listen(pin, spi_mode, frequency, @bytes) + board.spi_listen(pin, mode: spi_mode, frequency: frequency, read: @bytes) end def stop diff --git a/lib/dino/components/register/spi_out.rb b/lib/dino/components/register/spi_out.rb index 292776d0..727c7a58 100644 --- a/lib/dino/components/register/spi_out.rb +++ b/lib/dino/components/register/spi_out.rb @@ -23,7 +23,7 @@ def after_initialize(options={}) end def write(*bytes) - board.spi_write(pin, spi_mode, frequency, bytes.flatten) + board.spi_transfer(pin, mode: spi_mode, frequency: frequency, write: bytes.flatten) end end end diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 13bc8915..4bd9c0e5 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -150,10 +150,9 @@ void Dino::process() { // Implemented in DinoSPI.cpp #ifdef DINO_SPI - case 26: spiWrite (pin, val, auxMsg[0], (uint32_t)auxMsg[1], &auxMsg[5]); break; - case 27: spiRead (pin, val, auxMsg[0], (uint32_t)auxMsg[1] ); break; - case 28: addSpiListener (pin, val, auxMsg[0], (uint32_t)auxMsg[1]); break; - case 29: removeSpiListener(); break; + case 26: spiTransfer (pin, auxMsg[0], auxMsg[1], auxMsg[2], (uint32_t)auxMsg[3], &auxMsg[7]); break; + case 27: addSpiListener (pin, auxMsg[0], auxMsg[1], 0, (uint32_t)auxMsg[3]); break; + case 28: removeSpiListener(); break; #endif // Implemented in DinoI2C.cpp diff --git a/src/lib/Dino.h b/src/lib/Dino.h index 469e34de..8361243e 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -86,12 +86,11 @@ class Dino { void clearShiftListeners (); // SPI - void spiBegin (byte spiMode, uint32_t clockRate); + void spiBegin (byte settings, uint32_t clockRate); void spiEnd (); - void spiWrite (int selectPin, int len, byte spiMode, uint32_t clockRate, byte *data); //cmd = 26 - void spiRead (int selectPin, int len, byte spiMode, uint32_t clockRate); //cmd = 27 - void addSpiListener (int selectPin, int len, byte spiMode, uint32_t clockRate); //cmd = 28 - void removeSpiListener (); //cmd = 29 + void spiTransfer (int selectPin, byte settings, byte rLength, byte wLength, uint32_t clockRate, byte *data); //cmd = 26 + void addSpiListener (int selectPin, byte settings, byte rLength, byte wLength, uint32_t clockRate); //cmd = 27 + void removeSpiListener (); //cmd = 28 void updateSpiListeners (); void clearSpiListeners (); diff --git a/src/lib/DinoSPI.cpp b/src/lib/DinoSPI.cpp index a5669ac2..877df7bb 100644 --- a/src/lib/DinoSPI.cpp +++ b/src/lib/DinoSPI.cpp @@ -10,8 +10,8 @@ #define SPI_LISTENER_COUNT 4 struct SpiListener{ byte selectPin; + byte settings; byte len; - byte spiMode; uint32_t clockRate; boolean enabled; }; @@ -19,15 +19,20 @@ SpiListener spiListeners[SPI_LISTENER_COUNT]; // Convenience wrapper for SPI.begin -void Dino::spiBegin(byte spiMode, uint32_t clockRate){ +void Dino::spiBegin(byte settings, uint32_t clockRate){ SPI.begin(); - // Should make LSB/MSB optional. - switch(spiMode) { - case 0: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE0)); break; - case 1: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE1)); break; - case 2: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE2)); break; - case 3: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE3)); break; + + byte bitOrder; + if bitRead(settings, 7) { + bitOrder = MSBFIRST; + } else { + bitOrder = LSBFIRST; } + + byte mode = settings; + bitClear(mode, 7); + + SPI.beginTransaction(SPISettings(clockRate, bitOrder, mode)); } @@ -43,52 +48,43 @@ void Dino::spiEnd(){ // -// Request format for single direction SPI API functions. +// Request format for SPI 2-way transfers // pin = slave select pin (int) -// val = length (int) -// auxMsg[0] = SPI mode (byte) -// auxMsg[1-4] = clock frequency (uint32_t as 4 bytes) -// auxMsg[5]+ = data (bytes) (write func only) +// val = empty +// auxMsg[0] = SPI settings. 2 LSB = SPI mode. Bit 7 = MSB(1) / LSB(0). +// auxMsg[1] = write length (number of bytes) +// auxMsg[2] = read length (number of bytes) +// auxMsg[3-6] = clock frequency (uint32_t as 4 bytes) +// +// auxMsg[7]+ = data (bytes) (write only) // // CMD = 26 // Write to an SPI device. -void Dino::spiWrite(int selectPin, int len, byte spiMode, uint32_t clockRate, byte *data) { - spiBegin(spiMode, clockRate); +void Dino::spiTransfer(int selectPin, byte settings, byte rLength, byte wLength, uint32_t clockRate, byte *data) { - // Select the device. + spiBegin(settings, clockRate); digitalWrite(selectPin, LOW); - // Write one byte at a time. - for (uint8_t i = 0; i < len; i++) { - SPI.transfer(data[i]); + if (rLength > 0) { + // Stream read bytes as if coming from select pin for easy identification. + stream->print(selectPin); + stream->print(':'); } - spiEnd(); - // Leave select high. - digitalWrite(selectPin, HIGH); -} - - -// CMD = 27 -// Read from an SPI device. -void Dino::spiRead(int selectPin, int len, byte spiMode, uint32_t clockRate) { - spiBegin(spiMode, clockRate); + for (byte i = 0; (i < rLength || i < wLength); i++) { + byte b; - // Select the device. - digitalWrite(selectPin, LOW); - - // Send data as if coming from the slave select pin so it's easy to identify. - // Start with just pin number and : for now. - stream->print(selectPin); - stream->print(':'); - - for (int i = 1; i <= len; i++) { - // Read a single byte from the register. - byte reading = SPI.transfer(0x00); + if (i < wLength) { + b = SPI.transfer(data[i]); + } else { + b = SPI.transfer(0x00); + } - // Print it, then a comma or \n if it's the last byte. - stream->print(reading); - stream->print((i==len) ? '\n' : ','); + if (i < rLength) { + // Print read byte, then a comma or \n if it's the last read byte. + stream->print(b); + stream->print((i+1 == rLength) ? '\n' : ','); + } } spiEnd(); @@ -96,16 +92,17 @@ void Dino::spiRead(int selectPin, int len, byte spiMode, uint32_t clockRate) { digitalWrite(selectPin, HIGH); } -// CMD = 28 + +// CMD = 27 // Start listening to an SPI register. // Overwrite the first disabled listener in the struct array. -void Dino::addSpiListener(int selectPin, int len, byte spiMode, uint32_t clockRate) { +void Dino::addSpiListener(int selectPin, byte settings, byte rLength, byte wLength, uint32_t clockRate) { for (int i = 0; i < SPI_LISTENER_COUNT; i++) { if (spiListeners[i].enabled == false) { spiListeners[i] = { selectPin, - len, - spiMode, + settings, + rLength, clockRate, true }; @@ -116,7 +113,7 @@ void Dino::addSpiListener(int selectPin, int len, byte spiMode, uint32_t clockRa } } -// CMD = 29 +// CMD = 28 // Send a number for a select pin to remove an SPI register listener. void Dino::removeSpiListener(){ for (int i = 0; i < SPI_LISTENER_COUNT; i++) { @@ -131,10 +128,12 @@ void Dino::removeSpiListener(){ void Dino::updateSpiListeners(){ for (int i = 0; i < SPI_LISTENER_COUNT; i++) { if (spiListeners[i].enabled) { - spiRead(spiListeners[i].selectPin, - spiListeners[i].len, - spiListeners[i].spiMode, - spiListeners[i].clockRate); + spiTransfer(spiListeners[i].selectPin, + spiListeners[i].settings, + spiListeners[i].len, + 0, + spiListeners[i].clockRate, + {}); } } } From ba1f936dfc684ca30f210818936c015bf219057b Mon Sep 17 00:00:00 2001 From: vickash Date: Thu, 15 Feb 2018 18:32:51 -0400 Subject: [PATCH 175/296] Minor cleanup of C functionss for shift and SPI listen. --- src/lib/Dino.cpp | 8 ++++---- src/lib/Dino.h | 4 ++-- src/lib/DinoSPI.cpp | 19 ++++++------------- src/lib/DinoShift.cpp | 14 +++++++------- 4 files changed, 19 insertions(+), 26 deletions(-) diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 4bd9c0e5..f53e7480 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -144,15 +144,15 @@ void Dino::process() { #ifdef DINO_SHIFT case 21: shiftWrite (pin, val, auxMsg[0], auxMsg[1], &auxMsg[3]); break; case 22: shiftRead (pin, val, auxMsg[0], auxMsg[1], auxMsg[2]); break; - case 23: addShiftListener (pin, val, auxMsg[0], auxMsg[1], auxMsg[2]); break; - case 24: removeShiftListener (); break; + case 23: addShiftListener (); break; + case 24: removeShiftListener (); break; #endif // Implemented in DinoSPI.cpp #ifdef DINO_SPI case 26: spiTransfer (pin, auxMsg[0], auxMsg[1], auxMsg[2], (uint32_t)auxMsg[3], &auxMsg[7]); break; - case 27: addSpiListener (pin, auxMsg[0], auxMsg[1], 0, (uint32_t)auxMsg[3]); break; - case 28: removeSpiListener(); break; + case 27: addSpiListener (); break; + case 28: removeSpiListener(); break; #endif // Implemented in DinoI2C.cpp diff --git a/src/lib/Dino.h b/src/lib/Dino.h index 8361243e..29d965f2 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -80,7 +80,7 @@ class Dino { // Shift Registers void shiftWrite (int latchPin, int len, byte dataPin, byte clockPin, byte *data); //cmd = 21 void shiftRead (int latchPin, int len, byte dataPin, byte clockPin, byte clockHighFirst); //cmd = 22 - void addShiftListener (int latchPin, int len, byte dataPin, byte clockPin, byte clockHighFirst); //cmd = 23 + void addShiftListener (); //cmd = 23 void removeShiftListener (); //cmd = 24 void updateShiftListeners (); void clearShiftListeners (); @@ -89,7 +89,7 @@ class Dino { void spiBegin (byte settings, uint32_t clockRate); void spiEnd (); void spiTransfer (int selectPin, byte settings, byte rLength, byte wLength, uint32_t clockRate, byte *data); //cmd = 26 - void addSpiListener (int selectPin, byte settings, byte rLength, byte wLength, uint32_t clockRate); //cmd = 27 + void addSpiListener (); //cmd = 27 void removeSpiListener (); //cmd = 28 void updateSpiListeners (); void clearSpiListeners (); diff --git a/src/lib/DinoSPI.cpp b/src/lib/DinoSPI.cpp index 877df7bb..fe62aa0b 100644 --- a/src/lib/DinoSPI.cpp +++ b/src/lib/DinoSPI.cpp @@ -17,7 +17,6 @@ struct SpiListener{ }; SpiListener spiListeners[SPI_LISTENER_COUNT]; - // Convenience wrapper for SPI.begin void Dino::spiBegin(byte settings, uint32_t clockRate){ SPI.begin(); @@ -35,7 +34,6 @@ void Dino::spiBegin(byte settings, uint32_t clockRate){ SPI.beginTransaction(SPISettings(clockRate, bitOrder, mode)); } - // Convenience wrapper for SPI.end void Dino::spiEnd(){ SPI.endTransaction(); @@ -46,7 +44,6 @@ void Dino::spiEnd(){ #endif } - // // Request format for SPI 2-way transfers // pin = slave select pin (int) @@ -92,18 +89,17 @@ void Dino::spiTransfer(int selectPin, byte settings, byte rLength, byte wLength, digitalWrite(selectPin, HIGH); } - // CMD = 27 // Start listening to an SPI register. -// Overwrite the first disabled listener in the struct array. -void Dino::addSpiListener(int selectPin, byte settings, byte rLength, byte wLength, uint32_t clockRate) { +void Dino::addSpiListener() { for (int i = 0; i < SPI_LISTENER_COUNT; i++) { + // Overwrite the first disabled listener in the struct array. if (spiListeners[i].enabled == false) { spiListeners[i] = { - selectPin, - settings, - rLength, - clockRate, + pin, + auxMsg[0], + auxMsg[1], + (uint32_t)auxMsg[3], true }; return; @@ -123,7 +119,6 @@ void Dino::removeSpiListener(){ } } - // Gets called by Dino::updateListeners to run listeners in the main loop. void Dino::updateSpiListeners(){ for (int i = 0; i < SPI_LISTENER_COUNT; i++) { @@ -138,10 +133,8 @@ void Dino::updateSpiListeners(){ } } - // Gets called by Dino::reset to clear all listeners. void Dino::clearSpiListeners(){ for (int i = 0; i < SPI_LISTENER_COUNT; i++) spiListeners[i].enabled = false; } - #endif diff --git a/src/lib/DinoShift.cpp b/src/lib/DinoShift.cpp index a3945dc0..1f7d33da 100644 --- a/src/lib/DinoShift.cpp +++ b/src/lib/DinoShift.cpp @@ -75,16 +75,16 @@ void Dino::shiftRead(int latchPin, int len, byte dataPin, byte clockPin, byte cl // CMD = 23 // Start listening to a register using the Arduino shiftIn function. -// Overwrite the first disabled listener in the struct array. -void Dino::addShiftListener(int latchPin, int len, byte dataPin, byte clockPin, byte clockHighFirst) { +void Dino::addShiftListener() { for (int i = 0; i < SHIFT_LISTENER_COUNT; i++) { + // Overwrite the first disabled listener in the struct array. if (shiftListeners[i].enabled == false) { shiftListeners[i] = { - latchPin, - len, - dataPin, - clockPin, - clockHighFirst, + pin, + val, + auxMsg[0], + auxMsg[1], + auxMsg[2], true }; return; From 428e592f1ca6abbd0ddf411f699f5adcbfb4d621 Mon Sep 17 00:00:00 2001 From: vickash Date: Fri, 16 Feb 2018 10:32:06 -0400 Subject: [PATCH 176/296] Revert I2C to built in Wire library for compatibility. Added Ruby I2C interface. I2C works on ESP8266 and ARM chips now. Minor fixes to SPI and package manager for ARM to compile. Removed external I2C library. --- .gitmodules | 3 - lib/dino/api.rb | 1 + lib/dino/api/i2c.rb | 28 ++++ lib/dino/board.rb | 1 + .../components/one_wire/bus_enumerator.rb | 7 +- lib/dino_cli/generator.rb | 2 +- lib/dino_cli/packages.rb | 4 +- lib/dino_cli/targets.rb | 12 +- src/lib/Dino.cpp | 4 +- src/lib/Dino.h | 3 +- src/lib/DinoI2C.cpp | 122 +++++++----------- src/lib/DinoSPI.cpp | 15 +-- src/vendor/I2C-Master-Library | 1 - 13 files changed, 96 insertions(+), 107 deletions(-) create mode 100644 lib/dino/api/i2c.rb delete mode 160000 src/vendor/I2C-Master-Library diff --git a/.gitmodules b/.gitmodules index bb452163..9ae081fb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "src/vendor/Arduino-IRremote"] path = src/vendor/Arduino-IRremote url = https://github.com/z3t0/Arduino-IRremote.git -[submodule "src/vendor/I2C-Master-Library"] - path = src/vendor/I2C-Master-Library - url = https://github.com/vickash/I2C-Master-Library.git [submodule "src/vendor/arduino-DHT"] path = src/vendor/arduino-DHT url = https://github.com/markruys/arduino-DHT.git diff --git a/lib/dino/api.rb b/lib/dino/api.rb index 61fac8ff..8d10255a 100644 --- a/lib/dino/api.rb +++ b/lib/dino/api.rb @@ -3,6 +3,7 @@ module API require 'dino/api/helper' require 'dino/api/core' require 'dino/api/dht' + require 'dino/api/i2c' require 'dino/api/infrared' require 'dino/api/one_wire' require 'dino/api/servo' diff --git a/lib/dino/api/i2c.rb b/lib/dino/api/i2c.rb new file mode 100644 index 00000000..ac1ff66f --- /dev/null +++ b/lib/dino/api/i2c.rb @@ -0,0 +1,28 @@ +module Dino + module API + module I2C + include Helper + + # CMD = 33 + def i2c_search + write Message.encode command: 33 + end + + # CMD = 34 + def i2c_write(slave_address, data=[], options={}) + aux = pack :uint8, [slave_address, data.length, data].flatten + write Message.encode command: 34, + value: options[:repeated_start] ? 1 : 0, + aux_message: aux + end + + # CMD = 35 + def i2c_read(slave_address, register_start, num_bytes, options={}) + aux = pack :uint8, [slave_address, register_start, num_bytes] + write Message.encode command: 35, + value: options[:repeated_start] ? 1 : 0, + aux_message: aux + end + end + end +end diff --git a/lib/dino/board.rb b/lib/dino/board.rb index d07deaec..a474b4d7 100644 --- a/lib/dino/board.rb +++ b/lib/dino/board.rb @@ -1,6 +1,7 @@ module Dino class Board include API::Core + include API::I2C include API::Servo include API::ShiftIO include API::SPI diff --git a/lib/dino/components/one_wire/bus_enumerator.rb b/lib/dino/components/one_wire/bus_enumerator.rb index 2d7eb45a..30ff5560 100644 --- a/lib/dino/components/one_wire/bus_enumerator.rb +++ b/lib/dino/components/one_wire/bus_enumerator.rb @@ -2,7 +2,6 @@ module Dino module Components module OneWire class Bus - # 1 bits in mask force discrepancies to 1. 0 leaves bit as read from bus. def _search(branch_mask) reset write(SEARCH_ROM) @@ -24,9 +23,9 @@ def search block_until_read # No unsearched discrepancies left. - break if high_discrepancy == - 1 + break if high_discrepancy == -1 - # Force highest new discrepancy to 1 on the next search. + # Force highest new discrepancy to be a 1 on the next search. # i.e. Go as deep as possible into each branch found then back out. # branch_mask = branch_mask | (2 ** high_discrepancy) @@ -52,8 +51,6 @@ def parse_search_result(result) # Gives 0 at every discrepancy we didn't write 1 for on this search. new_discrepancies = address ^ complement - # Newer rubies: - # high_discrepancy = new_discrepancies.bit_length - 1 high_discrepancy = -1 (0..63).each { |i| high_discrepancy = i if new_discrepancies[i] == 0 } diff --git a/lib/dino_cli/generator.rb b/lib/dino_cli/generator.rb index d2004536..0a3b1ba8 100644 --- a/lib/dino_cli/generator.rb +++ b/lib/dino_cli/generator.rb @@ -111,7 +111,7 @@ def write @packages.each_key do |k| # Check if the package should be included for this target. package = @packages[k] - targeted = !package[:target] || package[:target].include?(options[:target]) + targeted = !package[:only] || package[:only].include?(options[:target]) excluded = package[:exclude] && package[:exclude].include?(options[:target]) # Append source file basename to the output dir to get output file path. diff --git a/lib/dino_cli/packages.rb b/lib/dino_cli/packages.rb index fc486460..96d39de8 100644 --- a/lib/dino_cli/packages.rb +++ b/lib/dino_cli/packages.rb @@ -81,7 +81,7 @@ class DinoCLI::Generator ir_out_esp8266: { description: "Transmit infrared signals with the ESP8266", directive: "DINO_IR_OUT", - target: [:esp8266], + only: [:esp8266], files: [ "lib/DinoIROut.cpp", "vendor/IRremoteESP8266/src/IRremoteESP8266.h", @@ -103,8 +103,6 @@ class DinoCLI::Generator directive: "DINO_I2C", files: [ "lib/DinoI2C.cpp", - "vendor/I2C-Master-Library/I2C.h", - "vendor/I2C-Master-Library/I2C.cpp", ] } } diff --git a/lib/dino_cli/targets.rb b/lib/dino_cli/targets.rb index 65f4e167..043a2bd0 100644 --- a/lib/dino_cli/targets.rb +++ b/lib/dino_cli/targets.rb @@ -1,7 +1,11 @@ class DinoCLI::Generator + STANDARD_PACKAGES = PACKAGES.each_key.map do |package| + package unless PACKAGES[package][:only] + end.compact + TARGETS = { - # Default target always includes all packages. - mega: PACKAGES.each_key.to_a, + # Default target includes all standard package. + mega: STANDARD_PACKAGES, # Core is core. core: [:core], @@ -10,9 +14,9 @@ class DinoCLI::Generator mega168: [:core, :servo, :dht, :one_wire, :ir_out, :tone, :spi, :i2c], # ARM includes everytyhing except specific incompatibilities. - arm: PACKAGES.each_key.to_a - [:serial, :tone, :ir_out, :i2c], + arm: STANDARD_PACKAGES - [:serial, :tone, :ir_out], # A surprising amount "just works" on the ESP, notably not LCD. - esp8266: PACKAGES.each_key.to_a - [:lcd, :serial, :ir_out, :i2c] + [:ir_out_esp8266], + esp8266: STANDARD_PACKAGES - [:lcd, :serial, :ir_out] + [:ir_out_esp8266], } end diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index f53e7480..7cd17cbc 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -157,9 +157,7 @@ void Dino::process() { // Implemented in DinoI2C.cpp #ifdef DINO_I2C - case 31: i2cBegin (); break; - case 32: i2cEnd (); break; - case 33: i2cScan (); break; + case 33: i2cSearch (); break; case 34: i2cWrite (); break; case 35: i2cRead (); break; #endif diff --git a/src/lib/Dino.h b/src/lib/Dino.h index 29d965f2..a6c6e9bf 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -96,8 +96,7 @@ class Dino { // I2C void i2cBegin (); //cmd = 31 - void i2cEnd (); //cmd = 32 - void i2cScan (); //cmd = 33 + void i2cSearch (); //cmd = 33 void i2cWrite (); //cmd = 34 void i2cRead (); //cmd = 35 diff --git a/src/lib/DinoI2C.cpp b/src/lib/DinoI2C.cpp index 6052deba..8a475a8e 100644 --- a/src/lib/DinoI2C.cpp +++ b/src/lib/DinoI2C.cpp @@ -4,112 +4,82 @@ #include "Dino.h" #ifdef DINO_I2C -#include "I2C.h" +#include +bool i2cStarted = false; -// CMD = 31 -// Start I2C communication. +// Only start the Wire class if not already started. +// Lazy initialization in case user wants to use I2C pins for something else. void Dino::i2cBegin() { - I2c.begin(); - // I2c.setSpeed(??); - // I2c.pullup(??); - // I2c.timeOut(??); - - // This format could be better. - stream->print(SDA); stream->print(':'); - stream->print("I2C"); stream->print(':'); - stream->print('1'); stream->print('\n'); -} - - -// CMD = 32 -// Stop I2C communication. -void Dino::i2cEnd() { - I2c.end(); - - // This format could be better. - stream->print(SDA); stream->print(':'); - stream->print("I2C"); stream->print(':'); - stream->print('0'); stream->print('\n'); + if (!i2cStarted) { + Wire.begin(); + i2cStarted = true; + } } - // CMD = 33 -// Scan for I2C devices. -// -// WARNING: This takes a long time! Try to record the device addresses -// results and put them into your code. -// -// Returns each found address as if a separate reading from SDA pin, eg. "18:104". -// Returns 128 as if read from SDA pin for search complete, eg. "18:128". -// Returns 255 as if read from SDA pin for I2C errors, eg. "18:255". -// -void Dino::i2cScan() { - uint8_t address = 0; - while (address < 128) { - // Scan for the next device. - address = I2c.scanOne(address); - - // Write whatever we get including address space end or errors. - stream->print(SDA); stream->print(':'); - stream->print(address); stream->print('\n'); - - // Increment address before scanning again. - address++; +// Ask each address for a single byte to see if it exists on the bus. +void Dino::i2cSearch() { + stream->print(SDA); + + i2cBegin(); + for (byte i = 0x08; i < 0x78; i++) { + Wire.requestFrom(i, 1); + if (Wire.available()){ + stream->print(':'); stream->print(i); + while(Wire.available()) Wire.read(); + } } -} + stream->print('\n'); +} // CMD = 34 -// Write to an I2C device. -// All parameters need to be sent in binary in the auxMsg. +// Write to an I2C device. All params as binary in auxMsg. // +// val = repeated starts? // auxMsg[0] = device address -// auxMsg[1] = register start address -// auxMsg[2] = number of bytes -// auxMsg[3]+ = data +// auxMsg[1] = number of bytes +// auxMsg[2]+ = data // -// Limited to 255 bytes. Validate on remote end. +// Max 256 bytes. Validate remotely. // void Dino::i2cWrite() { - I2c.write(auxMsg[0], auxMsg[1], &auxMsg[3], auxMsg[2]); + i2cBegin(); + Wire.beginTransmission(auxMsg[0]); + Wire.write(&auxMsg[2], auxMsg[1]); + Wire.endTransmission(val); } - // CMD = 35 -// Read from an I2C device. -// All params need to be sent in binary in the auxMsg. +// Read from an I2C device. All params as binary in auxMsg. // +// val = repeated starts? // auxMsg[0] = device address -// auxMsg[1] = register start address +// auxMsg[1] = start register address // auxMsg[2] = number of bytes // -// Streams data back in comma delimited ASCII decimal for now, -// matching shiftRead and readSPI. Limited to 32 bytes by I2C library buffer. -// Validate on remote end. +// Max 32 bytes, limited by Wire library buffer. Validate remotely. // void Dino::i2cRead() { - // Force length to be min 1, max 32. if (auxMsg[2] > 32) auxMsg[2] = 32; - if (auxMsg[2] == 0) auxMsg[2] = 1; - - // Read all the bytes into the library buffer. - I2c.read(auxMsg[0], auxMsg[1], auxMsg[2]); - // Send back the SDA pin, the device address, and start register address first. - stream->print(SDA); stream->print(':'); - stream->print(auxMsg[0]); stream->print(':'); - stream->print(auxMsg[1]); stream->print(':'); + i2cBegin(); + Wire.beginTransmission(auxMsg[0]); + Wire.write(auxMsg[1]); + Wire.endTransmission(val); // Add repeated start option here. + Wire.requestFrom(auxMsg[0], auxMsg[2], val); - // Send back the data bytes. + // Send data as if coming from SDA pin. Implement a bus lock remotely. + // Fail silently if no bytes read / invalid device address. + if(Wire.available()){ + stream->print(SDA); stream->print(':'); + } uint8_t currentByte = 0; - while(I2c.available()){ + while(Wire.available()){ currentByte++; - - // Get a byte from the I2C buffer, print it, then comma or \n if last byte. - stream->print(I2c.receive()); + stream->print(Wire.read()); stream->print((currentByte == auxMsg[2]) ? '\n' : ','); } } - #endif diff --git a/src/lib/DinoSPI.cpp b/src/lib/DinoSPI.cpp index fe62aa0b..2a2292a4 100644 --- a/src/lib/DinoSPI.cpp +++ b/src/lib/DinoSPI.cpp @@ -21,17 +21,15 @@ SpiListener spiListeners[SPI_LISTENER_COUNT]; void Dino::spiBegin(byte settings, uint32_t clockRate){ SPI.begin(); - byte bitOrder; - if bitRead(settings, 7) { - bitOrder = MSBFIRST; - } else { - bitOrder = LSBFIRST; - } - + bool msbfirst = bitRead(settings, 7); byte mode = settings; bitClear(mode, 7); - SPI.beginTransaction(SPISettings(clockRate, bitOrder, mode)); + if (msbfirst) { + SPI.beginTransaction(SPISettings(clockRate, MSBFIRST, mode)); + } else { + SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, mode)); + } } // Convenience wrapper for SPI.end @@ -58,7 +56,6 @@ void Dino::spiEnd(){ // CMD = 26 // Write to an SPI device. void Dino::spiTransfer(int selectPin, byte settings, byte rLength, byte wLength, uint32_t clockRate, byte *data) { - spiBegin(settings, clockRate); digitalWrite(selectPin, LOW); diff --git a/src/vendor/I2C-Master-Library b/src/vendor/I2C-Master-Library deleted file mode 160000 index 32d3fd72..00000000 --- a/src/vendor/I2C-Master-Library +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 32d3fd7253e6b39d7674b843435fb9e4160a1619 From a755926d9965aaae4be46a8906f94e6cc1d7ab49 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 17 Feb 2018 11:14:48 -0400 Subject: [PATCH 177/296] Reader#read_using(method, &block). Other minor fixes. Call arbitrary methods/procs to send commands to the board, then block until a value is read, pre-filtered and returned, just like #read. --- lib/dino/api/core.rb | 4 +- lib/dino/api/i2c.rb | 14 ++++-- lib/dino/components/mixins.rb | 3 +- lib/dino/components/mixins/board_proxy.rb | 2 +- lib/dino/components/mixins/bus_master.rb | 26 ++++++++++ lib/dino/components/mixins/bus_slave.rb | 33 +++++++++++++ lib/dino/components/mixins/callbacks.rb | 29 +++-------- lib/dino/components/mixins/poller.rb | 6 ++- lib/dino/components/mixins/reader.rb | 9 +++- lib/dino/components/one_wire/bus.rb | 48 ++++++++++--------- .../components/one_wire/bus_enumerator.rb | 19 +++----- lib/dino/components/one_wire/ds18b20.rb | 5 +- lib/dino/components/one_wire/slave.rb | 35 ++------------ lib/dino/components/register/shift_in.rb | 1 - .../components/one_wire/enumerator_spec.rb | 2 +- src/lib/DinoI2C.cpp | 18 ++++--- src/lib/DinoOneWire.cpp | 2 +- 17 files changed, 143 insertions(+), 113 deletions(-) create mode 100644 lib/dino/components/mixins/bus_master.rb create mode 100644 lib/dino/components/mixins/bus_slave.rb diff --git a/lib/dino/api/core.rb b/lib/dino/api/core.rb index 8ef13a32..146e6fef 100644 --- a/lib/dino/api/core.rb +++ b/lib/dino/api/core.rb @@ -50,11 +50,9 @@ def set_listener(pin, state=:off, options={}) raise "Listener divider must be in #{DIVIDERS.inspect}" end - # Create a bit mask for the settings we want to use. Gets sent in value. - mask = 0 + mask = Math.log2(divider).to_i mask |= 0b10000000 if (state == :on) mask |= 0b01000000 if (mode == :analog) - mask |= Math.log2(divider).to_i write Message.encode(command: 7, pin: convert_pin(pin), value: mask) end diff --git a/lib/dino/api/i2c.rb b/lib/dino/api/i2c.rb index ac1ff66f..6815b157 100644 --- a/lib/dino/api/i2c.rb +++ b/lib/dino/api/i2c.rb @@ -9,18 +9,22 @@ def i2c_search end # CMD = 34 - def i2c_write(slave_address, data=[], options={}) - aux = pack :uint8, [slave_address, data.length, data].flatten + def i2c_write(address, bytes=[], options={}) + aux = pack :uint8, [address, bytes.length, bytes].flatten write Message.encode command: 34, value: options[:repeated_start] ? 1 : 0, aux_message: aux end # CMD = 35 - def i2c_read(slave_address, register_start, num_bytes, options={}) - aux = pack :uint8, [slave_address, register_start, num_bytes] + def i2c_read(address, register, num_bytes, options={}) + aux = pack :uint8, [address, register, num_bytes] + + settings = options[:repeated_start] ? 1 : 0 + settings = settings | 0b10 unless register + write Message.encode command: 35, - value: options[:repeated_start] ? 1 : 0, + value: settings, aux_message: aux end end diff --git a/lib/dino/components/mixins.rb b/lib/dino/components/mixins.rb index 9dfe8b26..7e8cfe53 100644 --- a/lib/dino/components/mixins.rb +++ b/lib/dino/components/mixins.rb @@ -6,7 +6,8 @@ module Mixins require 'dino/components/mixins/reader' require 'dino/components/mixins/poller' require 'dino/components/mixins/listener' - require 'dino/components/mixins/bus' + require 'dino/components/mixins/bus_master' + require 'dino/components/mixins/bus_slave' require 'dino/components/mixins/board_proxy' end end diff --git a/lib/dino/components/mixins/board_proxy.rb b/lib/dino/components/mixins/board_proxy.rb index 5b79590d..b18b6ab0 100644 --- a/lib/dino/components/mixins/board_proxy.rb +++ b/lib/dino/components/mixins/board_proxy.rb @@ -2,7 +2,7 @@ module Dino module Components module Mixins module BoardProxy - include Bus + include BusMaster def after_initialize(options={}) super(options) diff --git a/lib/dino/components/mixins/bus_master.rb b/lib/dino/components/mixins/bus_master.rb new file mode 100644 index 00000000..a94514bc --- /dev/null +++ b/lib/dino/components/mixins/bus_master.rb @@ -0,0 +1,26 @@ +module Dino + module Components + module Mixins + module BusMaster + attr_reader :mutex + + def after_initialize(options={}) + super(options) + @components = [] + @mutex = Mutex.new + end + + # Essential part of board interface that components need. + attr_reader :components + + def add_component(component) + @components << component + end + + def remove_component(component) + @components.delete(component) + end + end + end + end +end diff --git a/lib/dino/components/mixins/bus_slave.rb b/lib/dino/components/mixins/bus_slave.rb new file mode 100644 index 00000000..2cc5fc7c --- /dev/null +++ b/lib/dino/components/mixins/bus_slave.rb @@ -0,0 +1,33 @@ +module Dino + module Components + module Mixins + module BusSlave + include Setup::Base + + attr_reader :address + alias :bus :board + + def initialize(options={}) + options[:board] ||= options[:bus] + super(options) + end + + def after_initialize(options={}) + super(options) + + unless options[:address] + raise ArgumentError, + 'missing Slave device address; try Bus#search first' + end + @address = options[:address] + end + + def atomically(&block) + bus.mutex.synchronize do + block.call + end + end + end + end + end +end diff --git a/lib/dino/components/mixins/callbacks.rb b/lib/dino/components/mixins/callbacks.rb index 1893bb93..cfcbf51f 100644 --- a/lib/dino/components/mixins/callbacks.rb +++ b/lib/dino/components/mixins/callbacks.rb @@ -9,16 +9,16 @@ def after_initialize(options={}) end def add_callback(key=:persistent, &block) - @callback_mutex.synchronize { + @callback_mutex.synchronize do @callbacks[key] ||= [] @callbacks[key] << block - } + end end def remove_callback(key=nil) - @callback_mutex.synchronize { + @callback_mutex.synchronize do key ? @callbacks.delete(key) : @callbacks = {} - } + end end alias :on_data :add_callback @@ -36,28 +36,13 @@ def update(data) update_self(data) end - # - # Values received by #update are usually idempotent, i.e. the new state - # of the component, and can pass to callbacks as-is. But some components - # input a state change instead. Eg. rotary encoders with +1 or -1 steps. - # - # To maintain the pattern of callbacks receiving new state, while leaving - # old state in the instance variable for comparison, we may want to - # calculate the new state from a change input, and pass that instead. - # Override this method to do so. See RotaryEncoder class for an example. - # + # Override this to process data before passing to callbacks. def pre_callback_filter(data) data end - # - # Assign data to @state automatically after callbacks by default. - # - # Override this to add behavior not matching this pattern, such as - # components where data cannot be directly assigned to @state. - # Eg. data is a hash, and value from a specific key reflects @state. - # See RotaryEncoder class for an example. - # + # Set @state to the value passed to callbacks after running them all. + # Override if some other behavior is needed. def update_self(data) @state = data end diff --git a/lib/dino/components/mixins/poller.rb b/lib/dino/components/mixins/poller.rb index c90e622b..3af630aa 100644 --- a/lib/dino/components/mixins/poller.rb +++ b/lib/dino/components/mixins/poller.rb @@ -5,11 +5,13 @@ module Poller include Reader include Threaded - def poll(interval=1, *args, &block) + def poll(interval=1, &block) stop add_callback(:poll, &block) if block_given? + threaded_loop do - _read(*args); sleep interval + _read + sleep interval end end diff --git a/lib/dino/components/mixins/reader.rb b/lib/dino/components/mixins/reader.rb index 8749258c..ef332c9e 100644 --- a/lib/dino/components/mixins/reader.rb +++ b/lib/dino/components/mixins/reader.rb @@ -4,18 +4,23 @@ module Mixins module Reader include Callbacks - def read(*args, &block) + def read_using(method, &block) add_callback(:read, &block) if block_given? + # Block and catches read value for return, AFTER the pre-filter. value = nil add_callback(:read) { |data| value = data } - _read(*args) + method.call block_until_read value end + def read(&block) + read_using(self.method(:_read), &block) + end + def block_until_read loop { break if !@callbacks[:read] } end diff --git a/lib/dino/components/one_wire/bus.rb b/lib/dino/components/one_wire/bus.rb index 8ae0eef1..75ed0947 100644 --- a/lib/dino/components/one_wire/bus.rb +++ b/lib/dino/components/one_wire/bus.rb @@ -3,7 +3,7 @@ module Components module OneWire class Bus include Setup::SinglePin - include Mixins::Bus + include Mixins::BusMaster include Mixins::Reader attr_reader :found_devices, :parasite_power @@ -14,38 +14,40 @@ def after_initialize(options = {}) read_power_supply end - # Without driving low first, results are inconsistent. - # Only first (LS) bit matters, but we can only read whole bytes. - # def read_power_supply - board.set_pin_mode(self.pin, :out) - board.digital_write(self.pin, board.low) - sleep 0.1 - - reset - write(SKIP_ROM, READ_POWER_SUPPLY) - byte = read(1) - - @parasite_power = (byte.to_i[0] == 0) ? true : false + mutex.synchronize do + # Without driving low first, results are inconsistent. + board.set_pin_mode(self.pin, :out) + board.digital_write(self.pin, board.low) + sleep 0.1 + + reset + write(SKIP_ROM, READ_POWER_SUPPLY) + + # Only LSBIT matters, but we can only read whole bytes. + byte = read(1) + @parasite_power = (byte[0] == 0) ? true : false + end end - def reset - board.one_wire_reset(pin) + def pre_callback_filter(bytes) + bytes = bytes.split(",").map(&:to_i) + bytes.length > 1 ? bytes : bytes[0] end def device_present - present = false - self.add_callback(:read) do |result| - present = (result.to_i == 0) ? true : false + mutex.synchronize do + byte = read_using -> { reset(1) } + (byte == 0) ? true : false end + end - board.one_wire_reset(pin, 1) - block_until_read - present + def reset(get_presence=0) + board.one_wire_reset(pin, get_presence) end - def _read(num_bytes) - board.one_wire_read(pin, num_bytes) + def read(num_bytes) + read_using -> { board.one_wire_read(pin, num_bytes) } end def write(*bytes) diff --git a/lib/dino/components/one_wire/bus_enumerator.rb b/lib/dino/components/one_wire/bus_enumerator.rb index 30ff5560..92515bc5 100644 --- a/lib/dino/components/one_wire/bus_enumerator.rb +++ b/lib/dino/components/one_wire/bus_enumerator.rb @@ -14,14 +14,11 @@ def search high_discrepancy = 0 loop do - self.add_callback(:read) do |result| + read_using -> { _search(branch_mask) } do |result| device, high_discrepancy = parse_search_result(result) @found_devices << device end - _search(branch_mask) - block_until_read - # No unsearched discrepancies left. break if high_discrepancy == -1 @@ -60,18 +57,14 @@ def parse_search_result(result) [{class: klass, address: address}, high_discrepancy] end - # Search result is a comma separated list of 8 byte-pairs. Each pair is - # formatted "addressByte-compByte". Pairs are ordered LS byte first. - # - def split_search_result(str) - byte_pairs = str.split(",") + # Result is 16 bytes, 8 byte address and complement interleaved LSByte first. + def split_search_result(data) address = 0 complement = 0 - byte_pairs.reverse.each do |pair| - address_byte, complement_byte = pair.split("-").map(&:to_i) - address = (address << 8) | address_byte - complement = (complement << 8) | complement_byte + data.reverse.each_slice(2) do |comp_byte, addr_byte| + address = (address << 8) | addr_byte + complement = (complement << 8) | comp_byte end [address, complement] diff --git a/lib/dino/components/one_wire/ds18b20.rb b/lib/dino/components/one_wire/ds18b20.rb index cedac9d0..bcd1c35e 100644 --- a/lib/dino/components/one_wire/ds18b20.rb +++ b/lib/dino/components/one_wire/ds18b20.rb @@ -44,7 +44,10 @@ def convert def _read convert - read_scratch(9) { |data| self.update(data) } + data = read_scratch(9) + + self.update(data) + pre_callback_filter(data) end def pre_callback_filter(bytes) diff --git a/lib/dino/components/one_wire/slave.rb b/lib/dino/components/one_wire/slave.rb index bceef808..fbac3c06 100644 --- a/lib/dino/components/one_wire/slave.rb +++ b/lib/dino/components/one_wire/slave.rb @@ -2,38 +2,17 @@ module Dino module Components module OneWire class Slave - include Setup::Base + include Mixins::BusSlave include Mixins::Poller + attr_reader :address alias :bus :board - def initialize(options={}) - options[:board] ||= options[:bus] - super(options) - end - - def after_initialize(options={}) - super(options) - - unless options[:address] - raise ArgumentError, - 'missing 1-Wire slave ROM address; try Bus#search first' - end - @address = options[:address] - end - - def read_scratch(num_bytes, &block) + def read_scratch(num_bytes) atomically do - # Bubble bus callback while still in the lock. - if block_given? - bus.add_callback(:read) do |data| - block.call data.split(",").map(&:to_i) - end - end - match bus.write(READ_SCRATCH) - bus.read(num_bytes).split(",").map(&:to_i) + bus.read(num_bytes) end end @@ -54,12 +33,6 @@ def copy_scratch end end - def atomically(&block) - bus.mutex.synchronize do - block.call - end - end - def match bus.reset if bus.found_devices.count < 2 diff --git a/lib/dino/components/register/shift_in.rb b/lib/dino/components/register/shift_in.rb index 226106ab..deec557d 100644 --- a/lib/dino/components/register/shift_in.rb +++ b/lib/dino/components/register/shift_in.rb @@ -44,7 +44,6 @@ def read(num_bytes=@bytes) preclock_high: rising_clock end - # Untested def listen(num_bytes=@bytes) board.shift_listen latch.pin, data.pin, clock.pin, num_bytes, preclock_high: rising_clock diff --git a/spec/lib/components/one_wire/enumerator_spec.rb b/spec/lib/components/one_wire/enumerator_spec.rb index 14dc675b..2628a1c0 100644 --- a/spec/lib/components/one_wire/enumerator_spec.rb +++ b/spec/lib/components/one_wire/enumerator_spec.rb @@ -92,7 +92,7 @@ def search(branch_mask) end end - output_string << addr.to_s << "-" << comp.to_s + output_string << addr.to_s << "," << comp.to_s output_string << "," if (i != 7 ) # No \n. TxRx strips it IRL. end output_string diff --git a/src/lib/DinoI2C.cpp b/src/lib/DinoI2C.cpp index 8a475a8e..316f1fc8 100644 --- a/src/lib/DinoI2C.cpp +++ b/src/lib/DinoI2C.cpp @@ -63,17 +63,23 @@ void Dino::i2cWrite() { // void Dino::i2cRead() { if (auxMsg[2] > 32) auxMsg[2] = 32; - + byte repeatedStart = bitRead(val, 0); i2cBegin(); - Wire.beginTransmission(auxMsg[0]); - Wire.write(auxMsg[1]); - Wire.endTransmission(val); // Add repeated start option here. - Wire.requestFrom(auxMsg[0], auxMsg[2], val); - // Send data as if coming from SDA pin. Implement a bus lock remotely. + // Bit 1 of val is flag for whether to write register address first or not. + if (bitRead(val, 1)) { + Wire.beginTransmission(auxMsg[0]); + Wire.write(auxMsg[1]); + Wire.endTransmission(repeatedStart); + } + + Wire.requestFrom(auxMsg[0], auxMsg[2], repeatedStart); + + // Send data as if coming from SDA pin. Prefix with device adddress. // Fail silently if no bytes read / invalid device address. if(Wire.available()){ stream->print(SDA); stream->print(':'); + stream->print(auxMsg[0]); stream->print('-'); } uint8_t currentByte = 0; while(Wire.available()){ diff --git a/src/lib/DinoOneWire.cpp b/src/lib/DinoOneWire.cpp index 70a56aeb..d762b0c6 100644 --- a/src/lib/DinoOneWire.cpp +++ b/src/lib/DinoOneWire.cpp @@ -59,7 +59,7 @@ void Dino::owSearch(){ } } stream->print(addr); - stream->print('-'); + stream->print(','); stream->print(comp); stream->print((i == 7) ? '\n' : ','); } From 9bb37f8d01c869e4f2a63f2dc138bb00f073ae22 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 17 Feb 2018 11:16:07 -0400 Subject: [PATCH 178/296] =?UTF-8?q?Missing=20deletion=20from=20last=20comm?= =?UTF-8?q?it=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dino/components/mixins/bus.rb | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 lib/dino/components/mixins/bus.rb diff --git a/lib/dino/components/mixins/bus.rb b/lib/dino/components/mixins/bus.rb deleted file mode 100644 index d597958f..00000000 --- a/lib/dino/components/mixins/bus.rb +++ /dev/null @@ -1,30 +0,0 @@ -module Dino - module Components - module Mixins - module Bus - # - # Behavior mixin for any component that tracks multiple other components - # attached to it, matching Board, but without the full Board interface. - # See OneWire::Bus class for an example. - # - attr_reader :mutex - - def after_initialize(options={}) - super(options) - @components = [] - @mutex = Mutex.new - end - - attr_reader :components - - def add_component(component) - @components << component - end - - def remove_component(component) - @components.delete(component) - end - end - end - end -end From e6ccc348f8e30382760d9ddedab0cf8c2ca144d3 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 17 Feb 2018 12:00:55 -0400 Subject: [PATCH 179/296] #read_scratch should take blocks to keep callbacks in their thread. Added Poller#poll_using(method, &block), to match Reader#poll_using. --- lib/dino/components/mixins/poller.rb | 8 ++++++-- lib/dino/components/one_wire/ds18b20.rb | 5 +---- lib/dino/components/one_wire/slave.rb | 3 ++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/dino/components/mixins/poller.rb b/lib/dino/components/mixins/poller.rb index 3af630aa..770ff6f4 100644 --- a/lib/dino/components/mixins/poller.rb +++ b/lib/dino/components/mixins/poller.rb @@ -5,16 +5,20 @@ module Poller include Reader include Threaded - def poll(interval=1, &block) + def poll_using(method, interval=1, &block) stop add_callback(:poll, &block) if block_given? threaded_loop do - _read + method.call sleep interval end end + def poll(interval=1, &block) + poll_using(self.method(:_read), interval, &block) + end + def stop super if defined?(super) stop_thread diff --git a/lib/dino/components/one_wire/ds18b20.rb b/lib/dino/components/one_wire/ds18b20.rb index bcd1c35e..cedac9d0 100644 --- a/lib/dino/components/one_wire/ds18b20.rb +++ b/lib/dino/components/one_wire/ds18b20.rb @@ -44,10 +44,7 @@ def convert def _read convert - data = read_scratch(9) - - self.update(data) - pre_callback_filter(data) + read_scratch(9) { |data| self.update(data) } end def pre_callback_filter(bytes) diff --git a/lib/dino/components/one_wire/slave.rb b/lib/dino/components/one_wire/slave.rb index fbac3c06..370ba1b4 100644 --- a/lib/dino/components/one_wire/slave.rb +++ b/lib/dino/components/one_wire/slave.rb @@ -8,8 +8,9 @@ class Slave attr_reader :address alias :bus :board - def read_scratch(num_bytes) + def read_scratch(num_bytes, &block) atomically do + bus.add_callback(:read, &block) if block_given? match bus.write(READ_SCRATCH) bus.read(num_bytes) From 8b8a5fe7bcba08f55ea6ea2599ae0378a7aa3470 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 17 Feb 2018 13:36:06 -0400 Subject: [PATCH 180/296] I2C bus, slave and DS3231 RTC example. --- dino.gemspec | 1 + examples/i2c/ds3231.rb | 23 +++++++++++++ lib/dino/api/i2c.rb | 10 ++++-- lib/dino/components.rb | 2 ++ lib/dino/components/i2c.rb | 9 ++++++ lib/dino/components/i2c/bus.rb | 54 +++++++++++++++++++++++++++++++ lib/dino/components/i2c/ds3231.rb | 33 +++++++++++++++++++ lib/dino/components/i2c/slave.rb | 22 +++++++++++++ 8 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 examples/i2c/ds3231.rb create mode 100644 lib/dino/components/i2c.rb create mode 100644 lib/dino/components/i2c/bus.rb create mode 100644 lib/dino/components/i2c/ds3231.rb create mode 100644 lib/dino/components/i2c/slave.rb diff --git a/dino.gemspec b/dino.gemspec index 171b28fc..d2f2a53b 100644 --- a/dino.gemspec +++ b/dino.gemspec @@ -35,6 +35,7 @@ Gem::Specification.new do |gem| gem.executables = ["dino"] gem.add_dependency 'rubyserial', '~> 0.5.0' + gem.add_dependency 'bcd', '~> 0.3.0' gem.add_development_dependency 'rake' gem.add_development_dependency 'rspec' diff --git a/examples/i2c/ds3231.rb b/examples/i2c/ds3231.rb new file mode 100644 index 00000000..7c5a3be8 --- /dev/null +++ b/examples/i2c/ds3231.rb @@ -0,0 +1,23 @@ +# +# Somewhat pointless example to ensure I2C is working. Sets the time on a +# connected DS3231 real-time-clock and reads it back every 5 seconds. +# +require 'bundler/setup' +require 'dino' + +board = Dino::Board.new(Dino::TxRx::Serial.new) + +# For now you need to pass in the SDA pin of the I2C interface Arduino/Wire uses. +# Most Arduinos: A4 +# Leonardo: 2 +# Due and Mega: 20 +# ESP8266 : 4 (D2 on WeMos/NodeMCU = GPIO 4 on the ESP8266 itself) +bus = Dino::Components::I2C::Bus.new(board: board, pin: 4) +rtc = Dino::Components::I2C::DS3231.new(bus: bus, address: 104) + +rtc.time = Time.now + +loop do + puts rtc.time + sleep 5 +end diff --git a/lib/dino/api/i2c.rb b/lib/dino/api/i2c.rb index 6815b157..17d833ea 100644 --- a/lib/dino/api/i2c.rb +++ b/lib/dino/api/i2c.rb @@ -18,11 +18,15 @@ def i2c_write(address, bytes=[], options={}) # CMD = 35 def i2c_read(address, register, num_bytes, options={}) - aux = pack :uint8, [address, register, num_bytes] - settings = options[:repeated_start] ? 1 : 0 - settings = settings | 0b10 unless register + # Won't write anything if no start register was given. + unless register + settings = settings | 0b10 + register = 0 + end + + aux = pack :uint8, [address, register, num_bytes] write Message.encode command: 35, value: settings, aux_message: aux diff --git a/lib/dino/components.rb b/lib/dino/components.rb index 8a58195f..b345a97a 100644 --- a/lib/dino/components.rb +++ b/lib/dino/components.rb @@ -4,6 +4,8 @@ module Components require 'dino/components/mixins' require 'dino/components/basic' require 'dino/components/register' + require 'dino/components/spi' + require 'dino/components/i2c' autoload :Led, 'dino/components/led' autoload :Button, 'dino/components/button' autoload :Sensor, 'dino/components/sensor' diff --git a/lib/dino/components/i2c.rb b/lib/dino/components/i2c.rb new file mode 100644 index 00000000..51986738 --- /dev/null +++ b/lib/dino/components/i2c.rb @@ -0,0 +1,9 @@ +module Dino + module Components + module I2C + require 'dino/components/i2c/bus' + require 'dino/components/i2c/slave' + require 'dino/components/i2c/ds3231' + end + end +end diff --git a/lib/dino/components/i2c/bus.rb b/lib/dino/components/i2c/bus.rb new file mode 100644 index 00000000..06ef013a --- /dev/null +++ b/lib/dino/components/i2c/bus.rb @@ -0,0 +1,54 @@ +module Dino + module Components + module I2C + class Bus + include Setup::SinglePin + include Mixins::BusMaster + include Mixins::Reader + + attr_reader :found_devices + + def after_initialize(options={}) + super(options) + bubble_callbacks + end + + def search + addresses = read_using -> { board.i2c_search } + @found_devices = addresses.split(":").map(&:to_i) + end + + def write(address, bytes=[], options={}) + board.i2c_write(address, bytes, options) + end + + def read(*args) + read_using -> { _read(*args) } + end + + def _read(address, register, num_bytes=1, options={}) + board.i2c_read(address, register, num_bytes, options) + end + + def bubble_callbacks + add_callback(:bus_master) do |str| + if str.match /d*-/ + address, data = str.split("-") + address = address.to_i + data = data.split(",").map(&:to_i) + update_component({address: address, data: data}) + end + end + end + + def update_component(hash) + @components.each do |component| + if component.address == hash[:address] + component.update(hash[:data]) + end + end + end + end + end + end +end diff --git a/lib/dino/components/i2c/ds3231.rb b/lib/dino/components/i2c/ds3231.rb new file mode 100644 index 00000000..04400448 --- /dev/null +++ b/lib/dino/components/i2c/ds3231.rb @@ -0,0 +1,33 @@ +module Dino + module Components + module I2C + class DS3231 < Slave + require 'bcd' + + def pre_callback_filter(bytes) + t = bytes.map { |b| BCD.encode(b) } + Time.new t[6]+ 1970, t[5], t[4], t[2], t[1], t[0] + end + + def time + # Return of read_bytes is still unfiltered string from the bus... + read_using -> { read_bytes(nil, 7) } + @state + end + + def time=(time) + bytes = [ 0, + BCD.decode(time.sec), + BCD.decode(time.min), + BCD.decode(time.hour), + BCD.decode(time.strftime('%u').to_i), + BCD.decode(time.day), + BCD.decode(time.month), + BCD.decode(time.year - 1970) ] + write(bytes) + time + end + end + end + end +end diff --git a/lib/dino/components/i2c/slave.rb b/lib/dino/components/i2c/slave.rb new file mode 100644 index 00000000..14cf1e14 --- /dev/null +++ b/lib/dino/components/i2c/slave.rb @@ -0,0 +1,22 @@ +module Dino + module Components + module I2C + class Slave + include Mixins::BusSlave + include Mixins::Poller + + def repeated_start + false + end + + def write(bytes=[]) + bus.write(address, bytes, repeated_start: repeated_start) + end + + def read_bytes(register, num_bytes=1) + bus.read(address, register, num_bytes, repeated_start: repeated_start) + end + end + end + end +end From bd83e3576f1ccc79f44c9b47199256c5e23271f9 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 17 Feb 2018 15:02:36 -0400 Subject: [PATCH 181/296] Bug fix and test for analog and digital listeners --- examples/register/shift_out.rb | 12 ++++++++---- lib/dino/components/basic/analog_input.rb | 8 ++++---- lib/dino/components/basic/digital_input.rb | 6 +++--- lib/dino/components/i2c/ds3231.rb | 2 +- lib/dino/components/mixins/listener.rb | 12 +----------- lib/dino/components/one_wire/slave.rb | 4 ++-- lib/dino/components/setup/input.rb | 12 ++++++------ spec/lib/components/basic/analog_input_spec.rb | 10 +++++++++- spec/lib/components/mixins/listener_spec.rb | 6 +++--- 9 files changed, 37 insertions(+), 35 deletions(-) diff --git a/examples/register/shift_out.rb b/examples/register/shift_out.rb index eefdd95a..6729d0df 100644 --- a/examples/register/shift_out.rb +++ b/examples/register/shift_out.rb @@ -6,13 +6,17 @@ require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) -shift_register = Dino::Components::Register::ShiftOut.new board: board, - pins: {latch: 9, data: 11, clock: 13} +register = Dino::Components::Register::ShiftOut.new board: board, + pins: {latch: 9, data: 11, clock: 13} # Write a single byte -shift_register.write(255) +register.write(255) # Write an array of bytes (for multiple registers). -shift_register.write([255, 0]) +register.write([255, 0]) + +# Register can behave as a BoardProxy, with components addressable directly. +led = Dino::Components::Led.new(board: register, pin: 0) +led.blink 1 sleep diff --git a/lib/dino/components/basic/analog_input.rb b/lib/dino/components/basic/analog_input.rb index 5e3ebb08..ff977c43 100644 --- a/lib/dino/components/basic/analog_input.rb +++ b/lib/dino/components/basic/analog_input.rb @@ -7,14 +7,14 @@ class AnalogInput include Mixins::Reader include Mixins::Poller include Mixins::Listener - + def _read - board.analog_read(self.pin) + board.analog_read(pin) end def _listen(divider=16) - divider ||= 16 - board.analog_listen(self.pin, divider) + @divider ||= 16 + board.analog_listen(pin, @divider) end end end diff --git a/lib/dino/components/basic/digital_input.rb b/lib/dino/components/basic/digital_input.rb index db24b99b..002125fb 100644 --- a/lib/dino/components/basic/digital_input.rb +++ b/lib/dino/components/basic/digital_input.rb @@ -14,12 +14,12 @@ def after_initialize(options={}) end def _read - board.digital_read(self.pin) + board.digital_read(pin) end def _listen(divider=4) - divider ||= 4 - board.digital_listen(self.pin, divider) + @divider ||= 4 + board.digital_listen(pin, @divider) end def on_high(&block) diff --git a/lib/dino/components/i2c/ds3231.rb b/lib/dino/components/i2c/ds3231.rb index 04400448..4185cee4 100644 --- a/lib/dino/components/i2c/ds3231.rb +++ b/lib/dino/components/i2c/ds3231.rb @@ -6,7 +6,7 @@ class DS3231 < Slave def pre_callback_filter(bytes) t = bytes.map { |b| BCD.encode(b) } - Time.new t[6]+ 1970, t[5], t[4], t[2], t[1], t[0] + Time.new t[6] + 1970, t[5], t[4], t[2], t[1], t[0] end def time diff --git a/lib/dino/components/mixins/listener.rb b/lib/dino/components/mixins/listener.rb index 88e07835..da450e03 100644 --- a/lib/dino/components/mixins/listener.rb +++ b/lib/dino/components/mixins/listener.rb @@ -12,19 +12,9 @@ def listen(divider=nil, &block) def stop super if defined?(super) - _stop_listen + _stop_listener remove_callbacks :listen end - - def _listen - raise NotImplementedError - .new("#{self.class.name}#_listen is not defined.") - end - - def _stop_listen - raise NotImplementedError - .new("#{self.class.name}#_stop_listen is not defined.") - end end end end diff --git a/lib/dino/components/one_wire/slave.rb b/lib/dino/components/one_wire/slave.rb index 370ba1b4..4df09665 100644 --- a/lib/dino/components/one_wire/slave.rb +++ b/lib/dino/components/one_wire/slave.rb @@ -54,8 +54,8 @@ def serial_number def extract_serial # Remove CRC & family code. - address = (@address & 0x00FFFFFFFFFFFFFF) >> 8 - address.to_s(16).rjust(12, "0") + serial = (@address & 0x00FFFFFFFFFFFFFF) >> 8 + serial.to_s(16).rjust(12, "0") end end end diff --git a/lib/dino/components/setup/input.rb b/lib/dino/components/setup/input.rb index 1349f438..8362b12a 100644 --- a/lib/dino/components/setup/input.rb +++ b/lib/dino/components/setup/input.rb @@ -3,24 +3,24 @@ module Components module Setup module Input include SinglePin - attr_reader :pullup + attr_reader :pullup, :divider def pullup=(pullup) @pullup = pullup board.set_pullup(self.pin, pullup) end - protected + def _stop_listener + board.stop_listener(pin) + end + protected + def initialize_pins(options={}) super(options) self.mode = :in self.pullup = options[:pullup] end - - def _stop_listen - board.stop_listener(pin) - end end end end diff --git a/spec/lib/components/basic/analog_input_spec.rb b/spec/lib/components/basic/analog_input_spec.rb index 69ae26b8..e5c438d6 100644 --- a/spec/lib/components/basic/analog_input_spec.rb +++ b/spec/lib/components/basic/analog_input_spec.rb @@ -16,11 +16,19 @@ module Basic end describe '#_listen' do - it 'should send #analog_listen to the board with its pin' do + it 'should send #analog_listen to the board with its pin and divider' do expect(board).to receive(:analog_listen).with(subject.pin, 16) subject._listen end end + + + describe '#_stop_listener' do + it 'should send #stop_listener to the board with its pin' do + expect(board).to receive(:stop_listener).with(subject.pin) + subject._stop_listener + end + end end end end diff --git a/spec/lib/components/mixins/listener_spec.rb b/spec/lib/components/mixins/listener_spec.rb index fc8e5c5d..bb24b60e 100644 --- a/spec/lib/components/mixins/listener_spec.rb +++ b/spec/lib/components/mixins/listener_spec.rb @@ -9,7 +9,7 @@ class ListenComponent include Setup::Base include Listener def _listen(divider=nil); end - def _stop_listen; end + def _stop_listener; end def initialize; after_initialize; end end @@ -42,8 +42,8 @@ def initialize; after_initialize; end end describe '#stop' do - it 'should call #_stop_listen' do - expect(subject).to receive(:_listen) + it 'should call #_stop_listener' do + expect(subject).to receive(:_stop_listener) subject.listen end From 75dbc5feb9c0d267d34b7c1144cee4decaed24e0 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 17 Feb 2018 15:18:55 -0400 Subject: [PATCH 182/296] Remove misleading IrReceiver. Just an active low digital in. --- examples/ir_receiver/ir_receiver.rb | 34 ----------------------------- lib/dino/components.rb | 3 +-- lib/dino/components/ir_receiver.rb | 7 ------ 3 files changed, 1 insertion(+), 43 deletions(-) delete mode 100644 examples/ir_receiver/ir_receiver.rb delete mode 100644 lib/dino/components/ir_receiver.rb diff --git a/examples/ir_receiver/ir_receiver.rb b/examples/ir_receiver/ir_receiver.rb deleted file mode 100644 index 3111f586..00000000 --- a/examples/ir_receiver/ir_receiver.rb +++ /dev/null @@ -1,34 +0,0 @@ -# -# This is an example of how to use the button class -# You must register helpers and have the main thread -# sleep or in someway keep running or your program -# will exit before any callbacks can be called -# -require 'bundler/setup' -require 'dino' - -board = Dino::Board.new(Dino::TxRx::Serial.new) -ir = Dino::Components::IrReceiver.new(pin: 2, board: board) -led = Dino::Components::Led.new(pin: 13, board: board) - -n = 0 - -flash = Proc.new do - n += 1 - puts "light flash #{n}" -end - -sleep 2 -Thread.new do - loop do - led.on - sleep 0.01 - led.off - sleep 0.01 - end -end - -sleep 4 -ir.on_flash(flash) - -sleep diff --git a/lib/dino/components.rb b/lib/dino/components.rb index b345a97a..f8221d63 100644 --- a/lib/dino/components.rb +++ b/lib/dino/components.rb @@ -4,7 +4,7 @@ module Components require 'dino/components/mixins' require 'dino/components/basic' require 'dino/components/register' - require 'dino/components/spi' + # require 'dino/components/spi' require 'dino/components/i2c' autoload :Led, 'dino/components/led' autoload :Button, 'dino/components/button' @@ -13,7 +13,6 @@ module Components autoload :Servo, 'dino/components/servo' autoload :SSD, 'dino/components/ssd' autoload :Stepper, 'dino/components/stepper' - autoload :IrReceiver, 'dino/components/ir_receiver' autoload :LCD, 'dino/components/lcd' autoload :Relay, 'dino/components/relay' autoload :SoftwareSerial, 'dino/components/softserial' diff --git a/lib/dino/components/ir_receiver.rb b/lib/dino/components/ir_receiver.rb deleted file mode 100644 index 5dcf44a0..00000000 --- a/lib/dino/components/ir_receiver.rb +++ /dev/null @@ -1,7 +0,0 @@ -module Dino - module Components - class IrReceiver < Basic::DigitalInput - alias :on_flash :on_low - end - end -end From a0e1c9835ca28aa98ed38258682261b920316c68 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 17 Feb 2018 17:41:04 -0400 Subject: [PATCH 183/296] ESP8266 class with pin mapping. Get aux limit in handshake. --- examples/i2c/ds3231.rb | 16 ++++++--- lib/dino/api/helper.rb | 13 -------- lib/dino/board.rb | 70 ++++----------------------------------- lib/dino/board/base.rb | 49 +++++++++++++++++++++++++++ lib/dino/board/default.rb | 35 ++++++++++++++++++++ lib/dino/board/esp8266.rb | 63 +++++++++++++++++++++++++++++++++++ spec/lib/board_spec.rb | 6 +++- spec/spec_helper.rb | 5 ++- src/lib/Dino.cpp | 2 ++ src/lib/Dino.h | 9 ++--- 10 files changed, 180 insertions(+), 88 deletions(-) create mode 100644 lib/dino/board/base.rb create mode 100644 lib/dino/board/default.rb create mode 100644 lib/dino/board/esp8266.rb diff --git a/examples/i2c/ds3231.rb b/examples/i2c/ds3231.rb index 7c5a3be8..f19f4481 100644 --- a/examples/i2c/ds3231.rb +++ b/examples/i2c/ds3231.rb @@ -7,11 +7,17 @@ board = Dino::Board.new(Dino::TxRx::Serial.new) -# For now you need to pass in the SDA pin of the I2C interface Arduino/Wire uses. -# Most Arduinos: A4 -# Leonardo: 2 -# Due and Mega: 20 -# ESP8266 : 4 (D2 on WeMos/NodeMCU = GPIO 4 on the ESP8266 itself) +# Only pass in the SDA pin of the I2C bus. SCL (clock) pin MUST be properly +# connected for things to work, but we don't need to control it. +# +# Most Arduinos: SDA = 'A4' SCL = 'A5' +# Leonardo: SDA = 2 SCL = 3 +# Due and Mega: SDA = 20 SCL = 21 +# ESP8266 : SDA = 4 SCL = 5 +# +# On the ESP8266, 'D2' and 'D1' also map to SDA and SCL respectively. +# This is for convenience when working with common development boards. +# bus = Dino::Components::I2C::Bus.new(board: board, pin: 4) rtc = Dino::Components::I2C::DS3231.new(bus: bus, address: 104) diff --git a/lib/dino/api/helper.rb b/lib/dino/api/helper.rb index a534fabd..3692a8c9 100644 --- a/lib/dino/api/helper.rb +++ b/lib/dino/api/helper.rb @@ -1,19 +1,6 @@ module Dino module API module Helper - # - # Microcontrollers have different aux message limits based on available RAM. - # Board should set @aux_limit from handshake. Default to safe minimum. - # - def aux_limit - @aux_limit || 527 # Should default to 39, but this isn't in handshake yet. - end - - def write(message) - raise NotImplementedError - .new("#{self.class.name}#write not defined in Board subclass") - end - def pack(type, data, options={}) # Always pack as little endian. template = case type diff --git a/lib/dino/board.rb b/lib/dino/board.rb index a474b4d7..80c585cc 100644 --- a/lib/dino/board.rb +++ b/lib/dino/board.rb @@ -1,69 +1,11 @@ module Dino - class Board - include API::Core - include API::I2C - include API::Servo - include API::ShiftIO - include API::SPI - include API::Infrared - include API::OneWire - include API::Tone + module Board + require 'dino/board/base' + require 'dino/board/default' + require 'dino/board/esp8266' - attr_reader :high, :low, :analog_high, :components, :analog_zero, :dac_zero - - def initialize(io, options={}) - @io, @components = io, [] - @analog_zero, @dac_zero = @io.handshake.to_s.split(",").map { |pin| pin.to_i } - io.add_observer(self) - self.analog_resolution = options[:bits] || 8 - end - - def analog_resolution=(value) - @bits = value || 8 - write Dino::Message.encode(command: 96, value: @bits) - @low = 0 - @high = 1 - @analog_high = (2 ** @bits) - 1 - end - - def write(msg) - @io.write(msg) - end - - def update(pin, msg) - @components.each do |part| - part.update(msg) if pin.to_i == part.pin - end - end - - def add_component(component) - @components << component - end - - def remove_component(component) - stop_listener(component.pin) - @components.delete(component) - end - - DIGITAL_REGEX = /\A\d+\z/i - ANALOG_REGEX = /\A(a)\d+\z/i - DAC_REGEX = /\A(dac)\d+\z/i - - def convert_pin(pin) - pin = pin.to_s - return pin.to_i if pin.match(DIGITAL_REGEX) - return analog_pin_to_i(pin) if pin.match(ANALOG_REGEX) - return dac_pin_to_i(pin) if pin.match(DAC_REGEX) - raise "Incorrect pin format" - end - - def analog_pin_to_i(pin) - @analog_zero + pin.gsub(/\Aa/i, '').to_i - end - - def dac_pin_to_i(pin) - raise "The board did not specify any DAC pins" unless @dac_zero - @dac_zero + pin.gsub(/\Adac/i, '').to_i + def self.new(options={}) + self::Default.new(options) end end end diff --git a/lib/dino/board/base.rb b/lib/dino/board/base.rb new file mode 100644 index 00000000..1dcda3d2 --- /dev/null +++ b/lib/dino/board/base.rb @@ -0,0 +1,49 @@ +module Dino + module Board + class Base + attr_reader :high, :low, :analog_high, :components, :analog_zero, :dac_zero + + def initialize(io, options={}) + @io, @components = io, [] + + ack = io.handshake.split(",").map { |num| num.to_i } + @aux_limit, @analog_zero, @dac_zero = ack + + io.add_observer(self) + self.analog_resolution = options[:bits] || 8 + end + + def analog_resolution=(value) + @bits = value || 8 + write Dino::Message.encode(command: 96, value: @bits) + @low = 0 + @high = 1 + @analog_high = (2 ** @bits) - 1 + end + + # Aux limits differ per board depending on RAM, 39 is the safe minimum. + def aux_limit + @aux_limit ||= 39 + end + + def write(msg) + @io.write(msg) + end + + def update(pin, msg) + @components.each do |part| + part.update(msg) if pin.to_i == part.pin + end + end + + def add_component(component) + @components << component + end + + def remove_component(component) + stop_listener(component.pin) + @components.delete(component) + end + end + end +end diff --git a/lib/dino/board/default.rb b/lib/dino/board/default.rb new file mode 100644 index 00000000..9bcd0d03 --- /dev/null +++ b/lib/dino/board/default.rb @@ -0,0 +1,35 @@ +module Dino + module Board + class Default < Base + include API::Core + include API::I2C + include API::Servo + include API::ShiftIO + include API::SPI + include API::Infrared + include API::OneWire + include API::Tone + + DIGITAL_REGEX = /\A\d+\z/i + ANALOG_REGEX = /\A(a)\d+\z/i + DAC_REGEX = /\A(dac)\d+\z/i + + def convert_pin(pin) + pin = pin.to_s + return pin.to_i if pin.match(DIGITAL_REGEX) + return analog_pin_to_i(pin) if pin.match(ANALOG_REGEX) + return dac_pin_to_i(pin) if pin.match(DAC_REGEX) + raise "Incorrect pin format" + end + + def analog_pin_to_i(pin) + @analog_zero + pin.gsub(/\Aa/i, '').to_i + end + + def dac_pin_to_i(pin) + raise "The board did not specify any DAC pins" unless @dac_zero + @dac_zero + pin.gsub(/\Adac/i, '').to_i + end + end + end +end diff --git a/lib/dino/board/esp8266.rb b/lib/dino/board/esp8266.rb new file mode 100644 index 00000000..2de6fb24 --- /dev/null +++ b/lib/dino/board/esp8266.rb @@ -0,0 +1,63 @@ +module Dino + module Board + class ESP8266 < Base + include API::Core + include API::I2C + include API::Servo + include API::ShiftIO + include API::SPI + include API::Infrared + include API::OneWire + include API::Tone + + + DIGITAL_REGEX = /\Ad\d+\z/i + ANALOG_REGEX = /\A(a)\d+\z/i + RAW_REGEX = /\A\d+\z/i + GPIO_REGEX = /\A(gpio)\d+\z/i + + D_PIN_MAP = { + 0 => 16, + 1 => 5, + 2 => 4, + 3 => 0, + 4 => 2, + 5 => 14, + 6 => 12, + 7 => 13, + 8 => 15, + 9 => 3, # Serial RXD + 10 => 1, # Serial TXD + } + + def convert_pin(pin) + pin = pin.to_s + return pin.to_i if pin.match(RAW_REGEX) + return gpio_pin_to_i(pin) if pin.match(GPIO_REGEX) + return analog_pin_to_i(pin) if pin.match(ANALOG_REGEX) + return digital_pin_to_i(pin) if pin.match(DIGITAL_REGEX) + raise "Incorrect pin format" + end + + # Only one analog in on the ESP8266, GPIO 17. + def analog_pin_to_i(pin) + gpio = pin.gsub(/\Aa/i, '').to_i + raise "Invalid analog input pin" unless gpio == 0 + 17 + end + + def gpio_pin_to_i(pin) + gpio = pin.gsub(/\Agpio/i, '').to_i + raise "Invalid GPIO input pin" if (gpio < 0 || gpio > 17) + gpio + end + + def digital_pin_to_i(pin) + d_index = pin.gsub(/\Ad/i, '').to_i + gpio = D_PIN_MAP[d_index] + raise "Invalid D pin" unless gpio + gpio + end + end + end +end diff --git a/spec/lib/board_spec.rb b/spec/lib/board_spec.rb index e549759e..381fbb07 100644 --- a/spec/lib/board_spec.rb +++ b/spec/lib/board_spec.rb @@ -3,7 +3,11 @@ module Dino describe Dino::Board do def io_mock(methods = {}) - @io ||= double(:io, {add_observer: true, handshake: "14,20", write: true, read: true}.merge(methods)) + @io ||= double :io, { add_observer: true, + handshake: "528,14,20", + write: true, + read: true + }.merge(methods) end subject { Board.new(io_mock) } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3ed0a269..a31b7943 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -14,7 +14,10 @@ def self.redefine(const, value, opts={}) module BoardMock def self.included(base) base.class_eval do - let(:txrx) { double(:txrx, add_observer: true, handshake: "14,20", write: true, read: true) } + let(:txrx) { double :txrx, add_observer: true, + handshake: "528,14,20", + write: true, + read: true } let(:board) { Dino::Board.new(txrx) } end end diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 7cd17cbc..dd7af541 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -227,6 +227,8 @@ void Dino::reset() { rcvBytes = 0; stream->print("ACK:"); + stream->print(AUX_SIZE); + stream->print(','); stream->print(A0); #if defined(__SAM3X8E__) stream->print(','); diff --git a/src/lib/Dino.h b/src/lib/Dino.h index a6c6e9bf..743ee1c8 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -128,14 +128,15 @@ class Dino { // Scale aux message allocation based on enabled features and chip. #if defined(DINO_IR_OUT) && !defined (__AVR_ATmega168__) - byte auxMsg[528]; + # define AUX_SIZE 528 #elif defined(DINO_SHIFT) || defined(DINO_SPI) || defined (DINO_I2C) - byte auxMsg[264]; + # define AUX_SIZE 264 #elif defined (DINO_LCD) - byte auxMsg[136]; + # define AUX_SIZE 136 #else - byte auxMsg[40]; + # define AUX_SIZE 40 #endif + byte auxMsg[AUX_SIZE]; // Save a pointer to any stream so we can call ->print and ->write on it. Stream* stream; From db5b9bde28809050c241043467a248cd538097fa Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 17 Feb 2018 17:58:28 -0400 Subject: [PATCH 184/296] Append current dino version to generated sketches. --- lib/dino_cli/generator.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/dino_cli/generator.rb b/lib/dino_cli/generator.rb index 0a3b1ba8..af02ecb9 100644 --- a/lib/dino_cli/generator.rb +++ b/lib/dino_cli/generator.rb @@ -2,6 +2,7 @@ class DinoCLI::Generator require "fileutils" require "dino_cli/packages" require "dino_cli/targets" + require "dino/version" attr_accessor :options def initialize(options={}) @@ -14,6 +15,7 @@ def append_target # Preserve the source sketch name, since we need to copy that file. options[:src_sketch_name] = options[:sketch_name].dup options[:sketch_name] << "_#{options[:target]}" unless options[:target] == :mega + options[:sketch_name] << "_#{::Dino::VERSION}" end def self.run!(options={}) From 4b575c5cfefa8054577e4226421a716c7e5bf1ae Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 17 Feb 2018 18:34:21 -0400 Subject: [PATCH 185/296] Trap interrupt and reset the board. --- lib/dino/tx_rx/base.rb | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index db90297f..972d7de0 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -11,32 +11,17 @@ class RxFlushTimeout < StandardError; end class Base include Observable include Handshake - # Let the methods in FlowControl wrap subclass methods too. + # We need the methods in FlowControl to wrap subclass methods too. def self.inherited(subclass) subclass.send(:prepend, FlowControl) end - def read(message) - raise NotImplementedError - .new("#{self.class.name}#read not defined in Dino::TxRx subclass") - end - - def write(message) - raise NotImplementedError - .new("#{self.class.name}#write not defined in Dino::TxRx subclass") - end - private def io @io ||= connect end - def connect - raise NotImplementedError - .new("#{self.class.name}#connect not defined in Dino::TxRx subclass") - end - def io_reset flush_read stop_read @@ -50,7 +35,16 @@ def flush_read end def start_read - @thread ||= Thread.new { loop { read_and_parse } } + @thread ||= Thread.new do + trap("INT") do + io.write("\n90\n") + raise Interrupt + end + + loop do + read_and_parse + end + end end def stop_read @@ -59,11 +53,6 @@ def stop_read @thread = nil end - def read_and_parse - raise NotImplementedError - .new("#{self.class.name}#read_and_parse not defined in Dino::TxRx::FlowControl") - end - def parse(line) if line.match(/\A\d+:/) pin, message = line.split(":", 2) From 4d1a3332e17bbeb2b57584d0206e1cbc0f027498 Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 18 Feb 2018 14:08:50 -0400 Subject: [PATCH 186/296] DHT temp/RH sensor implementation in mostly Ruby. Uses a (new) generic function on the board to pull the pin low for reset, then read pulse lengths (between rising/falling edges) sent by the sensor. Pulse durations get sent to Ruby for decoding. Removed DHT library. --- .gitmodules | 3 -- examples/dht/dht.rb | 17 +++--- lib/dino/api/core.rb | 18 +++++++ lib/dino/components/dht.rb | 37 ++++++++++--- .../components/one_wire/bus_enumerator.rb | 2 +- lib/dino_cli/packages.rb | 9 ---- lib/dino_cli/targets.rb | 2 +- src/lib/Dino.cpp | 8 +-- src/lib/Dino.h | 2 +- src/lib/DinoCoreIO.cpp | 52 +++++++++++++++++++ src/lib/DinoDHT.cpp | 35 ------------- src/vendor/arduino-DHT | 1 - 12 files changed, 115 insertions(+), 71 deletions(-) delete mode 100644 src/lib/DinoDHT.cpp delete mode 160000 src/vendor/arduino-DHT diff --git a/.gitmodules b/.gitmodules index 9ae081fb..d1468cc8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "src/vendor/Arduino-IRremote"] path = src/vendor/Arduino-IRremote url = https://github.com/z3t0/Arduino-IRremote.git -[submodule "src/vendor/arduino-DHT"] - path = src/vendor/arduino-DHT - url = https://github.com/markruys/arduino-DHT.git [submodule "src/vendor/IRremoteESP8266"] path = src/vendor/IRremoteESP8266 url = https://github.com/markszabo/IRremoteESP8266.git diff --git a/examples/dht/dht.rb b/examples/dht/dht.rb index e08d0d17..113e71c9 100644 --- a/examples/dht/dht.rb +++ b/examples/dht/dht.rb @@ -4,16 +4,21 @@ require 'bundler/setup' require 'dino' -board = Dino::Board.new(Dino::TxRx::Serial.new) +board = Dino::Board.new(Dino::TxRx::Serial.new(device: "/dev/cu.usbmodem1431")) dht = Dino::Components::DHT.new(pin: 7, board: board) # The DHT class pre-processes raw data from the board. When it reaches callbacks # it's already hash of :temperature and :humidity keys, both with Float values. -dht.add_callback do |data| - puts "The temperature is #{data[:temperature]} degrees Celsius" - puts "The relative humidity is #{data[:humidity]}%" +dht.add_callback do |reading| + print "#{Time.now.strftime '%Y-%m-%d %H:%M:%S'} - " + if reading[:error] + puts "Error: #{reading[:error]}" + else + print "#{reading[:celsius]} \xC2\xB0C | #{reading[:farenheit]} \xC2\xB0F | " + puts "#{reading[:humidity]}% relative humidity" + end end -# Read it every 10 seconds. -dht.poll(10) +# Read it every 5 seconds. +dht.poll(5) sleep diff --git a/lib/dino/api/core.rb b/lib/dino/api/core.rb index 146e6fef..bbda7e56 100644 --- a/lib/dino/api/core.rb +++ b/lib/dino/api/core.rb @@ -69,6 +69,24 @@ def analog_listen(pin, divider=16) def stop_listener(pin) set_listener(pin, :off) end + + def pulse_read(pin, options={}) + reset = options[:reset] || false + reset_time = options[:reset_time] || 0 + pulse_limit = options[:pulse_limit] || 100 + timeout = options[:timeout] || 200 + + settings = reset ? 1 : 0 + settings = settings | 0b10 if reset == high + + aux = pack :uint16, [reset_time, timeout] + aux << pack(:uint8, pulse_limit) + + write Message.encode command: 11, + pin: convert_pin(pin), + value: settings, + aux_message: aux + end end end end diff --git a/lib/dino/components/dht.rb b/lib/dino/components/dht.rb index cf3f91d9..46438eae 100644 --- a/lib/dino/components/dht.rb +++ b/lib/dino/components/dht.rb @@ -5,18 +5,39 @@ class DHT include Setup::Input include Mixins::Poller - def after_initialize(options={}) - super(options) - @state = {temperature: nil, humidity: nil} - end - def _read - board.dht_read(self.pin) + board.pulse_read(pin, reset: board.low, reset_time: 1000, pulse_limit: 84) end def pre_callback_filter(data) - t, h = data.split(",") - { temperature: t.to_f, humidity: h.to_f } + decode(data.split(",").map(&:to_i)) + end + + def decode(data) + data = data.last(81) + return { error: 'missing data' } unless data.length == 81 + data = data[0..79] + + bytes = [] + data.each_slice(16) do |b| + byte = 0b00000000 + b.each_slice(2) do |x,y| + bit = (yprint(pin); stream->print(':'); + for (byte i=1; i<=pulseCount; i++){ + stream->print(auxMsg[i+8]); + stream->print((i == pulseCount) ? '\n' : ','); + } + if (pulseCount == 0) stream->print('\n'); +} diff --git a/src/lib/DinoDHT.cpp b/src/lib/DinoDHT.cpp deleted file mode 100644 index 6f9e38f6..00000000 --- a/src/lib/DinoDHT.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// -// This file adds to the Dino class only if DINO_DHT is defined in Dino.h. -// Should not be included in sketch for Arduino Due yet. -// -#include "Dino.h" -#ifdef DINO_DHT - -// Include the DHT library and create an instance. -#include "DHT.h" -DHT dht; - -// CMD = 13 -// Read a DHT sensor -void Dino::dhtRead() { - // Can't access pin to check this in latest DHT library. - // Assuming safe to repeatedly call setup for now. - // if (pin != dht.pin) - dht.setup(pin); - - // Always read both values - float temperature = dht.getTemperature(); - float humidity = dht.getHumidity(); - - stream->print(pin); stream->print(':'); - // Send the values as a comma delimited printed decimals with 1dp precision. - stream->print(temperature, 1); stream->print(','); - stream->print(humidity, 1); - stream->print('\n'); - - #ifdef debug - Serial.print("Called Dino::dhtRead()\n"); - #endif -} - -#endif diff --git a/src/vendor/arduino-DHT b/src/vendor/arduino-DHT deleted file mode 160000 index cd24ce3c..00000000 --- a/src/vendor/arduino-DHT +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cd24ce3ca32fe0caf05d6c21565c8d5605860a08 From 03f454e1522d0a09d56f944d836fb30819471a4b Mon Sep 17 00:00:00 2001 From: vickash Date: Thu, 22 Feb 2018 10:35:45 -0400 Subject: [PATCH 187/296] Bunch of small fixes. --- lib/dino/api/spi.rb | 10 ++-------- lib/dino/board/base.rb | 2 ++ lib/dino/board/default.rb | 1 - lib/dino/board/esp8266.rb | 2 -- lib/dino/components/i2c/bus.rb | 2 +- lib/dino/components/mixins/board_proxy.rb | 20 ++++++++------------ lib/dino/components/one_wire/ds18b20.rb | 8 ++++---- lib/dino/components/register/spi_in.rb | 9 +++------ lib/dino/components/register/spi_out.rb | 7 ++----- spec/lib/components/one_wire/ds18b20_spec.rb | 8 ++++---- src/lib/DinoDefines.h | 1 - 11 files changed, 26 insertions(+), 44 deletions(-) diff --git a/lib/dino/api/spi.rb b/lib/dino/api/spi.rb index 430f0ac3..b023c0d8 100644 --- a/lib/dino/api/spi.rb +++ b/lib/dino/api/spi.rb @@ -21,11 +21,7 @@ def spi_header(options) # CMD = 26 def spi_transfer(pin, options={}) options[:read] ||= 0 - if options[:write] - options[:write] = [options[:write]].flatten - else - options[:write] = [] - end + options[:write] = [options[:write]].flatten.compact || [] return if (options[:read] == 0) && (options[:write].empty?) @@ -37,9 +33,7 @@ def spi_transfer(pin, options={}) # CMD = 27 def spi_listen(pin, options={}) - raise ArgumentError, - 'no SPI bytes to read' unless (options[:read] > 0) - options[:write] = [] + raise ArgumentError, 'no SPI bytes to read' unless (options[:read] > 0) header = spi_header(options) write Message.encode command: 27, diff --git a/lib/dino/board/base.rb b/lib/dino/board/base.rb index 1dcda3d2..d70c6aa1 100644 --- a/lib/dino/board/base.rb +++ b/lib/dino/board/base.rb @@ -1,6 +1,8 @@ module Dino module Board class Base + include API::Core + attr_reader :high, :low, :analog_high, :components, :analog_zero, :dac_zero def initialize(io, options={}) diff --git a/lib/dino/board/default.rb b/lib/dino/board/default.rb index 9bcd0d03..076a5121 100644 --- a/lib/dino/board/default.rb +++ b/lib/dino/board/default.rb @@ -1,7 +1,6 @@ module Dino module Board class Default < Base - include API::Core include API::I2C include API::Servo include API::ShiftIO diff --git a/lib/dino/board/esp8266.rb b/lib/dino/board/esp8266.rb index 2de6fb24..a50fbd53 100644 --- a/lib/dino/board/esp8266.rb +++ b/lib/dino/board/esp8266.rb @@ -1,7 +1,6 @@ module Dino module Board class ESP8266 < Base - include API::Core include API::I2C include API::Servo include API::ShiftIO @@ -10,7 +9,6 @@ class ESP8266 < Base include API::OneWire include API::Tone - DIGITAL_REGEX = /\Ad\d+\z/i ANALOG_REGEX = /\A(a)\d+\z/i RAW_REGEX = /\A\d+\z/i diff --git a/lib/dino/components/i2c/bus.rb b/lib/dino/components/i2c/bus.rb index 06ef013a..6e9496fc 100644 --- a/lib/dino/components/i2c/bus.rb +++ b/lib/dino/components/i2c/bus.rb @@ -33,7 +33,7 @@ def _read(address, register, num_bytes=1, options={}) def bubble_callbacks add_callback(:bus_master) do |str| if str.match /d*-/ - address, data = str.split("-") + address, data = str.split("-", 2) address = address.to_i data = data.split(",").map(&:to_i) update_component({address: address, data: data}) diff --git a/lib/dino/components/mixins/board_proxy.rb b/lib/dino/components/mixins/board_proxy.rb index b18b6ab0..cf9395ec 100644 --- a/lib/dino/components/mixins/board_proxy.rb +++ b/lib/dino/components/mixins/board_proxy.rb @@ -4,25 +4,21 @@ module Mixins module BoardProxy include BusMaster - def after_initialize(options={}) - super(options) - @high = 1 - @low = 0 + def high + 1 end - attr_reader :high, :low + def low + 0 + end def convert_pin(pin) - pin = pin.to_i + pin.to_i end - def set_pin_mode(pin, mode) - nil - end + def set_pin_mode(pin, mode); end - def set_pullup(pin, pullup) - nil - end + def set_pullup(pin, pullup); end def start_read; end end diff --git a/lib/dino/components/one_wire/ds18b20.rb b/lib/dino/components/one_wire/ds18b20.rb index cedac9d0..8aaf0245 100644 --- a/lib/dino/components/one_wire/ds18b20.rb +++ b/lib/dino/components/one_wire/ds18b20.rb @@ -51,14 +51,14 @@ def pre_callback_filter(bytes) return { crc_error: true } unless Helper.crc_check(bytes) @resolution = decode_resolution(bytes) - decode_temp(bytes).merge(raw: bytes) + decode_temperature(bytes).merge(raw: bytes) end # - # Temperature is the first 16 bits (2 bytes of 9 read), little-endian. - # It's a signed, 2's complement, little-endian decimal with 2^-4 exp. + # Temperature is the first 16 bits (2 bytes of 9 read). + # It's a signed, 2's complement, little-endian decimal. LSB = 2 ^ -4. # - def decode_temp(bytes) + def decode_temperature(bytes) celsius = bytes[0..1].pack('C*').unpack('s<')[0] * (2.0 ** -4) farenheit = (celsius * 1.8 + 32).round(4) diff --git a/lib/dino/components/register/spi_in.rb b/lib/dino/components/register/spi_in.rb index 3bcfc41b..36298543 100644 --- a/lib/dino/components/register/spi_in.rb +++ b/lib/dino/components/register/spi_in.rb @@ -14,12 +14,9 @@ class SPIIn < Select def after_initialize(options={}) super(options) if defined?(super) - - @spi_mode = options[:spi_mode] || 0 - - # No default value for clock frequency. - raise 'SPI clock rate (Hz) required in :frequency option' unless options[:frequency] - @frequency = options[:frequency] + + @spi_mode = options[:spi_mode] || 0 + @frequency = options[:frequency] || 3000000 end def read diff --git a/lib/dino/components/register/spi_out.rb b/lib/dino/components/register/spi_out.rb index 727c7a58..14cf87af 100644 --- a/lib/dino/components/register/spi_out.rb +++ b/lib/dino/components/register/spi_out.rb @@ -13,11 +13,8 @@ class SPIOut < Select attr_reader :spi_mode, :frequency def after_initialize(options={}) - @spi_mode = options[:spi_mode] || 0 - - # No default value for clock frequency. - raise 'SPI clock rate (Hz) required in :frequency option' unless options[:frequency] - @frequency = options[:frequency] + @spi_mode = options[:spi_mode] || 0 + @frequency = options[:frequency] || 3000000 super(options) if defined?(super) end diff --git a/spec/lib/components/one_wire/ds18b20_spec.rb b/spec/lib/components/one_wire/ds18b20_spec.rb index 277d7e94..a5ca702c 100644 --- a/spec/lib/components/one_wire/ds18b20_spec.rb +++ b/spec/lib/components/one_wire/ds18b20_spec.rb @@ -10,13 +10,13 @@ module OneWire describe '#decode_temp' do it 'should decode values matching the datasheet and convert C to F' do - expect(subject.decode_temp([0b1101_0000,0b0000_0111])) + expect(subject.decode_temperature([0b1101_0000,0b0000_0111])) .to eq(celsius: 125, farenheit: 257) - expect(subject.decode_temp([0b0000_0000,0b0000_0000])) + expect(subject.decode_temperature([0b0000_0000,0b0000_0000])) .to eq(celsius: 0, farenheit: 32) - expect(subject.decode_temp([0b0101_1110,0b1111_1111])) + expect(subject.decode_temperature([0b0101_1110,0b1111_1111])) .to eq(celsius: -10.125, farenheit: 13.775) - expect(subject.decode_temp([0b1001_0000,0b1111_1100])) + expect(subject.decode_temperature([0b1001_0000,0b1111_1100])) .to eq(celsius: -55, farenheit: -67) end end diff --git a/src/lib/DinoDefines.h b/src/lib/DinoDefines.h index 8e630eed..daca06b6 100644 --- a/src/lib/DinoDefines.h +++ b/src/lib/DinoDefines.h @@ -8,7 +8,6 @@ // #define DINO_SERVO // #define DINO_LCD // #define DINO_SERIAL -// #define DINO_DHT // #define DINO_ONE_WIRE // #define DINO_IR_OUT // #define DINO_TONE From 14b353245a71ddaedd790f8ac83554b507f52096 Mon Sep 17 00:00:00 2001 From: vickash Date: Thu, 22 Feb 2018 14:10:38 -0400 Subject: [PATCH 188/296] =?UTF-8?q?Pin=20wasn=E2=80=99t=20being=20set=20to?= =?UTF-8?q?=20ID=20servo=20&=20write=20microseconds=20instead=20now.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dino/api/servo.rb | 11 +++++++--- lib/dino/components/mixins/bus_master.rb | 9 +++----- lib/dino/components/servo.rb | 28 +++++++++++++++++++----- src/lib/DinoServo.cpp | 11 +++++++--- 4 files changed, 42 insertions(+), 17 deletions(-) diff --git a/lib/dino/api/servo.rb b/lib/dino/api/servo.rb index baae5b0e..487c4185 100644 --- a/lib/dino/api/servo.rb +++ b/lib/dino/api/servo.rb @@ -3,16 +3,21 @@ module API module Servo include Helper - def servo_toggle(pin, value=:off) + def servo_toggle(pin, value=:off, options={}) + options[:min] ||= 544 + options[:max] ||= 2400 + aux = pack :uint16, [options[:min], options[:max]] + write Message.encode command: 8, pin: convert_pin(pin), - value: (value == :off) ? 0 : 1 + value: (value == :off) ? 0 : 1, + aux: aux end def servo_write(pin, value=0) write Message.encode command: 9, pin: convert_pin(pin), - value: value + aux_message: pack(:uint16, value) end end end diff --git a/lib/dino/components/mixins/bus_master.rb b/lib/dino/components/mixins/bus_master.rb index a94514bc..731ffb4e 100644 --- a/lib/dino/components/mixins/bus_master.rb +++ b/lib/dino/components/mixins/bus_master.rb @@ -2,18 +2,15 @@ module Dino module Components module Mixins module BusMaster - attr_reader :mutex - - def after_initialize(options={}) - super(options) - @components = [] - @mutex = Mutex.new + def mutex + @mutex ||= Mutex.new end # Essential part of board interface that components need. attr_reader :components def add_component(component) + @components ||= [] @components << component end diff --git a/lib/dino/components/servo.rb b/lib/dino/components/servo.rb index 9e2a77f5..07eb77f3 100644 --- a/lib/dino/components/servo.rb +++ b/lib/dino/components/servo.rb @@ -5,18 +5,36 @@ class Servo include Mixins::Threaded def after_initialize(options={}) - board.servo_toggle(pin, :on) + super(options) + @min = options[:min] || 544 + @max = options[:max] || 2400 + attach + end + + def attach + board.servo_toggle(pin, :on, min: @min, max: @max) + end + + def detach + board.servo_toggle(pin, :off, min: @min, max: @max) end def position=(value) - @state = angle(value) - board.servo_write(pin, @state) + value = value % 180 unless value == 180 + + microseconds = ((value.to_f / 180) * (@max - @min)) + @min + board.servo_write(pin, microseconds.ceil) + + @state = value end + alias :angle= :position= + alias :angle :state alias :position :state - def angle(value) - value == 180 ? value : value % 180 + def write_microseconds(value) + raise 'invalud microsecond value' if value > @max || value < @min + board.servo_write(pin, value) end end end diff --git a/src/lib/DinoServo.cpp b/src/lib/DinoServo.cpp index 6691e353..631d0225 100644 --- a/src/lib/DinoServo.cpp +++ b/src/lib/DinoServo.cpp @@ -38,10 +38,13 @@ void Dino::servoToggle() { // Search by pin for in use servo object, attach and set active. else { boolean found = false; + uint16_t min = (auxMsg[1] << 8) | auxMsg[0]; + uint16_t max = (auxMsg[3] << 8) | auxMsg[2]; + for (int i = 0; i < SERVO_COUNT; i++) { if (servos[i].pin == pin) { found = true; - servos[i].servo.attach(pin); + servos[i].servo.attach(pin, min, max); servos[i].active = true; break; } @@ -50,8 +53,9 @@ void Dino::servoToggle() { if (found == false) { for (int i = 0; i < SERVO_COUNT; i++) { if (servos[i].active == false) { - servos[i].servo.attach(pin); + servos[i].servo.attach(pin, min, max); servos[i].active = true; + servos[i].pin = pin; break; } } @@ -69,7 +73,8 @@ void Dino::servoWrite() { // Find servo by pin and write value to it. for (int i = 0; i < SERVO_COUNT; i++) { if (servos[i].pin == pin) { - servos[i].servo.write(val); + uint16_t us = (auxMsg[1] << 8) | auxMsg[0]; + servos[i].servo.writeMicroseconds(us); } } From 2b2cc8d5d3256e036764690aba2afabd097efad8 Mon Sep 17 00:00:00 2001 From: vickash Date: Thu, 22 Feb 2018 14:30:43 -0400 Subject: [PATCH 189/296] Add Servo#speed= for continuous rotation servos. Maps from range -100..100 to @min..@max (minimum and maximum specified microsecond pulse values for the servo). --- lib/dino/components/servo.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/dino/components/servo.rb b/lib/dino/components/servo.rb index 07eb77f3..3eb609ad 100644 --- a/lib/dino/components/servo.rb +++ b/lib/dino/components/servo.rb @@ -28,9 +28,19 @@ def position=(value) @state = value end + def speed=(value) + raise 'invalud speed value' if value > 100 || value < -100 + + microseconds = (((value.to_f + 100) / 200) * (@max - @min)) + @min + board.servo_write(pin, microseconds.ceil) + + @state = value + end + alias :angle= :position= alias :angle :state alias :position :state + alias :speed :state def write_microseconds(value) raise 'invalud microsecond value' if value > @max || value < @min From 2d44bad90403b214319a9aafed0ec9b98f732d1a Mon Sep 17 00:00:00 2001 From: vickash Date: Thu, 8 Mar 2018 13:24:31 -0400 Subject: [PATCH 190/296] Switch from RSpec to MiniTest --- .travis.yml | 2 +- Rakefile | 13 +- dino.gemspec | 2 +- examples/rgb_led/rgb_led.rb | 2 +- lib/dino/api/core.rb | 6 +- lib/dino/board/base.rb | 8 +- lib/dino/board/default.rb | 4 +- lib/dino/board/esp8266.rb | 8 +- lib/dino/components.rb | 2 +- lib/dino/components/basic/analog_input.rb | 2 +- lib/dino/components/basic/digital_input.rb | 2 +- lib/dino/components/i2c/bus.rb | 6 +- lib/dino/components/lcd.rb | 28 +-- lib/dino/components/mixins/bus_master.rb | 9 +- lib/dino/components/mixins/callbacks.rb | 28 ++- lib/dino/components/mixins/poller.rb | 2 +- lib/dino/components/one_wire/bus.rb | 3 +- .../components/one_wire/bus_enumerator.rb | 4 + lib/dino/components/one_wire/slave.rb | 2 +- lib/dino/components/register/input.rb | 4 +- lib/dino/components/rgb_led.rb | 15 +- lib/dino/components/servo.rb | 4 +- lib/dino/components/setup/base.rb | 18 +- lib/dino/components/setup/multi_pin.rb | 4 +- lib/dino/components/setup/single_pin.rb | 7 +- lib/dino/components/stepper.rb | 8 +- lib/dino/message.rb | 19 +- spec/lib/board_spec.rb | 229 ------------------ .../lib/components/basic/analog_input_spec.rb | 35 --- .../components/basic/analog_output_spec.rb | 39 --- .../components/basic/digital_input_spec.rb | 59 ----- .../components/basic/digital_output_spec.rb | 57 ----- spec/lib/components/ir_emitter_spec.rb | 27 --- spec/lib/components/lcd_spec.rb | 127 ---------- spec/lib/components/led_spec.rb | 9 - spec/lib/components/mixins/callbacks_spec.rb | 100 -------- spec/lib/components/mixins/listener_spec.rb | 58 ----- spec/lib/components/mixins/poller_spec.rb | 66 ----- spec/lib/components/mixins/reader_spec.rb | 78 ------ spec/lib/components/mixins/threaded_spec.rb | 118 --------- spec/lib/components/one_wire/ds18b20_spec.rb | 66 ----- .../components/one_wire/enumerator_spec.rb | 140 ----------- spec/lib/components/one_wire/slave_spec.rb | 125 ---------- spec/lib/components/register/select_spec.rb | 30 --- spec/lib/components/register/shift_in_spec.rb | 87 ------- .../lib/components/register/shift_out_spec.rb | 40 --- spec/lib/components/rgb_led_spec.rb | 65 ----- spec/lib/components/sensor_spec.rb | 8 - spec/lib/components/servo_spec.rb | 42 ---- spec/lib/components/setup/base_spec.rb | 30 --- spec/lib/components/setup/input_spec.rb | 39 --- spec/lib/components/setup/multi_pin_spec.rb | 73 ------ spec/lib/components/setup/output_spec.rb | 25 -- spec/lib/components/setup/single_pin_spec.rb | 39 --- spec/lib/components/softwareserial_spec.rb | 23 -- spec/lib/components/ssd_spec.rb | 78 ------ spec/lib/components/stepper_spec.rb | 39 --- spec/lib/message_spec.rb | 47 ---- spec/lib/tx_rx/serial_spec.rb | 149 ------------ spec/lib/tx_rx/tcp_spec.rb | 37 --- spec/spec_helper.rb | 24 -- src/lib/Dino.cpp | 1 + test/api/core_test.rb | 136 +++++++++++ test/board_mock.rb | 8 + test/board_test.rb | 113 +++++++++ test/components/basic/analog_input_test.rb | 40 +++ test/components/basic/analog_output_test.rb | 41 ++++ test/components/basic/digital_input_test.rb | 61 +++++ test/components/basic/digital_output_test.rb | 59 +++++ test/components/ir_emitter_test.rb | 29 +++ test/components/lcd_test.rb | 49 ++++ test/components/led_test.rb | 21 ++ test/components/mixins/callbacks_test.rb | 77 ++++++ test/components/mixins/listener_test.rb | 57 +++++ test/components/mixins/poller_test.rb | 66 +++++ test/components/mixins/reader_test.rb | 56 +++++ test/components/mixins/threaded_test.rb | 100 ++++++++ test/components/one_wire/ds18b20_test.rb | 80 ++++++ test/components/one_wire/enumerator_test.rb | 141 +++++++++++ test/components/one_wire/helper_test.rb | 1 + test/components/one_wire/slave_test.rb | 151 ++++++++++++ test/components/register/select_test.rb | 26 ++ test/components/register/shift_in_test.rb | 70 ++++++ test/components/register/shift_out_test.rb | 33 +++ test/components/rgb_led_test.rb | 66 +++++ test/components/servo_test.rb | 63 +++++ test/components/setup/base_test.rb | 22 ++ test/components/setup/input_test.rb | 42 ++++ test/components/setup/multi_pin_test.rb | 61 +++++ test/components/setup/output_test.rb | 25 ++ test/components/setup/single_pin_test.rb | 21 ++ test/components/software_serial_test.rb | 33 +++ test/components/ssd_test.rb | 82 +++++++ test/components/stepper_test.rb | 49 ++++ test/message_test.rb | 44 ++++ test/test_helper.rb | 29 +++ test/txrx/serial_test.rb | 159 ++++++++++++ test/txrx/tcp_test.rb | 43 ++++ test/txrx_mock.rb | 10 + 99 files changed, 2290 insertions(+), 2295 deletions(-) delete mode 100644 spec/lib/board_spec.rb delete mode 100644 spec/lib/components/basic/analog_input_spec.rb delete mode 100644 spec/lib/components/basic/analog_output_spec.rb delete mode 100644 spec/lib/components/basic/digital_input_spec.rb delete mode 100644 spec/lib/components/basic/digital_output_spec.rb delete mode 100644 spec/lib/components/ir_emitter_spec.rb delete mode 100644 spec/lib/components/lcd_spec.rb delete mode 100644 spec/lib/components/led_spec.rb delete mode 100644 spec/lib/components/mixins/callbacks_spec.rb delete mode 100644 spec/lib/components/mixins/listener_spec.rb delete mode 100644 spec/lib/components/mixins/poller_spec.rb delete mode 100644 spec/lib/components/mixins/reader_spec.rb delete mode 100644 spec/lib/components/mixins/threaded_spec.rb delete mode 100644 spec/lib/components/one_wire/ds18b20_spec.rb delete mode 100644 spec/lib/components/one_wire/enumerator_spec.rb delete mode 100644 spec/lib/components/one_wire/slave_spec.rb delete mode 100644 spec/lib/components/register/select_spec.rb delete mode 100644 spec/lib/components/register/shift_in_spec.rb delete mode 100644 spec/lib/components/register/shift_out_spec.rb delete mode 100644 spec/lib/components/rgb_led_spec.rb delete mode 100644 spec/lib/components/sensor_spec.rb delete mode 100644 spec/lib/components/servo_spec.rb delete mode 100644 spec/lib/components/setup/base_spec.rb delete mode 100644 spec/lib/components/setup/input_spec.rb delete mode 100644 spec/lib/components/setup/multi_pin_spec.rb delete mode 100644 spec/lib/components/setup/output_spec.rb delete mode 100644 spec/lib/components/setup/single_pin_spec.rb delete mode 100644 spec/lib/components/softwareserial_spec.rb delete mode 100644 spec/lib/components/ssd_spec.rb delete mode 100644 spec/lib/components/stepper_spec.rb delete mode 100644 spec/lib/message_spec.rb delete mode 100644 spec/lib/tx_rx/serial_spec.rb delete mode 100644 spec/lib/tx_rx/tcp_spec.rb delete mode 100644 spec/spec_helper.rb create mode 100644 test/api/core_test.rb create mode 100644 test/board_mock.rb create mode 100644 test/board_test.rb create mode 100644 test/components/basic/analog_input_test.rb create mode 100644 test/components/basic/analog_output_test.rb create mode 100644 test/components/basic/digital_input_test.rb create mode 100644 test/components/basic/digital_output_test.rb create mode 100644 test/components/ir_emitter_test.rb create mode 100644 test/components/lcd_test.rb create mode 100644 test/components/led_test.rb create mode 100644 test/components/mixins/callbacks_test.rb create mode 100644 test/components/mixins/listener_test.rb create mode 100644 test/components/mixins/poller_test.rb create mode 100644 test/components/mixins/reader_test.rb create mode 100644 test/components/mixins/threaded_test.rb create mode 100644 test/components/one_wire/ds18b20_test.rb create mode 100644 test/components/one_wire/enumerator_test.rb create mode 100644 test/components/one_wire/helper_test.rb create mode 100644 test/components/one_wire/slave_test.rb create mode 100644 test/components/register/select_test.rb create mode 100644 test/components/register/shift_in_test.rb create mode 100644 test/components/register/shift_out_test.rb create mode 100644 test/components/rgb_led_test.rb create mode 100644 test/components/servo_test.rb create mode 100644 test/components/setup/base_test.rb create mode 100644 test/components/setup/input_test.rb create mode 100644 test/components/setup/multi_pin_test.rb create mode 100644 test/components/setup/output_test.rb create mode 100644 test/components/setup/single_pin_test.rb create mode 100644 test/components/software_serial_test.rb create mode 100644 test/components/ssd_test.rb create mode 100644 test/components/stepper_test.rb create mode 100644 test/message_test.rb create mode 100644 test/test_helper.rb create mode 100644 test/txrx/serial_test.rb create mode 100644 test/txrx/tcp_test.rb create mode 100644 test/txrx_mock.rb diff --git a/.travis.yml b/.travis.yml index 0627979d..46f3b26c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,4 +8,4 @@ rvm: - 2.0.0 - jruby-9.1.15.0 -script: bundle exec rspec spec +script: bundle exec rake test diff --git a/Rakefile b/Rakefile index 133cb3ef..9ff320d9 100644 --- a/Rakefile +++ b/Rakefile @@ -1,10 +1,11 @@ #!/usr/bin/env rake require "bundler/gem_tasks" +require 'rake/testtask' -task :default => [:spec] - -require 'rspec/core/rake_task' -desc "Run specs" -RSpec::Core::RakeTask.new do |t| - t.pattern = 'spec/**/*_spec.rb' +task :default => [:test] +Rake::TestTask.new do |t| + t.libs << "lib" + t.libs << "test" + t.warning = false + t.test_files = FileList['test/**/*_test.rb'] end diff --git a/dino.gemspec b/dino.gemspec index d2f2a53b..7d9d24cb 100644 --- a/dino.gemspec +++ b/dino.gemspec @@ -38,5 +38,5 @@ Gem::Specification.new do |gem| gem.add_dependency 'bcd', '~> 0.3.0' gem.add_development_dependency 'rake' - gem.add_development_dependency 'rspec' + gem.add_development_dependency 'minitest' end diff --git a/examples/rgb_led/rgb_led.rb b/examples/rgb_led/rgb_led.rb index c72c4d51..20e45031 100644 --- a/examples/rgb_led/rgb_led.rb +++ b/examples/rgb_led/rgb_led.rb @@ -6,7 +6,7 @@ require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) -led = Dino::Components::RgbLed.new(pins: {red: 11, green: 10, blue: 9}, board: board) +led = Dino::Components::RGBLed.new(pins: {red: 11, green: 10, blue: 9}, board: board) potentiometer = Dino::Components::Sensor.new(pin: 'A0', board: board) diff --git a/lib/dino/api/core.rb b/lib/dino/api/core.rb index bbda7e56..14eadb3a 100644 --- a/lib/dino/api/core.rb +++ b/lib/dino/api/core.rb @@ -8,9 +8,9 @@ module Core # CMD = 0 def set_pin_mode(pin, mode) pin, value = convert_pin(pin), mode == :out ? 0 : 1 - write Dino::Message.encode command: 0, - pin: convert_pin(pin), - value: value + write Message.encode command: 0, + pin: convert_pin(pin), + value: value end # CMD = 1 diff --git a/lib/dino/board/base.rb b/lib/dino/board/base.rb index d70c6aa1..df99bc38 100644 --- a/lib/dino/board/base.rb +++ b/lib/dino/board/base.rb @@ -10,13 +10,19 @@ def initialize(io, options={}) ack = io.handshake.split(",").map { |num| num.to_i } @aux_limit, @analog_zero, @dac_zero = ack + # Leave room for null termination. + @aux_limit = @aux_limit - 1 io.add_observer(self) self.analog_resolution = options[:bits] || 8 end + def analog_resolution + @bits ||= 8 + end + def analog_resolution=(value) - @bits = value || 8 + @bits = value write Dino::Message.encode(command: 96, value: @bits) @low = 0 @high = 1 diff --git a/lib/dino/board/default.rb b/lib/dino/board/default.rb index 076a5121..57d19b91 100644 --- a/lib/dino/board/default.rb +++ b/lib/dino/board/default.rb @@ -18,7 +18,7 @@ def convert_pin(pin) return pin.to_i if pin.match(DIGITAL_REGEX) return analog_pin_to_i(pin) if pin.match(ANALOG_REGEX) return dac_pin_to_i(pin) if pin.match(DAC_REGEX) - raise "Incorrect pin format" + raise ArgumentError, "incorrect pin format" end def analog_pin_to_i(pin) @@ -26,7 +26,7 @@ def analog_pin_to_i(pin) end def dac_pin_to_i(pin) - raise "The board did not specify any DAC pins" unless @dac_zero + raise ArgumentError, "board does not specify DAC pins" unless @dac_zero @dac_zero + pin.gsub(/\Adac/i, '').to_i end end diff --git a/lib/dino/board/esp8266.rb b/lib/dino/board/esp8266.rb index a50fbd53..7ebca523 100644 --- a/lib/dino/board/esp8266.rb +++ b/lib/dino/board/esp8266.rb @@ -34,26 +34,26 @@ def convert_pin(pin) return gpio_pin_to_i(pin) if pin.match(GPIO_REGEX) return analog_pin_to_i(pin) if pin.match(ANALOG_REGEX) return digital_pin_to_i(pin) if pin.match(DIGITAL_REGEX) - raise "Incorrect pin format" + raise ArgumentError, "incorrect pin format" end # Only one analog in on the ESP8266, GPIO 17. def analog_pin_to_i(pin) gpio = pin.gsub(/\Aa/i, '').to_i - raise "Invalid analog input pin" unless gpio == 0 + raise ArgumentError, "invalid analog input pin" unless gpio == 0 17 end def gpio_pin_to_i(pin) gpio = pin.gsub(/\Agpio/i, '').to_i - raise "Invalid GPIO input pin" if (gpio < 0 || gpio > 17) + raise ArgumentError, "invalid GPIO pin" if (gpio < 0 || gpio > 17) gpio end def digital_pin_to_i(pin) d_index = pin.gsub(/\Ad/i, '').to_i gpio = D_PIN_MAP[d_index] - raise "Invalid D pin" unless gpio + raise ArgumentError, "invalid D pin" unless gpio gpio end end diff --git a/lib/dino/components.rb b/lib/dino/components.rb index f8221d63..a29c5a45 100644 --- a/lib/dino/components.rb +++ b/lib/dino/components.rb @@ -9,7 +9,7 @@ module Components autoload :Led, 'dino/components/led' autoload :Button, 'dino/components/button' autoload :Sensor, 'dino/components/sensor' - autoload :RgbLed, 'dino/components/rgb_led' + autoload :RGBLed, 'dino/components/rgb_led' autoload :Servo, 'dino/components/servo' autoload :SSD, 'dino/components/ssd' autoload :Stepper, 'dino/components/stepper' diff --git a/lib/dino/components/basic/analog_input.rb b/lib/dino/components/basic/analog_input.rb index ff977c43..86b3c54b 100644 --- a/lib/dino/components/basic/analog_input.rb +++ b/lib/dino/components/basic/analog_input.rb @@ -13,7 +13,7 @@ def _read end def _listen(divider=16) - @divider ||= 16 + @divider = divider || 16 board.analog_listen(pin, @divider) end end diff --git a/lib/dino/components/basic/digital_input.rb b/lib/dino/components/basic/digital_input.rb index 002125fb..3ef4558a 100644 --- a/lib/dino/components/basic/digital_input.rb +++ b/lib/dino/components/basic/digital_input.rb @@ -18,7 +18,7 @@ def _read end def _listen(divider=4) - @divider ||= 4 + @divider = divider || 4 board.digital_listen(pin, @divider) end diff --git a/lib/dino/components/i2c/bus.rb b/lib/dino/components/i2c/bus.rb index 6e9496fc..c36db726 100644 --- a/lib/dino/components/i2c/bus.rb +++ b/lib/dino/components/i2c/bus.rb @@ -19,7 +19,7 @@ def search end def write(address, bytes=[], options={}) - board.i2c_write(address, bytes, options) + board.i2c_write(address, [bytes].flatten, options) end def read(*args) @@ -32,7 +32,7 @@ def _read(address, register, num_bytes=1, options={}) def bubble_callbacks add_callback(:bus_master) do |str| - if str.match /d*-/ + if str.match(/d*-/) address, data = str.split("-", 2) address = address.to_i data = data.split(",").map(&:to_i) @@ -42,7 +42,7 @@ def bubble_callbacks end def update_component(hash) - @components.each do |component| + components.each do |component| if component.address == hash[:address] component.update(hash[:data]) end diff --git a/lib/dino/components/lcd.rb b/lib/dino/components/lcd.rb index 6d4d85b2..81778671 100644 --- a/lib/dino/components/lcd.rb +++ b/lib/dino/components/lcd.rb @@ -29,20 +29,20 @@ def after_initialize(options) end LIBRARY_COMMANDS = { - clear: '2', - home: '3', - show_cursor: '6', - hide_cursor: '7', - blink: '8', - no_blink: '9', - on: '10', - off: '11', - scroll_left: '12', - scroll_right: '13', - enable_autoscroll: '14', - disable_autoscroll:'15', - left_to_right: '16', - right_to_left: '17' + clear: 2, + home: 3, + show_cursor: 6, + hide_cursor: 7, + blink: 8, + no_blink: 9, + on: 10, + off: 11, + scroll_left: 12, + scroll_right: 13, + enable_autoscroll: 14, + disable_autoscroll:15, + left_to_right: 16, + right_to_left: 17 } LIBRARY_COMMANDS.each_pair do |command, command_id| diff --git a/lib/dino/components/mixins/bus_master.rb b/lib/dino/components/mixins/bus_master.rb index 731ffb4e..3cddfa41 100644 --- a/lib/dino/components/mixins/bus_master.rb +++ b/lib/dino/components/mixins/bus_master.rb @@ -7,15 +7,16 @@ def mutex end # Essential part of board interface that components need. - attr_reader :components + def components + @components ||= [] + end def add_component(component) - @components ||= [] - @components << component + components << component end def remove_component(component) - @components.delete(component) + components.delete(component) end end end diff --git a/lib/dino/components/mixins/callbacks.rb b/lib/dino/components/mixins/callbacks.rb index cfcbf51f..13b84f27 100644 --- a/lib/dino/components/mixins/callbacks.rb +++ b/lib/dino/components/mixins/callbacks.rb @@ -2,6 +2,9 @@ module Dino module Components module Mixins module Callbacks + attr_reader :callback_mutex, :callbacks + attr_writer :callbacks + def after_initialize(options={}) super(options) @callbacks = {} @@ -9,14 +12,14 @@ def after_initialize(options={}) end def add_callback(key=:persistent, &block) - @callback_mutex.synchronize do + callback_mutex.synchronize do @callbacks[key] ||= [] @callbacks[key] << block end end def remove_callback(key=nil) - @callback_mutex.synchronize do + callback_mutex.synchronize do key ? @callbacks.delete(key) : @callbacks = {} end end @@ -26,13 +29,17 @@ def remove_callback(key=nil) def update(data) data = pre_callback_filter(data) - @callback_mutex.synchronize do - @callbacks.each_value do |array| - array.each { |callback| callback.call(data) } + + callback_mutex.synchronize do + callbacks.each_value do |array| + array.each do |callback| + callback.call(data) + end end - # Remove the special :read callback while still inside the lock. - @callbacks.delete(:read) + # Remove special :read callback before unlocking. + callbacks.delete(:read) end + update_self(data) end @@ -41,10 +48,9 @@ def pre_callback_filter(data) data end - # Set @state to the value passed to callbacks after running them all. - # Override if some other behavior is needed. - def update_self(data) - @state = data + # Override if behavior other than @state = filtered data is needed. + def update_self(filtered_data) + self.state = filtered_data end end end diff --git a/lib/dino/components/mixins/poller.rb b/lib/dino/components/mixins/poller.rb index 770ff6f4..8f9fb4b9 100644 --- a/lib/dino/components/mixins/poller.rb +++ b/lib/dino/components/mixins/poller.rb @@ -16,7 +16,7 @@ def poll_using(method, interval=1, &block) end def poll(interval=1, &block) - poll_using(self.method(:_read), interval, &block) + poll_using(method(:_read), interval, &block) end def stop diff --git a/lib/dino/components/one_wire/bus.rb b/lib/dino/components/one_wire/bus.rb index 75ed0947..7b3b81b6 100644 --- a/lib/dino/components/one_wire/bus.rb +++ b/lib/dino/components/one_wire/bus.rb @@ -6,11 +6,10 @@ class Bus include Mixins::BusMaster include Mixins::Reader - attr_reader :found_devices, :parasite_power + attr_reader :parasite_power def after_initialize(options = {}) super(options) - @found_devices = [] read_power_supply end diff --git a/lib/dino/components/one_wire/bus_enumerator.rb b/lib/dino/components/one_wire/bus_enumerator.rb index a4f6fc6e..bf277377 100644 --- a/lib/dino/components/one_wire/bus_enumerator.rb +++ b/lib/dino/components/one_wire/bus_enumerator.rb @@ -8,6 +8,10 @@ def _search(branch_mask) board.one_wire_search(pin, branch_mask) end + def found_devices + @found_devices ||= [] + end + def search @found_devices = [] branch_mask = 0 diff --git a/lib/dino/components/one_wire/slave.rb b/lib/dino/components/one_wire/slave.rb index 4df09665..ab92f7db 100644 --- a/lib/dino/components/one_wire/slave.rb +++ b/lib/dino/components/one_wire/slave.rb @@ -21,7 +21,7 @@ def write_scratch(*bytes) atomically do match bus.write(WRITE_SCRATCH) - bus.write(bytes) + bus.write(*bytes) end end diff --git a/lib/dino/components/register/input.rb b/lib/dino/components/register/input.rb index bd92a8c2..3a7f670d 100644 --- a/lib/dino/components/register/input.rb +++ b/lib/dino/components/register/input.rb @@ -5,6 +5,8 @@ module Input include Setup::Base include Mixins::Callbacks + attr_reader :bytes + def after_initialize(options={}) super(options) if defined?(super) # @@ -118,7 +120,7 @@ def stop def enable_proxy self.add_callback(:board_proxy) do |bit_array| bit_array.each_with_index do |value, pin| - @components.each do |part| + components.each do |part| update_component(part, pin, value) if pin == part.pin end @reading_pins[pin] = false diff --git a/lib/dino/components/rgb_led.rb b/lib/dino/components/rgb_led.rb index 2c92cc08..5ba14252 100644 --- a/lib/dino/components/rgb_led.rb +++ b/lib/dino/components/rgb_led.rb @@ -1,12 +1,12 @@ module Dino module Components - class RgbLed + class RGBLed include Setup::MultiPin proxy_pins red: Basic::AnalogOutput, green: Basic::AnalogOutput, - blue: Basic::AnalogOutput - + blue: Basic::AnalogOutput + # Format: [R, G, B] COLORS = { red: [255, 000, 000], @@ -27,17 +27,10 @@ def write(array) def color=(color) return write(color) if color.class == Array - + color = color.to_sym write(COLORS[color]) if COLORS.include? color end - - def cycle - [:red, :green, :blue].cycle do |color| - self.color = color - sleep(0.01) - end - end end end end diff --git a/lib/dino/components/servo.rb b/lib/dino/components/servo.rb index 3eb609ad..4768347a 100644 --- a/lib/dino/components/servo.rb +++ b/lib/dino/components/servo.rb @@ -23,7 +23,7 @@ def position=(value) value = value % 180 unless value == 180 microseconds = ((value.to_f / 180) * (@max - @min)) + @min - board.servo_write(pin, microseconds.ceil) + board.servo_write(pin, microseconds.round) @state = value end @@ -32,7 +32,7 @@ def speed=(value) raise 'invalud speed value' if value > 100 || value < -100 microseconds = (((value.to_f + 100) / 200) * (@max - @min)) + @min - board.servo_write(pin, microseconds.ceil) + board.servo_write(pin, microseconds.round) @state = value end diff --git a/lib/dino/components/setup/base.rb b/lib/dino/components/setup/base.rb index d194c255..802ef181 100644 --- a/lib/dino/components/setup/base.rb +++ b/lib/dino/components/setup/base.rb @@ -2,9 +2,14 @@ module Dino module Components module Setup module Base - attr_reader :board, :state + attr_reader :board + + def state + @state_mutex.synchronize { @state } + end def initialize(options={}) + @state_mutex = Mutex.new initialize_board(options) initialize_pins(options) register @@ -13,11 +18,16 @@ def initialize(options={}) protected - attr_writer :board, :state + def state=(value) + @state_mutex.synchronize { @state = value } + end def initialize_board(options={}) - raise 'a board is required for a component' unless options[:board] - self.board = options[:board] + if options[:board] + @board = options[:board] + else + raise ArgumentError, 'a board is required for a component' + end end def register diff --git a/lib/dino/components/setup/multi_pin.rb b/lib/dino/components/setup/multi_pin.rb index 9a0baa2d..9cf1c440 100644 --- a/lib/dino/components/setup/multi_pin.rb +++ b/lib/dino/components/setup/multi_pin.rb @@ -81,7 +81,9 @@ def initialize_pins(options={}) def validate_pins required_pins = self.class.class_eval('@@required_pins') rescue [] - required_pins.each { |key| raise "missing pins[:#{key}] pin" unless pins[key] } + required_pins.each do |key| + raise ArgumentError, 'missing pins[:#{key}] pin' unless pins[key] + end end def build_proxies diff --git a/lib/dino/components/setup/single_pin.rb b/lib/dino/components/setup/single_pin.rb index 09e2f4ed..4ae5286f 100644 --- a/lib/dino/components/setup/single_pin.rb +++ b/lib/dino/components/setup/single_pin.rb @@ -10,8 +10,11 @@ module SinglePin attr_writer :pin def initialize_pins(options={}) - raise 'a pin is required for this component' unless options[:pin] - self.pin = board.convert_pin(options[:pin]) + if options[:pin] + self.pin = options[:pin] + else + raise ArgumentError, 'a pin is required for this component' + end end def mode=(mode) diff --git a/lib/dino/components/stepper.rb b/lib/dino/components/stepper.rb index 3f739f76..4d355535 100644 --- a/lib/dino/components/stepper.rb +++ b/lib/dino/components/stepper.rb @@ -1,11 +1,11 @@ module Dino module Components class Stepper - include Setup::MultiPin - + include Setup::MultiPin + proxy_pins step: Basic::DigitalOutput, direction: Basic::DigitalOutput - + def step_cc direction.high step.high @@ -17,6 +17,8 @@ def step_cw step.high step.low end + + alias :step_ccw :step_cc end end end diff --git a/lib/dino/message.rb b/lib/dino/message.rb index 91fa9959..67fa9445 100644 --- a/lib/dino/message.rb +++ b/lib/dino/message.rb @@ -1,5 +1,7 @@ module Dino module Message + BYTE_RANGE = (0..255) + def self.encode(options={}) cmd = options[:command] pin = options[:pin] @@ -7,11 +9,18 @@ def self.encode(options={}) aux = options[:aux_message] aux = aux.to_s.gsub("\\","\\\\\\\\").gsub("\n", "\\\n") if aux - raise Exception.new('command must be specified') unless cmd - raise Exception.new('commands can only be four digits') if cmd.to_s.length > 4 - raise Exception.new('pins can only be four digits') if pin.to_s.length > 4 - raise Exception.new('values can only be four digits') if val.to_s.length > 4 - raise Exception.new('auxillary messages are limited to 512 characters') if aux.to_s.length > 512 + unless cmd && BYTE_RANGE.include?(cmd) + raise ArgumentError, 'command missing or not integer in range 0 to 255' + end + if pin && !BYTE_RANGE.include?(pin) + raise ArgumentError, 'pin must be integer in range 0 to 255' + end + if val && !BYTE_RANGE.include?(val) + raise ArgumentError, 'value must be integer in range 0 to 255' + end + if aux.to_s.length > 512 + raise ArgumentError, 'auxillary messages are limited to 512 characters' + end message = "" [aux, val, pin].each do |fragment| diff --git a/spec/lib/board_spec.rb b/spec/lib/board_spec.rb deleted file mode 100644 index 381fbb07..00000000 --- a/spec/lib/board_spec.rb +++ /dev/null @@ -1,229 +0,0 @@ -require 'spec_helper' - -module Dino - describe Dino::Board do - def io_mock(methods = {}) - @io ||= double :io, { add_observer: true, - handshake: "528,14,20", - write: true, - read: true - }.merge(methods) - end - - subject { Board.new(io_mock) } - - describe '#initialize' do - it 'should require an io object' do - expect { Board.new() }.to raise_exception - end - - it 'should observe the io' do - expect(io_mock).to receive(:add_observer).with(subject) - subject.send(:initialize, io_mock) - end - - it 'should initiate the handshake' do - expect(io_mock).to receive(:handshake) - subject - end - - it 'should set @analog_zero' do - expect(subject.analog_zero).to equal(14) - end - - it 'should set @dac_zero' do - expect(subject.dac_zero).to equal(20) - end - - it 'should set digital high and low' do - board = Board.new(io_mock) - expect(board.low).to equal(0) - expect(board.high).to equal(1) - end - - it 'should set analog high separately' do - board = Board.new(io_mock) - expect(board.analog_high).to equal(255) - end - end - - describe '#update' do - it 'should pass messages from a pin to the right part' do - subject.add_component(part1 = double(pin: 7)) - subject.add_component(part2 = double(pin: 9)) - - expect(part1).to receive(:update).with('wake up!') - expect(part2).to_not receive(:update).with('wake up!') - subject.update(7, 'wake up!') - end - - it 'should silently ignore messages from a pin if there is no part on it' do - expect { subject.update(5, 'wake up!') }.to_not raise_exception - end - end - - describe '#add_component' do - it 'should put the part in the components array' do - subject.add_component(part = double) - expect(subject.components).to include(part) - end - end - - describe '#remove_component' do - it 'should remove the part from the components array and stop listening' do - subject.add_component(part = double(pin: 12)) - expect(subject).to receive(:stop_listener).with(12) - subject.remove_component(part) - expect(subject.components).to be_empty - end - end - - describe '#write' do - it 'should call #write on the io with the message' do - expect(io_mock).to receive(:write).with('message') - subject.write('message') - end - end - - describe '#convert_pin' do - before(:each) { subject.instance_variable_set(:@analog_zero, 14) } - - it 'should leave numeric pins as is' do - expect(subject.convert_pin '13').to equal(13) - end - - it 'should convert analog pins to numeric form' do - expect(subject.convert_pin 'A1').to equal(15) - end - - it 'should convert dac pins to numeric form' do - expect(subject.convert_pin 'DAC1').to equal(21) - end - - it 'should raise if trying to convert a dac pin and the board has none' do - subject.instance_variable_set(:@dac_zero, nil) - expect { subject.convert_pin 'DAC1' }.to raise_exception(/dac/i) - end - - it 'should raise if trying to convert a wrongly formatted pin' do - expect { subject.convert_pin 'ADC1' }.to raise_exception(/incorrect/i) - end - end - - # - # Board API Tests - # - describe '#set_pin_mode' do - it 'should send a value of 0 if the pin mode is set to out' do - expect(io_mock).to receive(:write).with(Dino::Message.encode(command: 0, pin: 13, value: 0)) - subject.set_pin_mode(13, :out) - end - - it 'should send a value of 1 if the pin mode is set to in' do - expect(io_mock).to receive(:write).with(Dino::Message.encode(command: 0, pin: 13, value: 1)) - subject.set_pin_mode(13, :in) - end - end - - describe '#set_pullup' do - it 'should write high if pullup is enabled' do - expect(io_mock).to receive(:write).with(Dino::Message.encode(command: 1, pin: 13, value: subject.high)) - subject.set_pullup(13, true) - end - - it 'should write low if pullup is disabled' do - expect(io_mock).to receive(:write).with(Dino::Message.encode(command: 1, pin: 13, value: subject.low)) - subject.set_pullup(13, false) - end - end - - describe '#digital_write' do - it 'should digitalWrite the value to the pin' do - expect(io_mock).to receive(:write).with(Dino::Message.encode command: 1, pin: 1, value: 255) - subject.digital_write(01, 255) - end - end - - describe '#digital_read' do - it 'should digitalRead once from the given pin' do - expect(io_mock).to receive(:write).with(Dino::Message.encode command: 2, pin: 13) - subject.digital_read(13) - end - end - - describe '#analog_write' do - it 'should analogWrite the value to the pin' do - expect(io_mock).to receive(:write).with(Dino::Message.encode command: 3, pin: 1, value: 3) - subject.analog_write(01, 3) - end - end - - describe '#analog_read' do - it 'should analogRead once from the given pin' do - expect(io_mock).to receive(:write).with(Dino::Message.encode command: 4, pin: 13) - subject.analog_read(13) - end - end - - describe '#set_listener' do - it 'should set the right pin' do - expect(io_mock).to receive(:write).with(Dino::Message.encode command: 7, pin: 11, value: 0b00000011) - subject.set_listener(11, :off) - end - it 'should set the right mode' do - expect(io_mock).to receive(:write).with(Dino::Message.encode command: 7, pin: 11, value: 0b01000011) - subject.set_listener(11, :off, mode: :analog) - end - it 'should set the right state' do - expect(io_mock).to receive(:write).with(Dino::Message.encode command: 7, pin: 11, value: 0b11000011) - subject.set_listener(11, :on, mode: :analog) - end - it 'should set the right divider' do - expect(io_mock).to receive(:write).with(Dino::Message.encode command: 7, pin: 11, value: 0b11000000) - subject.set_listener(11, :on, mode: :analog, divider: 1) - end - end - - describe '#digital_listen' do - it 'should start listening for a digital signal on the given pin' do - expect(subject).to receive(:set_listener).with(13, :on, mode: :digital, divider: 4) - subject.digital_listen(13) - end - end - - describe '#analog_listen' do - it 'should start listening for an analog signal on the given pin' do - expect(subject).to receive(:set_listener).with(13, :on, mode: :analog, divider: 16) - subject.analog_listen(13) - end - end - - describe '#stop_listener' do - it 'should stop listening for any signal on the given pin' do - expect(subject).to receive(:set_listener).with(13, :off) - subject.stop_listener(13) - end - end - - describe '#analog_resolution=' do - it 'should tell the board to change the resolution' do - expect(io_mock).to receive(:write).with(Dino::Message.encode command: 96, value: 10) - subject.analog_resolution = 10 - end - - it 'should set @bits' do - subject.analog_resolution = 12 - expect(subject.instance_variable_get(:@bits)).to equal(12) - end - - it 'should set @high and @low correctly' do - subject.analog_resolution = 8 - expect(subject.low).to equal(0) - expect(subject.analog_high).to equal(255) - - subject.analog_resolution = 10 - expect(subject.analog_high).to equal(1023) - end - end - end -end diff --git a/spec/lib/components/basic/analog_input_spec.rb b/spec/lib/components/basic/analog_input_spec.rb deleted file mode 100644 index e5c438d6..00000000 --- a/spec/lib/components/basic/analog_input_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module Basic - describe AnalogInput do - include BoardMock - let(:options) { { pin: 'A0', board: board } } - subject { AnalogInput.new(options) } - - describe '#_read' do - it 'should send #analog_read to the board with its pin' do - expect(board).to receive(:analog_read).with(subject.pin) - subject._read - end - end - - describe '#_listen' do - it 'should send #analog_listen to the board with its pin and divider' do - expect(board).to receive(:analog_listen).with(subject.pin, 16) - subject._listen - end - end - - - describe '#_stop_listener' do - it 'should send #stop_listener to the board with its pin' do - expect(board).to receive(:stop_listener).with(subject.pin) - subject._stop_listener - end - end - end - end - end -end diff --git a/spec/lib/components/basic/analog_output_spec.rb b/spec/lib/components/basic/analog_output_spec.rb deleted file mode 100644 index ea2eaf07..00000000 --- a/spec/lib/components/basic/analog_output_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module Basic - describe AnalogOutput do - include BoardMock - let(:options) { { pin: '9', board: board } } - subject { AnalogOutput.new(options) } - - describe '#analog_write' do - it 'should update the @state instance variable and call #analog_write on the board' do - expect(board).to receive(:analog_write).with(subject.pin, 128).once - - subject.analog_write(128) - expect(subject.state).to eq(128) - end - end - - describe '#write' do - it 'should call #digital_write with board.high if value is board.analog_high' do - expect(subject).to receive(:digital_write).with(board.high) - subject.write(board.analog_high) - end - - it 'should call #digital_write with board.low if value is board.low' do - expect(subject).to receive(:digital_write).with(board.low) - subject.write(board.low) - end - - it 'should call #analog_write if value is anything else' do - expect(subject).to receive(:analog_write).with(128) - subject.write(128) - end - end - end - end - end -end diff --git a/spec/lib/components/basic/digital_input_spec.rb b/spec/lib/components/basic/digital_input_spec.rb deleted file mode 100644 index dd82fde2..00000000 --- a/spec/lib/components/basic/digital_input_spec.rb +++ /dev/null @@ -1,59 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module Basic - describe DigitalInput do - include BoardMock - let(:options) { { pin: 'A0', board: board } } - subject { DigitalInput.new(options) } - - it 'should start listening immediately' do - expect(board).to receive(:digital_listen).with(14, 4) - component = DigitalInput.new(options) - end - - describe '#_read' do - it 'should call board#digital_read with its pin once' do - expect(board).to receive(:digital_read).with(subject.pin).once - subject._read - end - end - - describe '#_listen' do - it 'should call board#digital_listen with its pin once' do - expect(board).to receive(:digital_listen).with(subject.pin, 4).once - subject._listen - end - end - - context 'callbacks' do - before :each do - @low_callback = double - @high_callback = double - subject.on_low { @low_callback.called } - subject.on_high { @high_callback.called } - end - - describe '#on_low' do - it 'should add a callback that only gets fired when LOW' do - expect(@low_callback).to receive(:called) - expect(@high_callback).not_to receive(:called) - - subject.update(board.low) - end - end - - describe '#on_high' do - it 'should add a callback that only gets fired when HIGH' do - expect(@high_callback).to receive(:called) - expect(@low_callback).not_to receive(:called) - - subject.update(board.high) - end - end - end - end - end - end -end diff --git a/spec/lib/components/basic/digital_output_spec.rb b/spec/lib/components/basic/digital_output_spec.rb deleted file mode 100644 index ad032cfc..00000000 --- a/spec/lib/components/basic/digital_output_spec.rb +++ /dev/null @@ -1,57 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module Basic - describe DigitalOutput do - include BoardMock - let(:options) { { pin: '13', board: board } } - subject { DigitalOutput.new(options) } - - describe '#after_initialize' do - it 'should set mode to out and go low' do - expect(board).to receive(:digital_write).with(13, board.low) - subject - end - end - - describe '#digital_write' do - it 'should update the @state instance variable and call #digital_write on the board' do - subject - expect(board).to receive(:digital_write).with(subject.pin, board.high).once - subject.digital_write(board.high) - expect(subject.state).to eq(board.high) - end - end - - describe '#high' do - it 'should call #digital_write with HIGH' do - expect(subject).to receive(:digital_write).with(board.high) - subject.high - end - end - - describe '#low' do - it 'should call #digital_write with LOW' do - expect(subject).to receive(:digital_write).with(board.low) - subject.low - end - end - - describe '#toggle' do - it 'should call high if currently LOW' do - subject.low - expect(subject).to receive(:high) - subject.toggle - end - - it 'should call LOW if anything else' do - subject.high - expect(subject).to receive(:low) - subject.toggle - end - end - end - end - end -end diff --git a/spec/lib/components/ir_emitter_spec.rb b/spec/lib/components/ir_emitter_spec.rb deleted file mode 100644 index f4331f41..00000000 --- a/spec/lib/components/ir_emitter_spec.rb +++ /dev/null @@ -1,27 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - describe IREmitter do - include BoardMock - let(:options) { { board: board, pin: 3 } } - subject { IREmitter.new(options) } - - describe '#send' do - before(:each) { subject } - - it 'should send messages in the request format the board expects' do - expect(board).to receive(:write).with "16.3.38.#{[4].pack('C')}#{[100,200,300,400].pack('v*')}\n" - - subject.send([100,200,300,400]) - end - - it 'should put modulation frequency (kHz) in the value field if given as option to #send' do - expect(board).to receive(:write).with "16.3.40.#{[4].pack('C')}#{[100,200,300,400].pack('v*')}\n" - - subject.send([100,200,300,400], frequency: 40) - end - end - end - end -end diff --git a/spec/lib/components/lcd_spec.rb b/spec/lib/components/lcd_spec.rb deleted file mode 100644 index 9b909707..00000000 --- a/spec/lib/components/lcd_spec.rb +++ /dev/null @@ -1,127 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - describe LCD do - include BoardMock - subject { LCD.new board: board, pins: { rs: 12, enable: 11, d4: 5, d5: 4, d6: 3, d7: 2 }, cols: 16, rows: 2 } - - before do - expect(board).to receive(:write).with("10..0.12,11,5,4,3,2\n") - expect(board).to receive(:write).with("10..1.16,2\n") - end - - describe '#clear' do - it 'clears the display' do - expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 2) - subject.clear - end - end - - describe '#home' do - it 'Moves the cursor to the first position' do - expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 3) - subject.home - end - end - - describe '#set_cursor' do - it 'moves the cursor to the given position' do - expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 4, aux_message: "0,1") - subject.set_cursor(0,1) - end - end - - describe '#puts' do - it 'prints a string in the display' do - expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 5, aux_message: "AB") - subject.puts("AB") - end - end - - describe '#show_cursor' do - it 'shows the cursor' do - expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 6) - subject.show_cursor - end - end - - describe '#hide_cursor' do - it 'hides the cursor' do - expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 7) - subject.hide_cursor - end - end - - describe '#blink' do - it 'shows a blinking cursor' do - expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 8) - subject.blink - end - end - - describe '#no_blink' do - it 'stops a blinking cursor' do - expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 9) - subject.no_blink - end - end - - describe '#on' do - it 'turn on the display' do - expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 10) - subject.on - end - end - - describe '#off' do - it 'turn off the display' do - expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 11) - subject.off - end - end - - describe '#scroll_left' do - it 'move the text in the display one position to the left' do - expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 12) - subject.scroll_left - end - end - - describe '#scroll_right' do - it 'move the text in the display one position to the right' do - expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 13) - subject.scroll_right - end - end - - describe '#enable_autoscroll' do - it 'enable autoscroll' do - expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 14) - subject.enable_autoscroll - end - end - - describe '#disable_autoscroll' do - it 'disable autoscroll' do - expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 15) - subject.disable_autoscroll - end - end - - describe '#left_to_right' do - it 'set the display writing to start from the left' do - expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 16) - subject.left_to_right - end - end - - describe '#right_to_left' do - it 'set the display writing to start from the right' do - expect(board).to receive(:write).with Dino::Message.encode(command: 10, value: 17) - subject.right_to_left - end - end - end - end -end diff --git a/spec/lib/components/led_spec.rb b/spec/lib/components/led_spec.rb deleted file mode 100644 index 32317773..00000000 --- a/spec/lib/components/led_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - describe Led do - it 'should start blinking in a new thread at the given interval' - end - end -end diff --git a/spec/lib/components/mixins/callbacks_spec.rb b/spec/lib/components/mixins/callbacks_spec.rb deleted file mode 100644 index 188bbf75..00000000 --- a/spec/lib/components/mixins/callbacks_spec.rb +++ /dev/null @@ -1,100 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module Mixins - describe Callbacks do - - class CallbackComponent - include Setup::Base - include Callbacks - def initialize; after_initialize; end - end - subject { CallbackComponent.new } - - describe '#initialize' do - it 'should have an empty hash to hold callbacks' do - expect(subject.instance_variable_get :@callbacks).to eq({}) - end - - it 'should have a mutex to protect access to the hash' do - expect(subject.instance_variable_get(:@callback_mutex).class).to equal(Mutex) - end - end - - describe '#add_callback' do - it 'should atomically add a callback to the hash' do - expect(subject.instance_variable_get :@callback_mutex).to receive(:synchronize).once.and_yield - callback = Proc.new{} - subject.add_callback(&callback) - expect(subject.instance_variable_get :@callbacks).to eq({persistent: [callback]}) - end - - it 'should add callbacks under arbitrary keys' do - callback = Proc.new{} - subject.add_callback(:key, &callback) - expect(subject.instance_variable_get :@callbacks).to eq({key: [callback]}) - end - end - - context 'with callbacks added' do - before :each do - @callback1 = Proc.new{} - @callback2 = Proc.new{} - subject.add_callback(&@callback1) - subject.add_callback(:read, &@callback2) - end - - describe '#remove_callback' do - it 'should remove all callbacks if no key given' do - subject.remove_callbacks - expect(subject.instance_variable_get :@callbacks).to eq({}) - end - - it 'should remove only callbacks the given key if given' do - subject.remove_callbacks(:read) - expect(subject.instance_variable_get(:@callbacks)[:read]).to eq(nil) - expect(subject.instance_variable_get(:@callbacks)[:persistent]).to_not eq(nil) - end - end - - describe '#update' do - it 'should call all the callbacks' do - expect(@callback1).to receive(:call).once - expect(@callback2).to receive(:call).once - subject.update("data") - end - - it 'should remove any callbacks saved with the key :read' do - subject.update("data") - expect(subject.instance_variable_get(:@callbacks)[:read]).to eq(nil) - end - end - - - describe '#pre_callback_filter' do - it 'should mutate data being passed to callbacks' do - class FilteredComponent < CallbackComponent - def pre_callback_filter(data) - "dino: #{data}" - end - end - callback = Proc.new{|data| data} - subject = FilteredComponent.new - subject.add_callback(&callback) - expect(callback).to receive(:call).with("dino: dino") - subject.update("dino") - end - end - - describe '#update_self' do - it 'should set the @state variable' do - subject.update("dino") - expect(subject.instance_variable_get :@state).to eq("dino") - end - end - end - end - end - end -end diff --git a/spec/lib/components/mixins/listener_spec.rb b/spec/lib/components/mixins/listener_spec.rb deleted file mode 100644 index bb24b60e..00000000 --- a/spec/lib/components/mixins/listener_spec.rb +++ /dev/null @@ -1,58 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module Mixins - describe Poller do - - class ListenComponent - include Setup::Base - include Listener - def _listen(divider=nil); end - def _stop_listener; end - def initialize; after_initialize; end - end - - subject { ListenComponent.new } - - describe '#initialize' do - it 'should automatically include Callbacks' do - expect(subject.class.ancestors).to include(Callbacks) - end - end - - describe '#listen' do - it 'should call #stop' do - expect(subject).to receive(:stop) - subject.listen - end - - it 'should call #_listen' do - expect(subject).to receive(:_listen) - subject.listen - end - - it 'should add a given block as callback with :listen key' do - callback = Proc.new{} - expect(subject).to receive(:add_callback).with(:listen) do |&block| - expect(block).to eq(callback) - end - subject.listen(&callback) - end - end - - describe '#stop' do - it 'should call #_stop_listener' do - expect(subject).to receive(:_stop_listener) - subject.listen - end - - it 'should remove all callbacks with the :listen key' do - expect(subject).to receive(:remove_callbacks).with(:listen) - subject.stop - end - end - end - end - end -end diff --git a/spec/lib/components/mixins/poller_spec.rb b/spec/lib/components/mixins/poller_spec.rb deleted file mode 100644 index cfafcaeb..00000000 --- a/spec/lib/components/mixins/poller_spec.rb +++ /dev/null @@ -1,66 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module Mixins - describe Poller do - - class PollComponent - include Setup::Base - include Poller - def _read; end - def initialize; after_initialize; end - end - - subject { PollComponent.new } - - describe '#initialize' do - it 'should automatically include Callbacks' do - expect(subject.class.ancestors).to include(Threaded) - expect(subject.class.ancestors).to include(Reader) - end - end - - describe '#poll' do - it 'should call #stop' do - expect(subject).to receive(:stop) - subject.poll - end - - it 'should add a given block as callback with :poll key' do - callback = Proc.new{} - expect(subject).to receive(:add_callback).with(:poll) do |&block| - expect(block).to eq(callback) - end - subject.poll(&callback) - end - - it 'should start a new thread' do - expect(subject).to receive(:threaded_loop) - subject.poll - subject.stop - end - - it 'should call #_read repeatedly' do - expect(subject).to receive(:_read).at_least(:twice) - subject.poll(0.01) - sleep 0.1 - subject.stop - end - end - - describe '#stop' do - it 'should not override the call to Threaded#stop_thread' do - expect(subject).to receive(:stop_thread) - subject.stop - end - - it 'should remove any callbacks with the :poll key' do - expect(subject).to receive(:remove_callbacks).with(:poll) - subject.stop - end - end - end - end - end -end diff --git a/spec/lib/components/mixins/reader_spec.rb b/spec/lib/components/mixins/reader_spec.rb deleted file mode 100644 index 66b7e63f..00000000 --- a/spec/lib/components/mixins/reader_spec.rb +++ /dev/null @@ -1,78 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module Mixins - describe Reader do - - class ReadComponent - include Setup::Base - include Reader - def _read; end - def initialize; after_initialize; end - end - - subject { ReadComponent.new } - - def read_callbacks - subject.instance_variable_get(:@callbacks)[:read] - end - - def inject(data, wait_for_callbacks = true) - Thread.new do - if wait_for_callbacks - while (!read_callbacks) do; sleep 0.01; end - end - loop do - sleep 0.01 - subject.update(data) - break unless read_callbacks - end - end - end - - describe '#initialize' do - it 'should automatically include Callbacks' do - expect(ReadComponent.ancestors).to include(Callbacks) - end - end - - describe '#read' do - it 'should call #_read exactly once' do - expect(subject).to receive(:_read).exactly(1).times - inject(1) - subject.read - end - - it 'should add the given block as a callback with key :read' do - callback = Proc.new{} - - # expect(subject).to receive(:add_callback).with(:read, &callback) - # would just be too easy? - blocks = [] - allow(subject).to receive(:add_callback).with(:read) do |&block| - blocks << block - expect(blocks).to include(callback) - end - - inject(1) - subject.read(&callback) - end - - it 'should run the given callback exactly once' do - callback = Proc.new{} - expect(callback).to receive(:call).exactly(1).times - inject(1) - subject.read(&callback) - inject(1, false) - end - - it 'should return the read value' do - inject("Hello") - expect(subject.read).to eq("Hello") - end - end - end - end - end -end diff --git a/spec/lib/components/mixins/threaded_spec.rb b/spec/lib/components/mixins/threaded_spec.rb deleted file mode 100644 index 3b761946..00000000 --- a/spec/lib/components/mixins/threaded_spec.rb +++ /dev/null @@ -1,118 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module Mixins - describe Threaded do - include BoardMock - - class ThreadedComponent - include Setup::Base - include Threaded - def foo(str="test") - @bar = str - end - attr_reader :bar - interrupt_with :foo - end - - subject { ThreadedComponent.new(board: board) } - - describe 'ClassMethods' do - it 'should add methods to interrupt the thread using #interrupt_with' do - ThreadedComponent.class_variable_get(:@@interrupts).should eq [:foo] - end - end - - describe '#threaded' do - it 'should stop any existing thread' do - expect(subject).to receive(:stop_thread) - subject.threaded do; end - end - - it 'should enable interrupts' do - expect(subject).to receive(:enable_interrupts) - subject.threaded do; end - end - - it 'should start a new thread that calls the given block' do - async = Proc.new {} - expect(Thread).to receive(:new) do |&block| - expect(block).to eq(async) - end - subject.threaded(&async) - end - - it 'should store in @thread' do - thread = Thread.current - component = subject - component.threaded {} - while(!component.instance_variable_get :@thread) do; sleep 0.05; end - expect(component.instance_variable_get :@thread).to_not eq(thread) - end - end - - describe '#threaded_loop' do - it 'should loop the block in the thread' do - @mutex = Mutex.new - @count = 0 - - component = subject - component.threaded_loop do - @mutex.synchronize do - @count = @count + 1 - end - end - - while(@mutex.synchronize{ @count } < 2) do; sleep 0.05; end - component.stop_thread - expect(@count).to be > 1 - end - end - - describe '#stop_thread' do - it 'should kill the thread' do - component = subject - component.threaded { sleep } - expect(component.instance_variable_get :@thread).to receive(:kill) - component.stop_thread - end - end - - describe '#enable_interrupts' do - it 'should override the given method on the singleton class only' do - first_part = subject - second_part = ThreadedComponent.new(board: board) - before_class = second_part.method(:foo) - before_instance = first_part.method(:foo) - - first_part.enable_interrupts - after_class = second_part.method(:foo) - after_instance = first_part.method(:foo) - - expect(after_class).to eq(before_class) - expect(after_instance).to_not eq(before_instance) - end - - it 'should pass arguments through to the original method' do - component = subject - original = component.method(:foo) - component.enable_interrupts - component.foo("dino") - expect(original).to_not eq(component.method(:foo)) - expect(component.bar).to eq("dino") - end - end - - describe 'calling an interrupt' do - it 'should stop the thread' do - component = subject - component.threaded { sleep } - expect(component).to receive(:stop_thread) - component.foo - end - end - end - end - end -end diff --git a/spec/lib/components/one_wire/ds18b20_spec.rb b/spec/lib/components/one_wire/ds18b20_spec.rb deleted file mode 100644 index a5ca702c..00000000 --- a/spec/lib/components/one_wire/ds18b20_spec.rb +++ /dev/null @@ -1,66 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module OneWire - describe DS18B20 do - include BoardMock - let(:bus) { double(mutex: Mutex.new).as_null_object } - subject { DS18B20.new(bus: bus, address: 0xFFFFFFFFFFFFFFFF) } - - describe '#decode_temp' do - it 'should decode values matching the datasheet and convert C to F' do - expect(subject.decode_temperature([0b1101_0000,0b0000_0111])) - .to eq(celsius: 125, farenheit: 257) - expect(subject.decode_temperature([0b0000_0000,0b0000_0000])) - .to eq(celsius: 0, farenheit: 32) - expect(subject.decode_temperature([0b0101_1110,0b1111_1111])) - .to eq(celsius: -10.125, farenheit: 13.775) - expect(subject.decode_temperature([0b1001_0000,0b1111_1100])) - .to eq(celsius: -55, farenheit: -67) - end - end - - describe '#decode_resolution' do - it 'should get the resolution from the entire scratchpad' do - expect(subject.decode_resolution([0,0,0,0,0b01100000])).to eq(12) - expect(subject.decode_resolution([0,0,0,0,0b01000000])).to eq(11) - expect(subject.decode_resolution([0,0,0,0,0b00100000])).to eq(10) - expect(subject.decode_resolution([0,0,0,0,0b00000000])).to eq(9) - end - end - - describe '#convert' do - it 'should be atomic' do - expect(subject).to receive(:atomically).exactly(1).times - subject.convert - end - - it 'should match first' do - expect(subject).to receive(:atomically).exactly(1).times - subject.convert - end - - it 'should send the command' do - expect(bus).to receive(:write).with(0x44) - subject.convert - end - - it 'should set the sleep time to default on first conversion' do - subject.convert - expect(subject.instance_variable_get(:@convert_time)).to eq(0.75) - end - - it 'should sleep for the conversion time' do - subject.convert - expect(subject).to receive(:sleep).with(subject.instance_variable_get(:@convert_time)) - subject.convert - end - - it 'should sleep inside the mutex lock if parasite power in use' - it 'should sleep outside the mutex lock if parasite power not in use' - end - end - end - end -end diff --git a/spec/lib/components/one_wire/enumerator_spec.rb b/spec/lib/components/one_wire/enumerator_spec.rb deleted file mode 100644 index 2628a1c0..00000000 --- a/spec/lib/components/one_wire/enumerator_spec.rb +++ /dev/null @@ -1,140 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module OneWire - # - # State machine simulating a bus during address search. Initialize with n for - # n devices with random (CRC-invalid) addresses. Call #reset before each search. - # - class BusSimulator - def initialize(device_count) - @devices = [] - device_count.times do - @devices << { rom: rand(2**64), in_search: true } - end - @devices = @devices.uniq - @index = -1 - end - - def addresses - @addresses ||= @devices.map { |d| d[:rom] } - end - - def reset - @devices.each { |d| d[:in_search] = true } - @index = -1 - end - - def read_address_bit - bit = 1 - @index = @index + 1 - @devices.each do |d| - if d[:in_search] - bit = bit & d[:rom][@index] - end - end - bit - end - - def read_complement_bit - bit = 1 - @devices.each do |d| - if d[:in_search] - comp = d[:rom][@index] ^ 1 - bit = bit & comp - end - end - bit - end - - def write_bit(bit) - @devices.each do |d| - d[:in_search] = false unless d[:rom][@index] == bit - end - end - end - - # - # Class wrapper for Ruby version of the C++ serach function running on the board. - # Hopefully that doesn't change... - # - class BoardSimulator - def initialize(simulator) - @simulator = simulator - end - - def reset - @simulator.reset - end - - def search(branch_mask) - output_string = "" - (0..7).each do |i| - addr = 0 - comp = 0 - (0..7).each do |j| - # Read bit and complement from simulator. - addr_bit = @simulator.read_address_bit - comp_bit = @simulator.read_complement_bit - - # Set them in variable. - addr = addr | (addr_bit * (2 ** j)) - comp = comp | (comp_bit * (2 ** j)) - - # Override address bit to a 1 in variable if 1 in mask. - # Write whatever addr_bit ends up being back to the bus simulator. - if branch_mask[(i*8) + j] == 1 - @simulator.write_bit(1) - addr = addr | (1 * (2 ** j)) - else - @simulator.write_bit(addr_bit) - end - end - - output_string << addr.to_s << "," << comp.to_s - output_string << "," if (i != 7 ) # No \n. TxRx strips it IRL. - end - output_string - end - end - - # - # Monkeypatch Bus to search the board sim instead of a real board. - # - class Bus - def initialize(options={}) - @board_sim = options[:board_sim] - super(options) - end - - def _search(branch_mask) - @board_sim.reset - result = @board_sim.search(branch_mask) - # Manually call #update to simulate callback thread. - self.update(result) - end - end - - describe Bus do - include BoardMock - describe '#search' do - it 'should find all the addresses' do - # Ignore CRC since the simulator uses random addresses. - allow(OneWire::Helper).to receive(:crc_check).and_return true - allow_any_instance_of(Bus).to receive(:read_power_supply).and_return false - - # This gets slow really fast... 256 seems fine. - bus_sim = BusSimulator.new(256) - board_sim = BoardSimulator.new(bus_sim) - bus = Bus.new(board: board, pin: 7, board_sim: board_sim) - bus.search - - found_addresses = bus.found_devices.map { |d| d[:address] } - expect(found_addresses.sort).to eq(bus_sim.addresses.sort) - end - end - end - end - end -end diff --git a/spec/lib/components/one_wire/slave_spec.rb b/spec/lib/components/one_wire/slave_spec.rb deleted file mode 100644 index aa96c5fa..00000000 --- a/spec/lib/components/one_wire/slave_spec.rb +++ /dev/null @@ -1,125 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module OneWire - describe Slave do - include BoardMock - let(:bus) { double(mutex: Mutex.new).as_null_object } - subject { Slave.new(bus: bus, address: 0xFFFFFFFFFFFFFFFF) } - - describe '#after_initialize' do - it 'should require an address' do - expect { Slave.new(bus: bus) }.to raise_error - end - end - - describe '#atomically' do - it 'should happen inside the mutex lock' do - expect(bus.mutex).to receive(:synchronize) - subject.atomically {} - end - - it 'should take a block and call it exactly once' do - block = Proc.new {} - expect(block).to receive(:call).exactly(1).times - subject.atomically(&block) - end - end - - describe '#match' do - it 'should NOT be atomic' do - expect(subject).to receive(:atomically).exactly(0).times - subject.match - end - - it 'should reset the bus' do - expect(bus).to receive(:reset) - subject.match - end - - it 'should skip rom if it is the only device on the bus' do - expect(bus).to receive(:write).with(0xCC) - subject.match - end - - it 'should try to match ROM, then send its ROM if not alone on the bus' do - # Just needs to be an array of anything > 1. - bus.stub(:found_devices) { [1,2] } - expect(bus).to receive(:write).with(0x55) - expect(bus).to receive(:write).with([255,255,255,255,255,255,255,255]) - device = Slave.new(bus: bus, address: 0xFFFFFFFFFFFFFFFF) - device.match - end - end - - describe '#copy_scratch' do - it 'should be atomic' do - expect(subject).to receive(:atomically).exactly(1).times - subject.copy_scratch - end - - it 'should call #match' do - expect(subject).to receive(:match).exactly(1).times - subject.copy_scratch - end - - it 'should send the command' do - expect(bus).to receive(:write).with(0x48) - subject.copy_scratch - end - - it 'should reset the bus after if parasite power is in use' do - bus.stub(:parasite_power) { true } - expect(bus).to receive(:reset).exactly(2).times - subject.copy_scratch - end - end - - describe '#read_scratch' do - it 'should be atomic' do - expect(subject).to receive(:atomically).exactly(1).times - subject.read_scratch(9) - end - - it 'should call #match' do - expect(subject).to receive(:match).exactly(1).times - subject.read_scratch(9) - end - - it 'should send the command' do - expect(bus).to receive(:write).with(0xBE) - subject.read_scratch(9) - end - - it 'should read the right number of bytes' do - expect(bus).to receive(:read).with(9) - subject.read_scratch(9) - end - end - - describe '#write_scratch' do - it 'should be atomic' do - expect(subject).to receive(:atomically).exactly(1).times - subject.write_scratch([1]) - end - - it 'should call #match' do - expect(subject).to receive(:match).exactly(1).times - subject.write_scratch(1) - end - - it 'should send the command' do - expect(bus).to receive(:write).with(0x4E) - subject.write_scratch(1) - end - - it 'should write the data' do - expect(bus).to receive(:write).with([1,2,3]) - subject.write_scratch(1,2,3) - end - end - end - end - end -end diff --git a/spec/lib/components/register/select_spec.rb b/spec/lib/components/register/select_spec.rb deleted file mode 100644 index 3a15edb3..00000000 --- a/spec/lib/components/register/select_spec.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module Register - describe Select do - include BoardMock - let(:options) { { pin: '10', board: board } } - subject { Select.new(options) } - - describe '#initialize' do - it 'should set mode to output' do - expect(board).to receive(:set_pin_mode).with(10, :out) - subject - end - end - - describe '#update' do - it 'should respond to callbacks' do - subject - @callback = Proc.new{} - subject.add_callback(&@callback) - expect(@callback).to receive(:call).once.with("127") - subject.update("127") - end - end - end - end - end -end diff --git a/spec/lib/components/register/shift_in_spec.rb b/spec/lib/components/register/shift_in_spec.rb deleted file mode 100644 index 407eb581..00000000 --- a/spec/lib/components/register/shift_in_spec.rb +++ /dev/null @@ -1,87 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module Register - describe ShiftIn do - include BoardMock - let(:options) { { board: board, pins: {clock: 12, data: 11, latch: 8} } } - subject { ShiftIn.new(options) } - - describe '#initialize' do - it 'should create a DigitalOutput instance for clock pin' do - expect(subject.clock.class).to eq(Basic::DigitalOutput) - end - - it 'should create a DigitalInput instance for data pin' do - expect(subject.data.class).to eq(Basic::DigitalInput) - end - - it 'should create a Register::Select instance for latch pin' do - expect(subject.latch.class).to eq(Register::Select) - end - - it 'should set the number of bytes when given as option' do - subject = ShiftIn.new(options.merge(bytes:2)) - expect(subject.instance_variable_get(:@bytes)).to eq(2) - end - - it 'should default the rising_clock variable to 0' do - expect(subject.instance_variable_get(:@rising_clock)).to eq(false) - end - - it 'should set @rising_clock to true if given anything other than false' do - subject = ShiftIn.new(options.merge(rising_clock: :yes)) - expect(subject.instance_variable_get(:@rising_clock)).to eq(true) - end - end - - describe '#read' do - before(:each) { subject } - - it 'should send message for single byte in the request format the board expects' do - expect(board).to receive(:write).with "22.8.1.#{[11,12,0].pack('C*')}\n" - subject.read - end - - it 'should request the correct number of bytes to be read' do - subject = ShiftIn.new(options.merge(bytes: 2)) - expect(board).to receive(:write).with "22.8.2.#{[11,12,0].pack('C*')}\n" - subject.read - end - - it 'should request clock pin to go high before reading if set' do - subject = ShiftIn.new(options.merge(rising_clock: 1)) - expect(board).to receive(:write).with "22.8.1.#{[11,12,1].pack('C*')}\n" - subject.read - end - end - - describe '#update' do - before(:each) { subject } - - it 'should bubble #update from the latch pin up to itself' do - expect(subject).to receive(:update).once.with("127,255") - subject.latch.update("127,255") - end - - it 'should update @state with data converted to array of 0/1 integers' do - subject.update("127") - expect(subject.instance_variable_get(:@state)).to eq([0,1,1,1,1,1,1,1]) - - subject = ShiftIn.new(options.merge(bytes: 2)) - subject.update("127,255") - expect(subject.instance_variable_get(:@state)).to eq([0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]) - end - - it 'should pass data to the callbacks as an array of 0/1 integers' do - @callback = Proc.new{} - subject.add_callback(&@callback) - expect(@callback).to receive(:call).once.with([0,1,1,1,1,1,1,1]) - subject.update("127") - end - end - end - end - end -end diff --git a/spec/lib/components/register/shift_out_spec.rb b/spec/lib/components/register/shift_out_spec.rb deleted file mode 100644 index a5104f65..00000000 --- a/spec/lib/components/register/shift_out_spec.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module Register - describe ShiftOut do - include BoardMock - let(:options) { { board: board, pins: {clock: 12, data: 11, latch: 8} } } - subject { ShiftOut.new(options) } - - describe '#initialize' do - it 'should create a DigitalOutput instance for clock and data pins' do - expect(subject.clock.class).to eq(Basic::DigitalOutput) - expect(subject.data.class).to eq(Basic::DigitalOutput) - end - - it 'should create a Register::Select instance for latch pin' do - expect(subject.latch.class).to eq(Register::Select) - end - end - - describe '#write' do - before(:each) { subject } - - it 'should send message for single byte in the request format the board expects' do - expect(board).to receive(:write).with "21.8.1.#{[11,12,0,255].pack('C*')}\n" - - subject.write(255) - end - - it 'should send message for array of bytes in the request format the board expects' do - expect(board).to receive(:write).with "21.8.2.#{[11,12,0,255,0].pack('C*')}\n" - - subject.write([255,0]) - end - end - end - end - end -end diff --git a/spec/lib/components/rgb_led_spec.rb b/spec/lib/components/rgb_led_spec.rb deleted file mode 100644 index 12859ef6..00000000 --- a/spec/lib/components/rgb_led_spec.rb +++ /dev/null @@ -1,65 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - describe RgbLed do - include BoardMock - let(:options) { { board: board, pins: {red: 1, green: 2, blue: 3} } } - subject { RgbLed.new(options) } - - describe '#initialize' do - it 'should create a BaseOutput instance for each pin' do - led = RgbLed.new(options) - - expect(led.red.class).to eq(Basic::AnalogOutput) - expect(led.green.class).to eq(Basic::AnalogOutput) - expect(led.blue.class).to eq(Basic::AnalogOutput) - end - end - - describe '#write' do - it 'should write the elements of the array to red, green and blue' do - expect(subject.red).to receive(:write).with(0) - expect(subject.green).to receive(:write).with(128) - expect(subject.blue).to receive(:write).with(0) - - subject.write [0, 128, 0] - end - end - - describe '#color=' do - it 'should write an array of values' do - expect(subject).to receive(:write).with([128, 0, 0]) - subject.color = [128, 0, 0] - end - - it 'should look up named colors in COLORS whether passed in as symbol or string' do - colors = { - red: [255, 000, 000], - green: [000, 255, 000], - blue: [000, 000, 255], - cyan: [000, 255, 255], - yellow: [255, 255, 000], - magenta: [255, 000, 255], - white: [255, 255, 255], - off: [000, 000, 000] - } - colors.each_value { |color| expect(subject).to receive(:write).with(color).twice } - - colors.each_key { |key| subject.color = key } - colors.each_key { |key| subject.color = key.to_s } - end - end - - describe '#cycle' do - it 'should cycle through the 3 base colors' do - expect_any_instance_of(Array).to receive(:cycle).and_yield(:red).and_yield(:green).and_yield(:blue) - expect(subject).to receive(:color=).with(:red) - expect(subject).to receive(:color=).with(:green) - expect(subject).to receive(:color=).with(:blue) - subject.cycle - end - end - end - end -end diff --git a/spec/lib/components/sensor_spec.rb b/spec/lib/components/sensor_spec.rb deleted file mode 100644 index 589a4587..00000000 --- a/spec/lib/components/sensor_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - describe Sensor do - end - end -end diff --git a/spec/lib/components/servo_spec.rb b/spec/lib/components/servo_spec.rb deleted file mode 100644 index e52e1644..00000000 --- a/spec/lib/components/servo_spec.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - describe Servo do - include BoardMock - let(:options) { { board: board, pin: 9 } } - subject { Servo.new(options) } - - describe '#initialize' do - it 'should toggle the servo library on for the pin' do - expect(board).to receive(:servo_toggle).with(options[:pin], :on) - subject - end - end - - describe '#position' do - let(:servo) {servo = Servo.new(pin: 13, board: board)} - - it 'should set the position of the Servo' do - servo.position = 90 - expect(servo.position).to eq(90) - end - - it 'should let you write up to 180' do - servo.position = 180 - expect(servo.position).to eq(180) - end - - it 'should modulate when position > 180' do - servo.position = 190 - expect(servo.position).to eq(10) - end - - it 'should write the new position to the board' do - expect(board).to receive(:servo_write).with(13, 10) - servo.position = 190 - end - end - end - end -end diff --git a/spec/lib/components/setup/base_spec.rb b/spec/lib/components/setup/base_spec.rb deleted file mode 100644 index b9164f1d..00000000 --- a/spec/lib/components/setup/base_spec.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module Setup - describe Base do - include BoardMock - let(:options) { { pin: 'A0', board: board } } - - class BaseComponent - include Base - end - subject { BaseComponent.new(options) } - - describe '#initialize' do - it 'should require a board' do - expect { - BaseComponent.new(pin: 'A0') - }.to raise_exception - end - - it 'should register itself as a component on the board' do - expect(board).to receive(:add_component) - BaseComponent.new(options) - end - end - end - end - end -end diff --git a/spec/lib/components/setup/input_spec.rb b/spec/lib/components/setup/input_spec.rb deleted file mode 100644 index 817cdea5..00000000 --- a/spec/lib/components/setup/input_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module Setup - describe Input do - include BoardMock - let(:options) { { pin: 'A0', board: board, pullup: true} } - - class InputComponent - include SinglePin - include Input - end - subject { InputComponent.new(options) } - - describe '#pullup=' do - it 'should tell the board to set the pullup mode correctly' do - subject - expect(board).to receive(:set_pullup).with(subject.pin, false) - subject.pullup = false - expect(subject.pullup).to be(false) - end - end - - describe '#initialize_pins' do - it 'should set the pin mode to in' do - expect(board).to receive(:set_pin_mode).with(board.convert_pin(options[:pin]), :in) - subject - end - - it 'should set the pulllup if included in options' do - expect(board).to receive(:set_pullup).with(subject.pin, true) - InputComponent.new(options) - end - end - end - end - end -end diff --git a/spec/lib/components/setup/multi_pin_spec.rb b/spec/lib/components/setup/multi_pin_spec.rb deleted file mode 100644 index e1d815c4..00000000 --- a/spec/lib/components/setup/multi_pin_spec.rb +++ /dev/null @@ -1,73 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module Setup - describe MultiPin do - include BoardMock - let(:options) { { pins: {one: 9, two: 10, maybe: 11}, board: board } } - - class MultiPinComponent - include MultiPin - require_pin :one - proxy_pin two: Basic::DigitalOutput - proxy_pin maybe: Basic::DigitalInput, optional: true - end - subject { MultiPinComponent.new(options) } - - describe "::require_pins" do - it 'should add required pins to the @@required_pins class variable' do - expect(MultiPinComponent.class_eval('@@required_pins')).to eq([:one, :two]) - end - end - - describe "::proxy_pins" do - it 'should automatically require the pin unless :optional is true' do - expect(MultiPinComponent.class_eval('@@required_pins')).to eq([:one, :two]) - end - - it 'should add the pins to the @@proxied_pins class variable with the right classes' do - expect(MultiPinComponent.class_eval('@@proxied_pins')).to eq({two: Basic::DigitalOutput, maybe: Basic::DigitalInput}) - end - end - - describe '#validate_pins' do - it 'should raise an error for any required pin that is missing' do - expect { - MultiPinComponent.new(board: board, pins: {one: 9, maybe: 11}) - }.to raise_exception(/two/) - end - - it 'should not raise errors for optional pins that are missing' do - expect { - MultiPinComponent.new(board: board, pins: {one: 9, two: 10}) - }.to_not raise_exception - end - end - - describe '#build_proxies' do - it 'should create the correct proxy subcomponents' do - expect(subject.proxies[:two].class).to equal(Basic::DigitalOutput) - expect(subject.proxies[:maybe].class).to equal(Basic::DigitalInput) - end - - it 'should create an attr_accessor for each proxy' do - expect(subject.two).to equal(subject.proxies[:two]) - expect(subject.maybe).to equal(subject.proxies[:maybe]) - end - - it 'should attach the subcomponents to the right pins' do - expect(subject.two.pin).to eq(10) - expect(subject.maybe.pin).to eq(11) end - end - - describe '#proxy_states' do - it 'should return a hash with the state of each subcomponent' do - subject.two.high - expect(subject.proxy_states).to eq({two: board.high, maybe: nil}) - end - end - end - end - end -end diff --git a/spec/lib/components/setup/output_spec.rb b/spec/lib/components/setup/output_spec.rb deleted file mode 100644 index 8de06ff6..00000000 --- a/spec/lib/components/setup/output_spec.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module Setup - describe Input do - include BoardMock - let(:options) { { pin: 'A0', board: board, pullup: true} } - - class OutputComponent - include SinglePin - include Output - end - subject { OutputComponent.new(options) } - - describe '#initialize_pins' do - it 'should set the pin mode to out' do - expect(board).to receive(:set_pin_mode).with(board.convert_pin(options[:pin]), :out) - subject - end - end - end - end - end -end diff --git a/spec/lib/components/setup/single_pin_spec.rb b/spec/lib/components/setup/single_pin_spec.rb deleted file mode 100644 index d34c5584..00000000 --- a/spec/lib/components/setup/single_pin_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - module Setup - describe SinglePin do - include BoardMock - let(:options) { { pin: 'A0', board: board } } - - class SinglePinComponent - include SinglePin - end - subject { SinglePinComponent.new(options) } - - describe '#initialize' do - it 'should require a pin' do - expect { - SinglePinComponent.new(board: board) - }.to raise_exception - end - - it 'should convert the pin to an integer' do - expect(board).to receive(:convert_pin).with(options[:pin]) - component = SinglePinComponent.new(options) - end - end - - describe '#mode=' do - it 'should tell the board to set the pin mode' do - expect(board).to receive(:set_pin_mode).with(subject.pin, :out) - - subject.send(:mode=, :out) - expect(subject.mode).to eq(:out) - end - end - end - end - end -end diff --git a/spec/lib/components/softwareserial_spec.rb b/spec/lib/components/softwareserial_spec.rb deleted file mode 100644 index 440a2627..00000000 --- a/spec/lib/components/softwareserial_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - describe SoftwareSerial do - include BoardMock - - subject { SoftwareSerial.new board: board, pins: { rx: 10, tx: 11 }, baud: 4800 } - - before do - expect(board).to receive(:write).with("12..0.10,11\n") - expect(board).to receive(:write).with("12..1.4800\n") - end - - describe '#puts' do - it 'prints a string to the serial interface' do - expect(board).to receive(:write).with "12..3.Testing\n" - subject.puts("Testing") - end - end - end - end -end diff --git a/spec/lib/components/ssd_spec.rb b/spec/lib/components/ssd_spec.rb deleted file mode 100644 index b5ead4f5..00000000 --- a/spec/lib/components/ssd_spec.rb +++ /dev/null @@ -1,78 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - describe SSD do - include BoardMock - let(:pins) { {anode: 11, a: 12, b: 13, c: 3, d: 4, e: 5, f: 10, g: 9} } - subject { SSD.new(board: board, pins: pins) } - - describe '#initialize' do - it 'should create a digital output for pins a-g' do - subject - - segments = [:a, :b, :c, :d, :e, :f, :g] - segments.each do |segment| - expect(subject.proxies[segment].class).to equal(Basic::DigitalOutput) - end - end - - it "should clear the display" do - expect(subject).to receive(:clear).once - subject.send(:initialize, {board: board, pins: pins}) - end - - it "should turn on the display" do - expect(subject).to receive(:on).once - subject.send(:initialize, {board: board, pins: pins}) - end - end - - describe '#clear' do - it 'should toggle all the seven leds to off' do - segments = [:a, :b, :c, :d, :e, :f, :g] - segments.each do |segment| - expect(subject.proxies[segment]).to receive(:high) - end - - subject.clear - end - end - - describe '#on' do - it 'should turn the ssd on' do - expect(subject.anode).to receive(:digital_write).with(board.high) - subject.on - end - end - - describe '#off' do - it 'should turn the ssd off' do - expect(subject.anode).to receive(:digital_write).with(board.low) - subject.off - end - end - - describe '#display' do - it "should scroll on multiple characters" do - expect(subject).to receive(:scroll).with('foo') - subject.display('foo') - end - - it "should make sure the ssd is turned on" do - expect(subject).to receive(:on) - subject.display(1) - end - - it "should clear the display on unknown character" do - expect(subject).to receive(:clear) - subject.display('+') - end - end - - describe 'with a cathode' do - it 'should invert the logic' - end - end - end -end diff --git a/spec/lib/components/stepper_spec.rb b/spec/lib/components/stepper_spec.rb deleted file mode 100644 index b4407123..00000000 --- a/spec/lib/components/stepper_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'spec_helper' - -module Dino - module Components - describe Stepper do - include BoardMock - let(:options) { { pins: {step: 9, direction: 10}, board: board } } - - subject { Stepper.new(options) } - - describe '#initialize' do - it 'should create a BaseOutput instance for each pin' do - expect(subject.step.class).to equal(Basic::DigitalOutput) - expect(subject.direction.class).to equal(Basic::DigitalOutput) - end - end - - describe '#step_cc' do - it 'should send high to the step pin with the direction pin high' do - expect(subject.direction).to receive(:digital_write).with(board.high) - expect(subject.step).to receive(:digital_write).with(board.high) - expect(subject.step).to receive(:digital_write).with(board.low) - - subject.step_cc - end - end - - describe '#step_cw' do - it 'should send high to the board with the direction pin low' do - expect(subject.direction).to receive(:digital_write).with(board.low) - expect(subject.step).to receive(:digital_write).with(board.high) - expect(subject.step).to receive(:digital_write).with(board.low) - - subject.step_cw - end - end - end - end -end diff --git a/spec/lib/message_spec.rb b/spec/lib/message_spec.rb deleted file mode 100644 index c8c5767e..00000000 --- a/spec/lib/message_spec.rb +++ /dev/null @@ -1,47 +0,0 @@ -require 'spec_helper' - -module Dino - describe Dino::Message do - describe '.encode' do - - it 'should require a command' do - expect { Dino::Message.encode }.to raise_exception(/command/) - expect { Dino::Message.encode(command:90) }.to_not raise_exception - end - - it 'should not allow commands longer than 4 digits' do - expect { Dino::Message.encode(command:90000) }.to raise_exception(/four/) - end - - it 'should not allow pins longer than 4 digits' do - expect { Dino::Message.encode(command:90, pin:90000) }.to raise_exception(/four/) - end - - it 'should not allow values longer than 4 digits' do - expect { Dino::Message.encode(command:90, value:90000) }.to raise_exception(/four/) - end - - it 'should not allow aux messages longer than 512' do - expect { Dino::Message.encode(command:90, aux_message: "0" * 513) }.to raise_exception(/512/) - end - - it 'should build messages correctly' do - expect(Dino::Message.encode command: 1, pin: 1, value: 1 ).to eq("1.1.1\n") - expect(Dino::Message.encode command: 1, pin: 1 ).to eq("1.1\n") - expect(Dino::Message.encode command: 1, value: 1 ).to eq("1..1\n") - expect(Dino::Message.encode command: 1 ).to eq("1\n") - expect(Dino::Message.encode command: 1, pin: 1, value: 1, aux_message: "Some Text" ).to eq("1.1.1.Some Text\n") - expect(Dino::Message.encode command: 1, aux_message: "Some Text" ).to eq("1...Some Text\n") - expect(Dino::Message.encode command: 1, value: 1, aux_message: "Some Text" ).to eq("1..1.Some Text\n") - end - - it 'should escape newlines inside aux message' do - expect(Dino::Message.encode command: 1, aux_message: "line1\nline2").to eq("1...line1\\\nline2\n") - end - - it 'should escape backslashes inside aux message' do - expect(Dino::Message.encode command: 1, aux_message: "line1\\line2").to eq("1...line1\\\\line2\n") - end - end - end -end diff --git a/spec/lib/tx_rx/serial_spec.rb b/spec/lib/tx_rx/serial_spec.rb deleted file mode 100644 index 421a167f..00000000 --- a/spec/lib/tx_rx/serial_spec.rb +++ /dev/null @@ -1,149 +0,0 @@ -require 'spec_helper' - -module Dino - describe TxRx::Serial do - def mock_tty - @mock_tty ||= "/dev/mock_serial" - end - - def mock_serial - @mock_serial ||= double("serial", read: "", write: nil) - end - - subject { - Dino::TxRx::Serial.new - } - - def io_instance - subject.send(:io) - end - - describe '#connect' do - it 'should use the specified device and baud rate from options hash' do - expect(::Serial).to receive(:new).with("/dev/ttyACM0", 9600).and_return(mock_serial) - txrx = Dino::TxRx::Serial.new(device: "/dev/ttyACM0", baud: 9600) - expect(txrx.send(:io)).to equal mock_serial - end - - context "on windows" do - it 'should create a new ::Serial object for the connected COM port if non specified' do - # Simulate being on Windows - original_platform = RUBY_PLATFORM - Constants.redefine(:RUBY_PLATFORM, "mswin", :on => Object) - - # Simulate 3 COM ports with COM2 connected. - expect_any_instance_of(TxRx::Serial).to receive(:tty_devices).and_return(["COM1", "COM2", "COM3"]) - expect(::Serial).to receive(:new).with("COM1", TxRx::Serial::BAUD).and_raise(RubySerial::Error) - expect(::Serial).to receive(:new).with("COM2", TxRx::Serial::BAUD).and_return(mock_serial) - expect(::Serial).to_not receive(:new).with("COM3", TxRx::Serial::BAUD) - - txrx = TxRx::Serial.new - expect(txrx.send(:io)).to equal(mock_serial) - - # Set platform back to original value - Constants.redefine(:RUBY_PLATFORM, original_platform, :on => Object) - end - end - - context "on *nix" do - it 'should create a new ::Serial for the first connected tty device if none specified' do - # Simulate /dev/ttyACM0 connected. - expect_any_instance_of(TxRx::Serial).to receive(:tty_devices).and_return(['/dev/ttyACM0', '/dev/tty.usbmodem1']) - expect(::Serial).to receive(:new).with('/dev/ttyACM0', TxRx::Serial::BAUD).and_return(mock_serial) - expect(::Serial).to_not receive(:new).with('/dev/tty.usbmodem1', TxRx::Serial::BAUD) - - txrx = TxRx::Serial.new - expect(txrx.send(:io)).to equal(mock_serial) - end - end - - it 'should raise SerialConnectError if it cannot connect to a board' do - allow(::Serial).to receive(:new).and_raise(RubySerial::Error) - expect { TxRx::Serial.new.send(:io) }.to raise_exception Dino::TxRx::SerialConnectError - end - end - - describe '#io_reset' do - it 'should call flush read, stop read, and start read' do - expect(subject).to receive(:flush_read).and_return true - expect(subject).to receive(:stop_read).and_return true - expect(subject).to receive(:start_read).and_return true - subject.send(:io_reset) - end - end - - describe '#read_and_parse' do - it 'should notify observers on change' do - expect(subject).to receive(:read).and_return("02:00") - expect(subject).to receive(:changed).and_return(true) - expect(subject).to receive(:notify_observers).with('02','00') - subject.send(:read_and_parse) - end - - it 'should not split messages into more than 2 parts on :' do - expect(subject).to receive(:read).and_return("02:00:00") - expect(subject).to receive(:changed).and_return(true) - expect(subject).to receive(:notify_observers).with('02','00:00') - subject.send(:read_and_parse) - end - end - - describe '#start_read' do - it 'should create a new thread and start looping' do - expect(Thread).to receive(:new).and_yield.and_return(double("abort_on_exception=" => true)) - expect(subject).to receive(:loop) - subject.send(:start_read) - end - end - - describe '#stop_read' do - it 'should kill the reading thread' do - subject.instance_variable_set(:@thread, mock_thread = double) - expect(Thread).to receive(:kill).with(mock_thread) - subject.send(:start_read) - subject.send(:stop_read) - end - end - - describe '#write' do - it 'should write to the device' do - expect(subject).to receive(:io).at_least(:once).and_return(mock_serial) - expect(mock_serial).to receive(:write).with('a message') - subject.write('a message') - end - it 'should break up messages larger than the board input buffer' - end - - describe '#read' do - before :each do - expect(subject).to receive(:io).at_least(:once).and_return(mock_serial) - end - - it 'should read single characters until it hits a newline and strip it' do - expect(mock_serial).to receive(:read).exactly(5).times.with(1).and_return("l", "i", "n", "e", "\n") - expect(subject.send(:read)).to eq("line") - end - - it 'should catch escaped newlines' do - expect(mock_serial).to receive(:read).exactly(7).times.with(1).and_return("l", "1", "\\", "\n", "l", "2", "\n") - expect(subject.send(:read)).to eq("l1\nl2") - end - - it 'should return a blank string if there is just a newline' do - expect(mock_serial).to receive(:read).exactly(1).times.with(1).and_return("\n") - expect(subject.send(:read)).to eq("") - end - - it 'should allow escaped backslashes' do - expect(io_instance).to receive(:read).exactly(3).times.with(1).and_return("\\", "\\", "\n") - expect(subject.send(:read)).to eq("\\") - end - end - - describe '#handshake' do - it 'should call #io_reset' - it 'should write again after timeout' - it 'should return the value from the ACK' - end - end -end diff --git a/spec/lib/tx_rx/tcp_spec.rb b/spec/lib/tx_rx/tcp_spec.rb deleted file mode 100644 index e2424564..00000000 --- a/spec/lib/tx_rx/tcp_spec.rb +++ /dev/null @@ -1,37 +0,0 @@ -require 'spec_helper' - -module Dino - describe TxRx::TCP do - before :each do - @host = "127.0.0.1" - @port = 3467 - @instance = TxRx::TCP.new(@host, @port) - end - - describe "#connect" do - it 'should raise a TCPConnectError exception if it cannot connect to the server' do - expect { @instance.send :io }.to raise_exception Dino::TxRx::TCPConnectError - end - - it 'should return the TCPSocket if connected' do - @server = TCPServer.new @port - expect(@instance.send :io).to be_a(TCPSocket) - @server.close - end - end - - describe '#io' do - it 'should set io to a new TCPSocket with the specified host and port' do - expect(TCPSocket).to receive(:open).with(@host, @port) - @instance.send :io - end - - it 'should use the existing io instance if set' do - @server = TCPServer.new @port - socket = @instance.send :io - expect(@instance.send :io).to equal(socket) - @server.close - end - end - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb deleted file mode 100644 index a31b7943..00000000 --- a/spec/spec_helper.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'rspec' - -require File.expand_path(File.join('../..', 'lib/dino'), __FILE__) - -# Nice little helper module to redefine constants quietly. -module Constants - def self.redefine(const, value, opts={}) - opts = {:on => self.class}.merge(opts) - opts[:on].send(:remove_const, const) if self.class.const_defined?(const) - opts[:on].const_set(const, value) - end -end - -module BoardMock - def self.included(base) - base.class_eval do - let(:txrx) { double :txrx, add_observer: true, - handshake: "528,14,20", - write: true, - read: true } - let(:board) { Dino::Board.new(txrx) } - end - end -end diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 70a0dca5..d1a0e3b3 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -156,6 +156,7 @@ void Dino::process() { case 33: i2cSearch (); break; case 34: i2cWrite (); break; case 35: i2cRead (); break; + case 36: i2cTransfer (); break; #endif // Implemented in DinoOneWire.cpp diff --git a/test/api/core_test.rb b/test/api/core_test.rb new file mode 100644 index 00000000..5511a920 --- /dev/null +++ b/test/api/core_test.rb @@ -0,0 +1,136 @@ +require 'dino' +require 'txrx_mock' +require 'minitest/autorun' + +class APICoreTest < Minitest::Test + def txrx + @txrx ||= TxRxMock.new + end + + def board + @board ||= Dino::Board.new(txrx) + end + + def test_set_pin_mode + mock = MiniTest::Mock.new + mock.expect(:call, nil, [Dino::Message.encode(command: 0, pin: 1, value: board.low)]) + mock.expect(:call, nil, [Dino::Message.encode(command: 0, pin: 1, value: board.high)]) + + board.stub(:write, mock) do + board.set_pin_mode 1, :out + board.set_pin_mode 1, :in + end + mock.verify + end + + def test_set_pullup + mock = MiniTest::Mock.new + mock.expect(:call, nil, [Dino::Message.encode(command: 1, pin: 1, value: board.low)]) + mock.expect(:call, nil, [Dino::Message.encode(command: 1, pin: 1, value: board.high)]) + + board.stub(:write, mock) do + board.set_pullup 1, false + board.set_pullup 1, true + end + mock.verify + end + + def test_digital_write + mock = MiniTest::Mock.new + mock.expect(:call, nil, [Dino::Message.encode(command: 1, pin: 1, value: board.low)]) + mock.expect(:call, nil, [Dino::Message.encode(command: 1, pin: 1, value: board.high)]) + + board.stub(:write, mock) do + board.digital_write 1, board.low + board.digital_write 1, board.high + end + mock.verify + end + + def test_digital_read + mock = MiniTest::Mock.new + mock.expect(:call, nil, [Dino::Message.encode(command: 2, pin: 1)]) + + board.stub(:write, mock) do + board.digital_read 1 + end + mock.verify + end + + def test_analog_write + mock = MiniTest::Mock.new + mock.expect(:call, nil, [Dino::Message.encode(command: 3, pin: 1, value: board.low)]) + mock.expect(:call, nil, [Dino::Message.encode(command: 3, pin: 1, value: board.analog_high)]) + mock.expect(:call, nil, [Dino::Message.encode(command: 3, pin: 1, value: 128)]) + + board.stub(:write, mock) do + board.analog_write 1, board.low + board.analog_write 1, board.analog_high + board.analog_write 1, 128 + end + mock.verify + end + + def test_analog_read + mock = MiniTest::Mock.new + mock.expect(:call, nil, [Dino::Message.encode(command: 4, pin: 1)]) + + board.stub(:write, mock) do + board.analog_read 1 + end + mock.verify + end + + def test_set_listener + mock = MiniTest::Mock.new + mock.expect(:call, nil, [Dino::Message.encode(command: 7, pin: 1, value: 0b00000011)]) + mock.expect(:call, nil, [Dino::Message.encode(command: 7, pin: 1, value: 0b01000011)]) + mock.expect(:call, nil, [Dino::Message.encode(command: 7, pin: 1, value: 0b11000011)]) + mock.expect(:call, nil, [Dino::Message.encode(command: 7, pin: 1, value: 0b11000000)]) + + board.stub(:write, mock) do + board.set_listener(1, :off) + board.set_listener(1, :off, mode: :analog) + board.set_listener(1, :on, mode: :analog) + board.set_listener(1, :on, mode: :analog, divider: 1) + end + mock.verify + end + + def test_digital_listen + mock = MiniTest::Mock.new + mock.expect(:call, nil, [1, :on, {mode: :digital, divider: 4}]) + + board.stub(:set_listener, mock) do + board.digital_listen(1) + end + end + + def test_analog_listen + mock = MiniTest::Mock.new + mock.expect(:call, nil, [1, :on, {mode: :analog, divider: 16}]) + + board.stub(:set_listener, mock) do + board.analog_listen(1) + end + end + + def test_stop_listener + mock = MiniTest::Mock.new + mock.expect(:call, nil, [1, :off]) + + board.stub(:set_listener, mock) do + board.stop_listener(1) + end + end + + def test_analog_resolution + mock = MiniTest::Mock.new + mock.expect(:call, nil, [Dino::Message.encode(command: 96, value: 10)]) + + board.stub(:write, mock) do + board.analog_resolution = 10 + end + mock.verify + end +end diff --git a/test/board_mock.rb b/test/board_mock.rb new file mode 100644 index 00000000..ce0ecb7b --- /dev/null +++ b/test/board_mock.rb @@ -0,0 +1,8 @@ +require 'dino' +require 'txrx_mock' + +class BoardMock < Dino::Board::Default + def initialize + super(TxRxMock.new) + end +end diff --git a/test/board_test.rb b/test/board_test.rb new file mode 100644 index 00000000..9bdc917b --- /dev/null +++ b/test/board_test.rb @@ -0,0 +1,113 @@ +require 'dino' +require 'txrx_mock' +require 'minitest/autorun' + +class BoardTest < Minitest::Test + def txrx + @txrx ||= TxRxMock.new + end + + def board + @board ||= Dino::Board.new(txrx) + end + + def test_require_a_txrx_object + assert_raises(Exception) { Dino::Board.new } + end + + def test_starts_observing_txrx + io = TxRxMock.new + mock = MiniTest::Mock.new.expect(:call, nil, [Dino::Board::Default]) + io.stub(:add_observer, mock) do + Dino::Board.new(io) + end + mock.verify + end + + def test_calls_handshake_on_txrx + mock = MiniTest::Mock.new.expect(:call, "528,14,20") + txrx.stub(:handshake, mock) do + Dino::Board.new(txrx) + end + mock.verify + end + + def test_set_aux_limit + assert_equal 527, board.aux_limit + end + + def test_set_dac_and_analog_zero + assert_equal 20, board.dac_zero + assert_equal 14, board.analog_zero + end + + def test_set_low_high_analog_high + assert_equal 0, board.low + assert_equal 1, board.high + assert_equal 255, board.analog_high + end + + def test_add_remove_component + mock = MiniTest::Mock.new + mock.expect(:pin, 1) + + board.add_component(mock) + assert_equal [mock], board.components + + board.remove_component(mock) + assert_equal [], board.components + end + + def test_write + board + mock = MiniTest::Mock.new.expect(:call, nil, ["message"]) + txrx.stub(:write, mock) do + board.write("message") + end + mock.verify + end + + def test_update_passes_messages_to_components + mock1 = MiniTest::Mock.new.expect(:update, nil, ["data"]) + 3.times { mock1.expect(:pin, 1) } + mock2 = MiniTest::Mock.new.expect(:update, nil, ["byte"]) + 3.times { mock2.expect(:pin, 2) } + board.add_component(mock1) + board.add_component(mock2) + board.update(1, "data") + board.update(2, "byte") + board.update(3, "ignore") + mock1.verify + mock2.verify + end + + def test_convert_pin + assert_equal 9, board.convert_pin(9) + assert_equal 13, board.convert_pin('13') + assert_equal 15, board.convert_pin('A1') + assert_equal 15, board.convert_pin(:A1) + assert_equal 21, board.convert_pin('DAC1') + end + + def test_convert_pin_esp8266 + board = Dino::Board::ESP8266.new(txrx) + assert_equal 2, board.convert_pin(2) + assert_equal 4, board.convert_pin('D2') + assert_equal 5, board.convert_pin('GPIO5') + assert_equal 0, board.convert_pin('gpio0') + assert_equal 17, board.convert_pin('A0') + end + + def test_incorrect_pin_formats + assert_raises(ArgumentError) { board.convert_pin('ADC1') } + board.instance_variable_set(:@dac_zero, nil) + assert_raises(ArgumentError) { board.convert_pin('DAC1') } + end + + def test_analog_resolution + board.analog_resolution = 10 + assert_equal 0, board.low + assert_equal 1023, board.analog_high + assert_equal 10, board.analog_resolution + end +end diff --git a/test/components/basic/analog_input_test.rb b/test/components/basic/analog_input_test.rb new file mode 100644 index 00000000..e97bd9b6 --- /dev/null +++ b/test/components/basic/analog_input_test.rb @@ -0,0 +1,40 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class AnalogInputTest < Minitest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= Dino::Components::Basic::AnalogInput.new(board: board, pin: 14) + end + + def test__read + mock = MiniTest::Mock.new.expect :call, nil, [14] + board.stub(:analog_read, mock) do + part._read + end + mock.verify + end + + def test__listen + mock = MiniTest::Mock.new + mock.expect :call, nil, [14, 16] + mock.expect :call, nil, [14, 32] + board.stub(:analog_listen, mock) do + part._listen + part._listen(32) + end + mock.verify + end + + def test__stop_listen + mock = MiniTest::Mock.new.expect :call, nil, [14] + board.stub(:stop_listener, mock) do + part._stop_listener + end + mock.verify + end +end diff --git a/test/components/basic/analog_output_test.rb b/test/components/basic/analog_output_test.rb new file mode 100644 index 00000000..e007988d --- /dev/null +++ b/test/components/basic/analog_output_test.rb @@ -0,0 +1,41 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class AnalogOutputTest < Minitest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= Dino::Components::Basic::AnalogOutput.new(board: board, pin: 14) + end + + def test_analog_write + mock = MiniTest::Mock.new.expect :call, nil, [14, 128] + board.stub(:analog_write, mock) do + part.analog_write(128) + end + mock.verify + assert_equal part.state, 128 + end + + def test_write_uses_digital_write_at_limits + mock = MiniTest::Mock.new + mock.expect :call, nil, [board.high] + mock.expect :call, nil, [board.low] + part.stub(:digital_write, mock) do + part.write(board.analog_high) + part.write(board.low) + end + mock.verify + end + + def test_write_uses_analog_write_between_limits + mock = MiniTest::Mock.new.expect :call, nil, [128] + part.stub(:analog_write, mock) do + part.write(128) + end + mock.verify + end +end diff --git a/test/components/basic/digital_input_test.rb b/test/components/basic/digital_input_test.rb new file mode 100644 index 00000000..22929dd4 --- /dev/null +++ b/test/components/basic/digital_input_test.rb @@ -0,0 +1,61 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class DigitalInputTest < Minitest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= Dino::Components::Basic::DigitalInput.new(board: board, pin: 14) + end + + def test_start_listening_immediately + mock = MiniTest::Mock.new.expect :call, nil, [14, 4] + board.stub(:digital_listen, mock) do + part + end + mock.verify + end + + def test__read + mock = MiniTest::Mock.new.expect :call, nil, [14] + board.stub(:digital_read, mock) do + part._read + end + mock.verify + end + + def test__listen + part + mock = MiniTest::Mock.new + mock.expect :call, nil, [14, 4] + mock.expect :call, nil, [14, 32] + board.stub(:digital_listen, mock) do + part._listen + part._listen(32) + end + mock.verify + end + + def test_on_low + low_cb = MiniTest::Mock.new.expect :call, nil + high_cb = MiniTest::Mock.new + part.on_low { low_cb.call } + part.on_high { high_cb.call } + part.update(board.low) + low_cb.verify + high_cb.verify + end + + def test_on_high + low_cb = MiniTest::Mock.new + high_cb = MiniTest::Mock.new.expect :call, nil + part.on_low { low_cb.call } + part.on_high { high_cb.call } + part.update(board.high) + low_cb.verify + high_cb.verify + end +end diff --git a/test/components/basic/digital_output_test.rb b/test/components/basic/digital_output_test.rb new file mode 100644 index 00000000..919727aa --- /dev/null +++ b/test/components/basic/digital_output_test.rb @@ -0,0 +1,59 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class DigitalOutputTest < Minitest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= Dino::Components::Basic::DigitalOutput.new(board: board, pin: 14) + end + + def test_low_on_initialize + mock = MiniTest::Mock.new.expect :call, nil, [14, board.low] + board.stub(:digital_write, mock) do + part + end + mock.verify + end + + def test_digital_write + part + mock = MiniTest::Mock.new.expect :call, nil, [14, board.high] + board.stub(:digital_write, mock) do + part.digital_write(board.high) + end + mock.verify + assert_equal board.high, part.state + end + + def test_high_and_low + part + mock = MiniTest::Mock.new + mock.expect :call, nil, [board.high] + mock.expect :call, nil, [board.low] + part.stub(:digital_write, mock) do + part.high + part.low + end + mock.verify + end + + def test_toggle + part.low + mock = MiniTest::Mock.new.expect :call, nil + part.stub(:high, mock) do + part.toggle + end + mock.verify + + part.high + mock = MiniTest::Mock.new.expect :call, nil + part.stub(:low, mock) do + part.toggle + end + mock.verify + end +end diff --git a/test/components/ir_emitter_test.rb b/test/components/ir_emitter_test.rb new file mode 100644 index 00000000..35e6e62d --- /dev/null +++ b/test/components/ir_emitter_test.rb @@ -0,0 +1,29 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class IREmitterTest < MiniTest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= Dino::Components::IREmitter.new(board: board, pin:1) + end + + # These are really API tests. + # Should move these and test that API methods get called here instead. + def test_packs_pulses_correctly + part + string = "16.1.38.#{[4].pack('C')}#{[100,200,300,400].pack('S<*')}\n" + mock = MiniTest::Mock.new.expect(:call, nil, [string]) + board.stub(:write, mock) { part.send [100,200,300,400] } + end + + def test_accepts_modulation_frequency_as_option + part + string = "16.1.40.#{[4].pack('C')}#{[100,200,300,400].pack('S<*')}\n" + mock = MiniTest::Mock.new.expect(:call, nil, [string]) + board.stub(:write, mock) { part.send [100,200,300,400], frequency: 40 } + end +end diff --git a/test/components/lcd_test.rb b/test/components/lcd_test.rb new file mode 100644 index 00000000..902571e2 --- /dev/null +++ b/test/components/lcd_test.rb @@ -0,0 +1,49 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class LCDTest < MiniTest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= Dino::Components::LCD.new cols: 16, rows: 2, + board: board, + pins: { rs: 12, enable: 11, + d4: 5, d5: 4, d6: 3, d7: 2 } + end + + def test_initialize + mock = MiniTest::Mock.new + mock.expect :call, nil, ["10..0.12,11,5,4,3,2\n"] + mock.expect :call, nil, ["10..1.16,2\n"] + board.stub(:write, mock) do + part + end + mock.verify + end + + Dino::Components::LCD::LIBRARY_COMMANDS.each_pair do |command, command_id| + define_method("test_#{command.to_s}".to_sym) do + part + mock = MiniTest::Mock.new.expect :call, nil, [Dino::Message.encode(command: 10, value: command_id)] + board.stub(:write, mock) { part.send(command) } + mock.verify + end + end + + def test_set_cursor + part + mock = MiniTest::Mock.new.expect :call, nil, [Dino::Message.encode(command: 10, value: 4, aux_message: "0,1")] + board.stub(:write, mock) { part.set_cursor(0,1) } + mock.verify + end + + def test_puts + part + mock = MiniTest::Mock.new.expect :call, nil, [Dino::Message.encode(command: 10, value: 5, aux_message: "AB")] + board.stub(:write, mock) { part.puts("AB")} + mock.verify + end +end diff --git a/test/components/led_test.rb b/test/components/led_test.rb new file mode 100644 index 00000000..39bb328b --- /dev/null +++ b/test/components/led_test.rb @@ -0,0 +1,21 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class LedTest < MiniTest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= Dino::Components::Led.new(board: board, pin:1) + end + + def test_blink_runs_in_thread + mock = MiniTest::Mock.new.expect :call, nil + part.stub(:threaded_loop, mock) do + part.blink(0.5) + end + mock.verify + end +end diff --git a/test/components/mixins/callbacks_test.rb b/test/components/mixins/callbacks_test.rb new file mode 100644 index 00000000..734cbaa9 --- /dev/null +++ b/test/components/mixins/callbacks_test.rb @@ -0,0 +1,77 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class CallbackComponent + include Dino::Components::Setup::Base + include Dino::Components::Mixins::Callbacks + + def pre_callback_filter(data) + "dino: #{data}" + end +end + +class CallbacksTest < Minitest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= CallbackComponent.new(board: board, pin: 1) + end + + def test_add_callback + callback = Proc.new{} + part.add_callback(&callback) + assert_equal part.callbacks, {persistent: [callback]} + end + + def test_add_callback_with_key + callback = Proc.new{} + part.add_callback(:key, &callback) + assert_equal({key: [callback]}, part.callbacks) + end + + def add_two_callbacks + @callback1 = Proc.new{} + @callback2 = Proc.new{} + part.add_callback(&@callback1) + part.add_callback(:read, &@callback2) + end + + def test_remove_callback + add_two_callbacks + part.remove_callbacks + assert_equal({}, part.callbacks) + end + + def test_remove_callback_with_key + add_two_callbacks + part.remove_callbacks(:read) + assert_nil part.callbacks[:read] + assert_equal [@callback1], part.callbacks[:persistent] + end + + def test_update_runs_callbacks_and_removes_read_callbacks + cb1 = MiniTest::Mock.new.expect :call, nil + cb2 = MiniTest::Mock.new.expect :call, nil + part.add_callback { cb1.call } + part.add_callback(:read) { cb2.call } + part.update("data") + assert_nil part.callbacks[:read] + cb1.verify + cb2.verify + end + + def test_pre_callback_filter_modifies_data + cb = MiniTest::Mock.new.expect :call, nil, ["dino: value"] + part.add_callback { |x| cb.call(x) } + part.update("value") + cb.verify + end + + def test_update_self + part.update("test") + assert_equal "dino: test", part.state + end +end diff --git a/test/components/mixins/listener_test.rb b/test/components/mixins/listener_test.rb new file mode 100644 index 00000000..30b9a9c8 --- /dev/null +++ b/test/components/mixins/listener_test.rb @@ -0,0 +1,57 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class ListenerComponent + include Dino::Components::Setup::Base + include Dino::Components::Mixins::Listener + + def _listen(divider=nil); end + def _stop_listener; end +end + +class ListenerTest < Minitest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= ListenerComponent.new(board: board, pin: 1) + end + + def test_include_callbacks + assert_includes ListenerComponent.ancestors, + Dino::Components::Mixins::Callbacks + end + + def test_call_stop_before_listening + mock = MiniTest::Mock.new.expect :call, nil + part.stub(:stop, mock) { part.listen } + mock.verify + end + + def test_call__listen + mock = MiniTest::Mock.new + mock.expect :call, nil, [nil] + mock.expect :call, nil, [32] + part.stub(:_listen, mock) do + part.listen + part.listen(32) + end + mock.verify + end + + def test_call__stop_listener + mock = MiniTest::Mock.new.expect :call, nil + part.stub(:_stop_listener, mock) { part.stop } + mock.verify + end + + def test_add_and_remove_callback + callback = Proc.new{} + part.listen(&callback) + assert_equal [callback], part.callbacks[:listen] + part.stop + assert_nil part.callbacks[:listen] + end +end diff --git a/test/components/mixins/poller_test.rb b/test/components/mixins/poller_test.rb new file mode 100644 index 00000000..0972f390 --- /dev/null +++ b/test/components/mixins/poller_test.rb @@ -0,0 +1,66 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class PollerComponent + include Dino::Components::Setup::Base + include Dino::Components::Mixins::Poller + def _read; end +end + +class PollerTest < Minitest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= PollerComponent.new(board: board, pin: 1) + end + + def inject(data, wait_for_callbacks = true) + Thread.new do + if wait_for_callbacks + while (!part.callbacks[:read]) do; sleep 0.01; end + end + loop do + sleep 0.01 + part.update(data) + break unless part.callbacks[:read] + end + end + end + + def test_include_callbacks_and_threaded + assert_includes PollerComponent.ancestors, + Dino::Components::Mixins::Callbacks + + assert_includes PollerComponent.ancestors, + Dino::Components::Mixins::Threaded + end + + def test_call_stop_before_polling + mock = MiniTest::Mock.new.expect :call, nil + part.stub(:stop, mock) { part.poll } + mock.verify + end + + def test_uses_threaded_loop + mock = MiniTest::Mock.new.expect :call, nil + part.stub(:threaded_loop, mock) { part.poll } + mock.verify + end + + def test_add_and_remove_callback + callback = Proc.new{} + part.poll(&callback) + assert_equal part.callbacks[:poll], [callback] + part.stop + assert_nil part.callbacks[:poll] + end + + def test_stop_kills_thread + mock = MiniTest::Mock.new.expect :call, nil + part.stub(:stop_thread, mock) { part.stop } + mock.verify + end +end diff --git a/test/components/mixins/reader_test.rb b/test/components/mixins/reader_test.rb new file mode 100644 index 00000000..1ea84e0a --- /dev/null +++ b/test/components/mixins/reader_test.rb @@ -0,0 +1,56 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class ReaderComponent + include Dino::Components::Setup::Base + include Dino::Components::Mixins::Reader + def _read; end +end + +class ReaderTest < Minitest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= ReaderComponent.new(board: board, pin: 1) + end + + def inject(data, wait_for_callbacks = true) + Thread.new do + if wait_for_callbacks + while (!part.callbacks[:read]) do; sleep 0.01; end + end + loop do + sleep 0.01 + part.update(data) + break unless part.callbacks[:read] + end + end + end + + def test_include_callbacks + assert_includes ReaderComponent.ancestors, + Dino::Components::Mixins::Callbacks + end + + def test_read_once + mock = MiniTest::Mock.new.expect :call, nil + inject(1) + part.stub(:_read, mock) { part.read } + mock.verify + end + + # test returns read value + # test blocks main thread + # test read_using -> {} + + def test_add_run_remove_callback + cb = MiniTest::Mock.new.expect :call, nil + inject(1) + part.read { cb.call } + assert_nil part.callbacks[:read] + cb.verify + end +end diff --git a/test/components/mixins/threaded_test.rb b/test/components/mixins/threaded_test.rb new file mode 100644 index 00000000..d12183df --- /dev/null +++ b/test/components/mixins/threaded_test.rb @@ -0,0 +1,100 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class ThreadedComponent + include Dino::Components::Setup::Base + include Dino::Components::Mixins::Threaded + def foo(str="test") + @bar = str + end + attr_reader :bar + interrupt_with :foo +end + +class ThreadedTest < Minitest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= ThreadedComponent.new(board: board, pin: 1) + end + + def test_add_interrupts_using_interrupt_with + assert_equal ThreadedComponent.class_variable_get(:@@interrupts), [:foo] + end + + def test_threaded_stops_threads + mock = MiniTest::Mock.new.expect :call, nil + part.stub(:stop_thread, mock) { part.threaded {} } + mock.verify + end + + def test_threaded_enables_interrupts + mock = MiniTest::Mock.new.expect :call, nil + part.stub(:enable_interrupts, mock) { part.threaded {} } + mock.verify + end + + def test_threaded_calls_block_given + mock = MiniTest::Mock.new.expect :call, nil + part.threaded { mock.call } + sleep 0.1 # Should find a better way to do this. + mock.verify + end + + def test_thread_stored_in_instance_variable + thread = Thread.current + part.threaded {} + refute_equal(thread, part.instance_variable_get(:@thread)) + end + + def test_threaded_loop_calls_block_repeatedly + mock = MiniTest::Mock.new.expect(:call, nil).expect(:call, nil) + part.stub(:foo, mock) do + part.threaded_loop do + part.foo + sleep 0.05 + end + sleep 0.07 # And this. + end + mock.verify + end + + def test_stop_thread_kills_thread + mock = MiniTest::Mock.new.expect(:kill, nil) + part.instance_variable_set(:@thread, mock) + part.stop_thread + mock.verify + end + + def test_interrupts_override_singleton_only + second_part = ThreadedComponent.new(board: BoardMock.new, pin: 2) + before_class = second_part.method(:foo) + before_instance = part.method(:foo) + + part.enable_interrupts + after_class = second_part.method(:foo) + after_instance = part.method(:foo) + + assert_equal before_class, after_class + refute_equal before_instance, after_instance + end + + def test_original_method_called_when_interrupts_enabled + mock = MiniTest::Mock.new.expect(:call, nil, ["dino"]) + part.stub(:foo, mock) do + part.enable_interrupts + part.foo("dino") + end + mock.verify + end + + def test_interrupt_stops_thread + part.enable_interrupts + mock = MiniTest::Mock.new.expect :call, nil + part.stub(:stop_thread, mock) { part. foo } + mock.verify + end +end diff --git a/test/components/one_wire/ds18b20_test.rb b/test/components/one_wire/ds18b20_test.rb new file mode 100644 index 00000000..768f7efe --- /dev/null +++ b/test/components/one_wire/ds18b20_test.rb @@ -0,0 +1,80 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class DS18B20Test < MiniTest::Test + def bus + @bus ||= Dino::Components::OneWire::Bus.new(board: BoardMock.new, pin: 1) + end + + def part + @part ||= Dino::Components::OneWire::DS18B20.new(bus: bus, address: 0xFFFFFFFFFFFFFFFF) + end + + def test_decode_temperatures + assert_equal({ celsius: 125, farenheit: 257 }, + part.decode_temperature([0b1101_0000,0b0000_0111])) + assert_equal({ celsius: 0, farenheit: 32 }, + part.decode_temperature([0b0000_0000,0b0000_0000])) + assert_equal({ celsius: -10.125, farenheit: 13.775 }, + part.decode_temperature([0b0101_1110,0b1111_1111])) + assert_equal({ celsius: -55, farenheit: -67 }, + part.decode_temperature([0b1001_0000,0b1111_1100])) + end + + def test_decode_resolution + assert_equal 12, part.decode_resolution([0,0,0,0,0b01100000]) + assert_equal 11, part.decode_resolution([0,0,0,0,0b01000000]) + assert_equal 10, part.decode_resolution([0,0,0,0,0b00100000]) + assert_equal 9, part.decode_resolution([0,0,0,0,0b00000000]) + end + + def test_set_convert_time + part.instance_variable_set(:@resolution, 9) + part.set_convert_time + assert_equal 0.09375, part.instance_variable_get(:@convert_time) + part.instance_variable_set(:@resolution, 12) + part.set_convert_time + assert_equal 0.75, part.instance_variable_get(:@convert_time) + end + + # test resolution= + + def test_convert_is_atomic + mock = MiniTest::Mock.new.expect(:call, nil) + part.stub(:atomically, mock) { part.convert } + end + + def test_convert_matches_first + mock = MiniTest::Mock.new.expect(:call, nil) + part.stub(:match, mock) { part.convert} + end + + def test_convert_sends_the_command + mock = MiniTest::Mock.new + mock.expect(:call, nil, [0xCC]) + mock.expect(:call, nil, [0x44]) + bus.stub(:write, mock) { part.convert } + end + + def test_convert_sets_max_convert_time_first + part.convert + assert_equal 0.75, part.instance_variable_get(:@convert_time) + end + + def test_convert_sleeps_for_convert_time + mock = MiniTest::Mock.new.expect(:call, nil, [0.75]) + part.stub(:sleep, mock) do + part.convert + end + end + + def test_convert_sleeps_inside_lock_if_parasite_power + bus.instance_variable_set(:@parasite_power, true) + Thread.new do + sleep 0.5 + assert_equal true, bus.mutex.locked? + end + part.convert + end +end diff --git a/test/components/one_wire/enumerator_test.rb b/test/components/one_wire/enumerator_test.rb new file mode 100644 index 00000000..c7e17429 --- /dev/null +++ b/test/components/one_wire/enumerator_test.rb @@ -0,0 +1,141 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +# State machine simulating a bus during address search. Initialize with n for +# n devices with random (CRC-invalid) addresses. Call #reset before each search. +class BusSimulator + def initialize(device_count) + @devices = [] + device_count.times do + @devices << { rom: rand(2**64), in_search: true } + end + @devices = @devices.uniq + @index = -1 + end + + def addresses + @addresses ||= @devices.map { |d| d[:rom] } + end + + def reset + @devices.each { |d| d[:in_search] = true } + @index = -1 + end + + def read_address_bit + bit = 1 + @index = @index + 1 + @devices.each do |d| + if d[:in_search] + bit = bit & d[:rom][@index] + end + end + bit + end + + def read_complement_bit + bit = 1 + @devices.each do |d| + if d[:in_search] + comp = d[:rom][@index] ^ 1 + bit = bit & comp + end + end + bit + end + + def write_bit(bit) + @devices.each do |d| + d[:in_search] = false unless d[:rom][@index] == bit + end + end +end + +# Ruby version of the C++ functions running on the board. +class BoardSimulator + def initialize(simulator) + @simulator = simulator + end + + def reset + @simulator.reset + end + + def search(branch_mask) + output_string = "" + (0..7).each do |i| + addr = 0 + comp = 0 + (0..7).each do |j| + # Read bit and complement from simulator. + addr_bit = @simulator.read_address_bit + comp_bit = @simulator.read_complement_bit + + # Set them in variable. + addr = addr | (addr_bit * (2 ** j)) + comp = comp | (comp_bit * (2 ** j)) + + # Override address bit to a 1 in variable if 1 in mask. + # Write whatever addr_bit ends up being back to the bus simulator. + if branch_mask[(i*8) + j] == 1 + @simulator.write_bit(1) + addr = addr | (1 * (2 ** j)) + else + @simulator.write_bit(addr_bit) + end + end + + output_string << addr.to_s << "," << comp.to_s + output_string << "," if (i != 7 ) # No \n. TxRx strips it IRL. + end + output_string + end +end + +# Monkeypatch Bus and Helper to stub in simulations. +module Dino + module Components + module OneWire + + class Bus + def read_power_supply + @parasite_power = false + end + + def initialize(options={}) + @board_sim = options[:board_sim] + super(options) + end + + def _search(branch_mask) + @board_sim.reset + result = @board_sim.search(branch_mask) + # Manually call #update to simulate data recevieved from board. + self.update(result) + end + end + + class Helper + def self.crc_check(bytes) + true + end + end + end + end +end + +class OneWireEnumeratorTest < Minitest::Test + def test_find_all_addresses + # This gets slow with large number of devices. + bus_sim = BusSimulator.new(100) + board_sim = BoardSimulator.new(bus_sim) + bus = Dino::Components::OneWire::Bus.new board: BoardMock.new, + pin: 1, + board_sim: board_sim + bus.search + + found_addresses = bus.found_devices.map { |d| d[:address] } + assert_equal found_addresses.sort, bus_sim.addresses.sort + end +end diff --git a/test/components/one_wire/helper_test.rb b/test/components/one_wire/helper_test.rb new file mode 100644 index 00000000..1acf3f67 --- /dev/null +++ b/test/components/one_wire/helper_test.rb @@ -0,0 +1 @@ +# Test crc check. diff --git a/test/components/one_wire/slave_test.rb b/test/components/one_wire/slave_test.rb new file mode 100644 index 00000000..3369b746 --- /dev/null +++ b/test/components/one_wire/slave_test.rb @@ -0,0 +1,151 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +module Dino + module Components + module OneWire + class Bus + def read(num_bytes) + data = "" + num_bytes.times do + data << "," unless data.empty? + data << "255" + end + self.update(data) + self.pre_callback_filter(data) + end + end + end + end +end + +class OneWireSlaveTest < MiniTest::Test + def bus + @bus ||= Dino::Components::OneWire::Bus.new(board: BoardMock.new, pin: 1) + end + + def part + @part ||= Dino::Components::OneWire::Slave.new(bus: bus, address: 0xFFFFFFFFFFFFFFFF) + end + + def test_requires_address + assert_raises(ArgumentError) { Dino::Components::OneWire::Slave.new(bus: bus) } + end + + def test_atomically_locks_the_bus_mutex + mock = MiniTest::Mock.new.expect(:synchronize, nil) + bus.stub(:mutex, mock) do + part.atomically {} + end + mock.verify + end + + def test_atomically_calls_the_block_once + mock = MiniTest::Mock.new.expect(:call, nil) + part.atomically { mock.call } + mock.verify + end + + def test_match_calls_reset + mock = MiniTest::Mock.new.expect(:call, nil) + bus.stub(:reset, mock) do + part.match + end + mock.verify + end + + def test_match_skips_rom_if_alone + mock = MiniTest::Mock.new.expect(:call, nil, [0xCC]) + bus.stub(:write, mock) do + part.match + end + mock.verify + end + + def test_match_matches_rom_if_not_alone + mock = MiniTest::Mock.new + mock.expect(:call, nil, [0x55]) + mock.expect(:call, nil, [[255,255,255,255,255,255,255,255]]) + bus.instance_variable_set(:@found_devices, [1,2]) + bus.stub(:write, mock) do + part.match + end + mock.verify + end + + def test_copy_scratch_is_atomic + mock = MiniTest::Mock.new.expect(:call, nil) + part.stub(:atomically, mock) { part.copy_scratch } + mock.verify + end + + def test_copy_scratch_matches_first + mock = MiniTest::Mock.new.expect(:call, nil) + part.stub(:match, mock) { part.copy_scratch } + mock.verify + end + + def test_copy_scratch_sends_the_command + mock = MiniTest::Mock.new + mock.expect(:call, nil, [0xCC]) + mock.expect(:call, nil, [0x48]) + bus.stub(:write, mock) { part.copy_scratch } + mock.verify + end + + def test_copy_scratch_resets_after_command_if_parasite_power + mock = MiniTest::Mock.new.expect(:call, nil).expect(:call, nil) + bus.stub(:parasite_power, true) do + bus.stub(:reset, mock) { part.copy_scratch } + end + mock.verify + end + + def test_read_scratch_is_atomic + mock = MiniTest::Mock.new.expect(:call, nil) + part.stub(:atomically, mock) { part.read_scratch(9) } + mock.verify + end + + def test_read_scratch_matches_first + mock = MiniTest::Mock.new.expect(:call, nil) + part.stub(:match, mock) { part.read_scratch(9) } + mock.verify + end + + def test_read_scratch_sends_the_command + mock = MiniTest::Mock.new + mock.expect(:call, nil, [0xCC]) + mock.expect(:call, nil, [0xBE]) + bus.stub(:write, mock) { part.read_scratch(9) } + mock.verify + end + + def test_read_scratch_reads_bytes_from_bus + mock = MiniTest::Mock.new.expect(:call, nil, [9]) + bus.stub(:read, mock) { part.read_scratch(9) } + mock.verify + end + + def test_write_scratch_is_atomic + mock = MiniTest::Mock.new.expect(:call, nil) + part.stub(:atomically, mock) { part.write_scratch(1) } + mock.verify + end + + def test_write_scratch_matches_first + mock = MiniTest::Mock.new.expect(:call, nil) + part.stub(:match, mock) { part.write_scratch(1) } + mock.verify + end + + def test_write_scratch_sends_the_command_and_data + mock = MiniTest::Mock.new + mock.expect(:call, nil, [0xCC]) + mock.expect(:call, nil, [0x4E]) + mock.expect(:call, nil, [1,2,3]) + bus.stub(:write, mock) { part.write_scratch(1, 2, 3) } + mock.verify + end +end diff --git a/test/components/register/select_test.rb b/test/components/register/select_test.rb new file mode 100644 index 00000000..955e9e6d --- /dev/null +++ b/test/components/register/select_test.rb @@ -0,0 +1,26 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class RegisterSelectTest < Minitest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= Dino::Components::Register::Select.new(board: board, pin: 14) + end + + def test_default_to_output + mock = MiniTest::Mock.new.expect :call, nil, [14, :out] + board.stub(:set_pin_mode, mock) do + part + end + mock.verify + end + + def test_callbacks + assert_includes Dino::Components::Register::Select.ancestors, + Dino::Components::Mixins::Callbacks + end +end diff --git a/test/components/register/shift_in_test.rb b/test/components/register/shift_in_test.rb new file mode 100644 index 00000000..551cc0ce --- /dev/null +++ b/test/components/register/shift_in_test.rb @@ -0,0 +1,70 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class RegisterShiftInTest < Minitest::Test + def board + @board ||= BoardMock.new + end + + def options + { board: board, pins: { clock: 12, data: 11, latch: 8 } } + end + + def part + @part ||= Dino::Components::Register::ShiftIn.new(options) + end + + def test_proxies + assert_equal Dino::Components::Basic::DigitalOutput, part.clock.class + assert_equal Dino::Components::Basic::DigitalInput, part.data.class + assert_equal Dino::Components::Register::Select, part.latch.class + end + + def test_byte_length + new_part = Dino::Components::Register::ShiftIn.new(options.merge(bytes: 2)) + assert_equal 2, new_part.bytes + end + + def test_rising_clock + assert_equal part.rising_clock, false + new_part = Dino::Components::Register::ShiftIn.new(options.merge(rising_clock: :yes)) + assert_equal true, new_part.rising_clock + end + + def test_read + # mock = MiniTest::Mock.new.expect :call, nil, ["22.8.1.#{[11,12,0].pack('C<*')}\n"] + mock = MiniTest::Mock.new.expect :call, nil, [8, 11, 12, 2, {preclock_high: true}] + board.stub(:shift_read, mock) do + new_part = Dino::Components::Register::ShiftIn.new(options.merge(bytes: 2, rising_clock: true)) + new_part.read + end + mock.verify + end + + def test_callback_bubble + mock = MiniTest::Mock.new.expect :call, nil, ["127,255"] + part.stub(:update, mock) do + part.latch.update "127,255" + end + mock.verify + end + + def test_bit_array_conversion + part.update("127") + assert_equal [0,1,1,1,1,1,1,1], part.state + + new_part = Dino::Components::Register::ShiftIn.new(options.merge(bytes: 2)) + new_part.update("127,255") + assert_equal [0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], new_part.state + end + + def test_callbacks_get_bit_array + mock = MiniTest::Mock.new.expect :call, nil, [[0,1,1,1,1,1,1,1]] + part.add_callback do |data| + mock.call(data) + end + part.update("127") + mock.verify + end +end diff --git a/test/components/register/shift_out_test.rb b/test/components/register/shift_out_test.rb new file mode 100644 index 00000000..711cc7c3 --- /dev/null +++ b/test/components/register/shift_out_test.rb @@ -0,0 +1,33 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class RegisterShiftOutTest < Minitest::Test + def board + @board ||= BoardMock.new + end + + def options + { board: board, pins: { clock: 12, data: 11, latch: 8 } } + end + + def part + @part ||= Dino::Components::Register::ShiftOut.new(options) + end + + def test_proxies + assert_equal Dino::Components::Basic::DigitalOutput, part.clock.class + assert_equal Dino::Components::Basic::DigitalOutput, part.data.class + assert_equal Dino::Components::Register::Select, part.latch.class + end + + def test_write + # mock = MiniTest::Mock.new.expect :call, nil, ["21.8.1.#{[11,12,0,255,127].pack('C*')}\n"] + new_part = Dino::Components::Register::ShiftOut.new(options.merge(bytes: 2)) + mock = MiniTest::Mock.new.expect :call, nil, [8, 11, 12, [255,127]] + board.stub(:shift_write, mock) do + new_part.write(255,127) + end + mock.verify + end +end diff --git a/test/components/rgb_led_test.rb b/test/components/rgb_led_test.rb new file mode 100644 index 00000000..0b55bf1f --- /dev/null +++ b/test/components/rgb_led_test.rb @@ -0,0 +1,66 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class RGBLedTest < MiniTest::Test + def board + @board ||= BoardMock.new + end + + def options + { board: board, pins: { red: 1, green: 2, blue: 3 } } + end + + def part + @part ||= Dino::Components::RGBLed.new(options) + end + + def test_proxies + assert_equal Dino::Components::Basic::AnalogOutput, part.red.class + assert_equal Dino::Components::Basic::AnalogOutput, part.green.class + assert_equal Dino::Components::Basic::AnalogOutput, part.blue.class + end + + def test_write + red_mock = MiniTest::Mock.new.expect :write, nil, [0] + green_mock = MiniTest::Mock.new.expect :write, nil, [128] + blue_mock = MiniTest::Mock.new.expect :write, nil, [0] + + part.stub(:red, red_mock) do + part.stub(:green, green_mock) do + part.stub(:blue, blue_mock) do + part.write [0, 128, 0] + end + end + end + red_mock.verify + green_mock.verify + blue_mock.verify + end + + def test_color_array + mock = MiniTest::Mock.new.expect :call, nil, [[128,0,0]] + part.stub(:write, mock) do + part.color = [128,0,0] + end + mock.verify + end + + def test_color_names + colors = Dino::Components::RGBLed::COLORS + + mock = MiniTest::Mock.new + colors.each_value do |color| + mock.expect :call, nil, [color] + mock.expect :call, nil, [color] + end + + part.stub(:write, mock) do + colors.each_key do |key| + part.color = key + part.color = key.to_s + end + end + mock.verify + end +end diff --git a/test/components/servo_test.rb b/test/components/servo_test.rb new file mode 100644 index 00000000..3cf78407 --- /dev/null +++ b/test/components/servo_test.rb @@ -0,0 +1,63 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class ServoTest < MiniTest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= Dino::Components::Servo.new(board: board, pin:1, min: 0, max: 360) + end + + def test_toggle_on_initialize + mock = MiniTest::Mock.new.expect(:call, nil, [1, :on, Hash]) + board.stub(:servo_toggle, mock) do + Dino::Components::Servo.new(board: board, pin:1) + end + end + + def test_attach + part + mock = MiniTest::Mock.new.expect(:call, nil, [1, :on, Hash]) + board.stub(:servo_toggle, mock) { part.attach } + mock.verify + end + + def test_detach + part + mock = MiniTest::Mock.new.expect(:call, nil, [1, :off, Hash]) + board.stub(:servo_toggle, mock) { part.detach } + mock.verify + end + + def test_position_modulo_180 + part.position = 190 + assert_equal 10, part.position + part.position = 180 + assert_equal 180, part.position + part.position = 0 + assert_equal 0, part.position + part.position = -1 + assert_equal 179, part.position + end + + def test_position_writes_mapped_microseconds_to_board + part + mock = MiniTest::Mock.new.expect(:call, nil, [1, 20]) + board.stub(:servo_write, mock) do + part.position = 10 + end + mock.verify + end + + def test_speed_writes_mapped_microseconds_to_board + part + mock = MiniTest::Mock.new.expect(:call, nil, [1, 180]) + board.stub(:servo_write, mock) do + part.speed = 0 + end + mock.verify + end +end diff --git a/test/components/setup/base_test.rb b/test/components/setup/base_test.rb new file mode 100644 index 00000000..4a7b3434 --- /dev/null +++ b/test/components/setup/base_test.rb @@ -0,0 +1,22 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class BaseComponent + include Dino::Components::Setup::Base +end + +class BaseSetupTest < Minitest::Test + def board + @board ||= BoardMock.new + end + + def test_requires_board + assert_raises(ArgumentError) { BaseComponent.new } + end + + def test_registers_with_board + part = BaseComponent.new(board: board) + assert_equal board.components, [part] + end +end diff --git a/test/components/setup/input_test.rb b/test/components/setup/input_test.rb new file mode 100644 index 00000000..b65678ab --- /dev/null +++ b/test/components/setup/input_test.rb @@ -0,0 +1,42 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class InputComponent + include Dino::Components::Setup::Input +end + +class InputSetupTest < Minitest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= InputComponent.new(board: board, pin: 1) + end + + def test_pin_mode + mock = Minitest::Mock.new.expect :call, nil, [1, :in] + board.stub(:set_pin_mode, mock) do + part + end + mock.verify + end + + def test_pullup + mock = Minitest::Mock.new + mock.expect :call, nil, [1, nil] + mock.expect :call, nil, [1, true] + board.stub(:set_pullup, mock) do + part.pullup = true + end + mock.verify + end + + def test_pullup_in_options + mock = Minitest::Mock.new.expect :call, nil, [2, true] + board.stub(:set_pullup, mock) do + new_part = InputComponent.new(board: board, pin: 2, pullup: true) + end + end +end diff --git a/test/components/setup/multi_pin_test.rb b/test/components/setup/multi_pin_test.rb new file mode 100644 index 00000000..40bb6140 --- /dev/null +++ b/test/components/setup/multi_pin_test.rb @@ -0,0 +1,61 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class MultiPinComponent + include Dino::Components::Setup::MultiPin + require_pin :one + proxy_pin two: Dino::Components::Basic::DigitalOutput + proxy_pin maybe: Dino::Components::Basic::DigitalInput, optional: true +end + +class MultiPinSetupTest < Minitest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= MultiPinComponent.new board: board, + pins: { one: 9, two: 10, maybe: 11 } + end + + def test_require_pins + assert_equal MultiPinComponent.class_variable_get(:@@required_pins), [:one, :two] + end + + def test_proxy_pins + assert_equal MultiPinComponent.class_variable_get(:@@proxied_pins), + {two: Dino::Components::Basic::DigitalOutput, + maybe: Dino::Components::Basic::DigitalInput} + end + + def test_validate_pins + assert_raises(ArgumentError) do + MultiPinComponent.new board: board, pins: { one: 9, maybe: 11 } + end + assert_raises(ArgumentError) do + MultiPinComponent.new board: board, pins: { one: 10, maybe: 11 } + end + MultiPinComponent.new board: board, pins: { one: 9, two:10 } + end + + def test_build_proxies + assert_equal Dino::Components::Basic::DigitalOutput, part.proxies[:two].class + assert_equal Dino::Components::Basic::DigitalInput, part.proxies[:maybe].class + end + + def test_proxy_reader_methods + assert_equal part.proxies[:two], part.two + assert_equal part.proxies[:maybe], part.maybe + end + + def test_pins_mapped_correctly + assert_equal 10, part.two.pin + assert_equal 11, part.maybe.pin + end + + def test_proxy_states + part.two.high + assert_equal({two: board.high, maybe: nil}, part.proxy_states) + end +end diff --git a/test/components/setup/output_test.rb b/test/components/setup/output_test.rb new file mode 100644 index 00000000..59c44c5d --- /dev/null +++ b/test/components/setup/output_test.rb @@ -0,0 +1,25 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class OutputComponent + include Dino::Components::Setup::Output +end + +class OutputSetupTest < Minitest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= OutputComponent.new(board: board, pin: 1) + end + + def test_pin_mode + mock = Minitest::Mock.new.expect :call, nil, [1, :out] + board.stub(:set_pin_mode, mock) do + part + end + mock.verify + end +end diff --git a/test/components/setup/single_pin_test.rb b/test/components/setup/single_pin_test.rb new file mode 100644 index 00000000..9308d825 --- /dev/null +++ b/test/components/setup/single_pin_test.rb @@ -0,0 +1,21 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class SinglePinComponent + include Dino::Components::Setup::Input +end + +class SinglePinSetupTest < Minitest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= SinglePinComponent.new(board: board, pin: 1) + end + + def test_requires_pin + assert_raises(ArgumentError) { SinglePinComponent.new(board: board) } + end +end diff --git a/test/components/software_serial_test.rb b/test/components/software_serial_test.rb new file mode 100644 index 00000000..da07697d --- /dev/null +++ b/test/components/software_serial_test.rb @@ -0,0 +1,33 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class SoftwareSerialTest < MiniTest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= Dino::Components::SoftwareSerial.new board: board, pins: { rx: 10, tx: 11 }, baud: 4800 + end + + def test_initialize + mock = MiniTest::Mock.new + mock.expect :call, nil, ["12..0.10,11\n"] + mock.expect :call, nil, ["12..1.4800\n"] + board.stub(:write, mock) do + part + end + mock.verify + end + + def test_puts + part + mock = MiniTest::Mock.new + mock.expect :call, nil, ["12..3.Testing\n"] + board.stub(:write, mock) do + part.puts("Testing") + end + mock.verify + end +end diff --git a/test/components/ssd_test.rb b/test/components/ssd_test.rb new file mode 100644 index 00000000..23b386a2 --- /dev/null +++ b/test/components/ssd_test.rb @@ -0,0 +1,82 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class SSDTest < MiniTest::Test + def board + @board ||= BoardMock.new + end + + def options + { board: board, + pins: {anode: 11, a: 12, b: 13, c: 3, d: 4, e: 5, f: 10, g: 9} } + end + + def part + @part ||= Dino::Components::SSD.new(options) + end + + def test_proxies + segments = [:a, :b, :c, :d, :e, :f, :g] + segments.each do |segment| + assert_equal Dino::Components::Basic::DigitalOutput, part.proxies[segment].class + end + end + + def test_initialize_clear + mock = MiniTest::Mock.new.expect :call, nil + part.stub(:clear, mock) do + part.send(:initialize, options) + end + mock.verify + end + + def test_initialize_on + mock = MiniTest::Mock.new.expect :call, nil + part.stub(:on, mock) do + part.send(:initialize, options) + end + mock.verify + end + + def test_on + mock = MiniTest::Mock.new.expect :high, nil + part.stub(:anode, mock) do + part.on + end + mock.verify + end + + def test_off + mock = MiniTest::Mock.new.expect :low, nil + part.stub(:anode, mock) do + part.off + end + mock.verify + end + + def test_scroll + mock = MiniTest::Mock.new.expect :call, nil, ['foo'] + part.stub(:scroll, mock) do + part.display('foo') + end + mock.verify + end + + def test_display_ensures_on + mock = MiniTest::Mock.new.expect :call, nil + part.stub(:on, mock) do + part.display(1) + end + mock.verify + end + + def test_display_clears_if_unknown_char + mock = MiniTest::Mock.new.expect :call, nil + part.stub(:clear, mock) do + part.display('+') + end + mock.verify + end + # Test with cathode +end diff --git a/test/components/stepper_test.rb b/test/components/stepper_test.rb new file mode 100644 index 00000000..7e17c3f7 --- /dev/null +++ b/test/components/stepper_test.rb @@ -0,0 +1,49 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class StepperTest < MiniTest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= Dino::Components::Stepper.new board: board, + pins: {step: 9, direction: 10} + end + + def test_initialize + assert_equal Dino::Components::Basic::DigitalOutput, part.step.class + assert_equal Dino::Components::Basic::DigitalOutput, part.direction.class + end + + def test_step_cw + dir_mock = MiniTest::Mock.new.expect :low, nil + step_mock = MiniTest::Mock.new + step_mock.expect :high, nil + step_mock.expect :low, nil + + part.stub(:direction, dir_mock) do + part.stub(:step, step_mock) do + part.step_cw + end + end + dir_mock.verify + step_mock.verify + end + + def test_step_cc + dir_mock = MiniTest::Mock.new.expect :high, nil + step_mock = MiniTest::Mock.new + step_mock.expect :high, nil + step_mock.expect :low, nil + + part.stub(:direction, dir_mock) do + part.stub(:step, step_mock) do + part.step_cc + end + end + dir_mock.verify + step_mock.verify + end +end diff --git a/test/message_test.rb b/test/message_test.rb new file mode 100644 index 00000000..d56294da --- /dev/null +++ b/test/message_test.rb @@ -0,0 +1,44 @@ +require 'dino' +require 'minitest/autorun' + +class MessageTest < Minitest::Test + def test_require_a_command + assert_raises(ArgumentError) { Dino::Message.encode } + assert_instance_of String, Dino::Message.encode(command: 90) + end + + def test_require_command_in_correct_range + assert_raises(ArgumentError) { Dino::Message.encode command: -1 } + assert_raises(ArgumentError) { Dino::Message.encode command: 256 } + end + + def test_require_pin_in_correct_range + assert_raises(ArgumentError) { Dino::Message.encode command: 0, pin: -1 } + assert_raises(ArgumentError) { Dino::Message.encode command: 0, pin: 256 } + end + + def test_require_value_in_correct_range + assert_raises(ArgumentError) { Dino::Message.encode command: 0, value: -1 } + assert_raises(ArgumentError) { Dino::Message.encode command: 0, value: 256 } + end + + # Test that aux message is limited by board aux_limit + + def test_build_messages_correctly + assert_equal "1.1.1\n", Dino::Message.encode(command: 1, pin: 1, value: 1) + assert_equal "1.1\n", Dino::Message.encode(command: 1, pin: 1) + assert_equal "1..1\n", Dino::Message.encode(command: 1, value: 1) + assert_equal "1\n", Dino::Message.encode(command: 1) + assert_equal "1...test\n", Dino::Message.encode(command: 1, aux_message: "test") + end + + def test_escape_newline_in_aux + assert_equal Dino::Message.encode(command: 1, aux_message: "line1\nline2"), + "1...line1\\\nline2\n" + end + + def test_escape_backslash_in_aux + assert_equal Dino::Message.encode(command: 1, aux_message: "line1\\line2"), + "1...line1\\\\line2\n" + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 00000000..b5b6a24b --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,29 @@ +require 'dino' + +# Nice little helper module to redefine constants quietly. +module Constants + def self.redefine(const, value, opts={}) + opts = {:on => self.class}.merge(opts) + opts[:on].send(:remove_const, const) if self.class.const_defined?(const) + opts[:on].const_set(const, value) + end +end + +# Taken from: https://gist.github.com/moertel/11091573 +def suppress_output + begin + original_stderr = $stderr.clone + original_stdout = $stdout.clone + $stderr.reopen(File.new('/dev/null', 'w')) + $stdout.reopen(File.new('/dev/null', 'w')) + retval = yield + rescue Exception => e + $stdout.reopen(original_stdout) + $stderr.reopen(original_stderr) + raise e + ensure + $stdout.reopen(original_stdout) + $stderr.reopen(original_stderr) + end + retval +end diff --git a/test/txrx/serial_test.rb b/test/txrx/serial_test.rb new file mode 100644 index 00000000..29aace3d --- /dev/null +++ b/test/txrx/serial_test.rb @@ -0,0 +1,159 @@ +require 'dino' +require 'minitest/autorun' +require 'txrx_mock' +require 'test_helper' + +class DummySerial + def read + "" + end + + def write(message) + nil + end +end + +class TxRxSerialTest < Minitest::Test + def mock_tty_device + "/dev/mock_serial" + end + + def txrx + @txrx ||= Dino::TxRx::Serial.new + end + + def io + suppress_output do + txrx.send(:io) + end + end + + def test_connect_with_device_specified + mock = MiniTest::Mock.new.expect(:call, "serial-obj", ["/dev/ttyACM0", 9600]) + Serial.stub(:new, mock) do + txrx = Dino::TxRx::Serial.new(device: "/dev/ttyACM0", baud: 9600) + assert_equal "serial-obj", suppress_output { txrx.send(:io) } + end + mock.verify + end + + def test_connect_with_no_devices + txrx.stub(:tty_devices, []) do + assert_raises(Dino::TxRx::SerialConnectError) { io } + end + end + + def test_connect_on_unix + txrx.stub(:tty_devices, ['/dev/ttyACM0', '/dev/tty.usbmodem1']) do + mock = MiniTest::Mock.new + # Raise error for first device + mock.expect(:call, nil) { raise RubySerial::Error } + mock.expect :call, "serial-obj", ['/dev/tty.usbmodem1', Dino::TxRx::Serial::BAUD] + + Serial.stub(:new, mock) do + assert_equal "serial-obj", io + end + mock.verify + end + end + + def test_connect_on_windows + # Simulate being on Windows + original_platform = RUBY_PLATFORM + Constants.redefine(:RUBY_PLATFORM, "mswin", :on => Object) + + mock = MiniTest::Mock.new + # Raise error for COM1 + mock.expect(:call, nil) { raise RubySerial::Error } + mock.expect :call, "serial-obj", ["COM2", Dino::TxRx::Serial::BAUD] + + Serial.stub(:new, mock) do + assert_equal "serial-obj", io + end + mock.verify + + # Set platform back to original value + Constants.redefine(:RUBY_PLATFORM, original_platform, :on => Object) + end + + def test_io_reset + flush_mock = MiniTest::Mock.new.expect :call, true + stop_mock = MiniTest::Mock.new.expect :call, true + start_mock = MiniTest::Mock.new.expect :call, true + txrx.stub(:flush_read, flush_mock) do + txrx.stub(:stop_read, stop_mock) do + txrx.stub(:start_read, start_mock) do + txrx.send(:io_reset) + end + end + end + flush_mock.verify + stop_mock.verify + start_mock.verify + end + + def test_read_and_parse + # Should not split on any colon after the first. + txrx.stub(:read, "02:00:00") do + txrx.stub(:changed, true) do + mock = MiniTest::Mock.new.expect :call, nil, ['02', '00:00'] + txrx.stub(:notify_observers, mock) do + txrx.send(:read_and_parse) + end + mock.verify + end + end + end + + # Test start read? + + def test_stop_read + thread = Thread.new { sleep } + mock = MiniTest::Mock.new.expect :call, nil, [thread] + txrx.instance_variable_set(:@thread, thread) + + Thread.stub(:kill, mock) do + txrx.send(:stop_read) + end + mock.verify + end + + def test_write + mock = MiniTest::Mock.new.expect :write, nil, ['message'] + txrx.stub(:io, mock) do + txrx.write('message') + end + mock.verify + end + + def test_read_single_chars_until_newline_and_strips_it + mock = MiniTest::Mock.new + "line\n".split("").each do |char| + mock.expect :read, char, [1] + end + txrx.stub(:io, mock) do + assert_equal "line", txrx.send(:read) + end + mock.verify + end + + def test_read_handles_escaped_newlines_and_backslashes + mock = MiniTest::Mock.new + "l1\\\nl2\\\\\n".split("").each do |char| + mock.expect :read, char, [1] + end + txrx.stub(:io, mock) do + assert_equal "l1\nl2\\", txrx.send(:read) + end + mock.verify + end + + def test_read_returns_empty_string_if_just_newline + mock = MiniTest::Mock.new + mock.expect :read, "\n", [1] + txrx.stub(:io, mock) do + assert_equal "", txrx.send(:read) + end + mock.verify + end +end diff --git a/test/txrx/tcp_test.rb b/test/txrx/tcp_test.rb new file mode 100644 index 00000000..a10f4958 --- /dev/null +++ b/test/txrx/tcp_test.rb @@ -0,0 +1,43 @@ +require 'dino' +require 'minitest/autorun' +require 'txrx_mock' +require 'test_helper' + +class DummySerial + def read + "" + end + + def write(message) + nil + end +end + +class TxRxTCPTest < Minitest::Test + def txrx + @txrx ||= Dino::TxRx::TCP.new("127.0.0.1", 3467) + end + + def io + suppress_output do + txrx.send(:io) + end + end + + def test_connect_raises_if_server_unavailable + assert_raises(Dino::TxRx::TCPConnectError) { io } + end + + def test_connect + server = TCPServer.new 3467 + assert_equal TCPSocket, io.class + server.close + end + + def test_io_doesnt_reconnect + server = TCPServer.new 3467 + socket = io + assert_equal socket, io + server.close + end +end diff --git a/test/txrx_mock.rb b/test/txrx_mock.rb new file mode 100644 index 00000000..80edbd40 --- /dev/null +++ b/test/txrx_mock.rb @@ -0,0 +1,10 @@ +require 'dino' + +class TxRxMock + def add_observer(board); true; end + def read; true; end + def write(str); true; end + def handshake + "528,14,20" + end +end From a6aa8b91a0a48685295faff94cc6af744e655a0d Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 10 Mar 2018 08:35:15 -0400 Subject: [PATCH 191/296] millis instead of micros for receive acknowledgement --- src/lib/Dino.cpp | 4 ++-- src/lib/Dino.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index d1a0e3b3..a444c566 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -30,10 +30,10 @@ void Dino::run(){ // Acknowledge when we've received as many bytes as the serial input buffer if (rcvBytes == rcvThreshold) acknowledge(); } - if (gotByte) lastRcv = micros(); + if (gotByte) lastRcv = millis(); // Also acknowledge when the last byte received goes outside the receive window. - if ((rcvBytes > 0) && ((micros() - lastRcv) > rcvWindow)) acknowledge(); + if ((rcvBytes > 0) && ((millis() - lastRcv) > rcvWindow)) acknowledge(); // Run dino's listeners. updateListeners(); diff --git a/src/lib/Dino.h b/src/lib/Dino.h index a31e778b..77944152 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -149,7 +149,7 @@ class Dino { // Keep count of bytes as we receive them and send a dino message with how many. uint8_t rcvBytes = 0; uint8_t rcvThreshold = 64; - unsigned long lastRcv = micros(); - long long rcvWindow = 1000000; + unsigned long lastRcv = millis(); + long long rcvWindow = 1000; }; #endif From e633e653447ccf45d4e43b2055194d4aea09b36f Mon Sep 17 00:00:00 2001 From: Vasiliy Gandzha Date: Thu, 3 May 2018 16:23:50 +0700 Subject: [PATCH 192/296] Update helper.rb fix error for ruby 2+ --- lib/dino/components/one_wire/helper.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/dino/components/one_wire/helper.rb b/lib/dino/components/one_wire/helper.rb index b4fcbd29..1bfff474 100644 --- a/lib/dino/components/one_wire/helper.rb +++ b/lib/dino/components/one_wire/helper.rb @@ -2,12 +2,14 @@ module Dino module Components module OneWire class Helper + ADDRESS_TO_BYTES_CLASSES = [Integer, Bignum, Fixnum].freeze + def self.address_to_bytes(address) [address].pack('Q<').split("").map(&:ord) end def self.crc_check(data) - if data.class == Integer + if ADDRESS_TO_BYTES_CLASSES.include?(data.class) bytes = address_to_bytes(data) else bytes = data From 5d2063cfea3a0a0ffb3bb751edf1cf41ad821c2c Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 1 Dec 2018 15:20:40 -0400 Subject: [PATCH 193/296] Small fixes to examples and comments --- examples/ethernet.rb | 15 -------- examples/one_wire/ds18b20.rb | 2 +- .../rotary_encoder_mac_volume.rb | 12 +++---- examples/ser2net.rb | 31 ----------------- examples/stepper/stepper.rb | 16 ++++----- examples/tcp.rb | 34 +++++++++++++++++++ lib/dino/components/i2c/bus.rb | 12 ++++--- lib/dino/components/one_wire/bus.rb | 1 + src/dino_wifi.ino | 2 +- 9 files changed, 57 insertions(+), 68 deletions(-) delete mode 100644 examples/ethernet.rb delete mode 100644 examples/ser2net.rb create mode 100644 examples/tcp.rb diff --git a/examples/ethernet.rb b/examples/ethernet.rb deleted file mode 100644 index 832dcee1..00000000 --- a/examples/ethernet.rb +++ /dev/null @@ -1,15 +0,0 @@ -# -# This example shows how to use an Arduino with an Ethernet shield and the du_ethernet.ino sketch loaded. -# Replace the IP address in this example with the IP you used when uploading the sketch. -# The Ethernet shield uses up pin 13, so you'll need an LED on pin 5 to make sure it's working. -# -require File.expand_path('../../lib/dino', __FILE__) - -connection = Dino::TxRx::TCP.new("192.168.0.77") -board = Dino::Board.new(connection) -led = Dino::Components::Led.new(pin: 5, board: board) - -[:on, :off].cycle do |switch| - led.send(switch) - sleep 0.5 -end diff --git a/examples/one_wire/ds18b20.rb b/examples/one_wire/ds18b20.rb index 5745aecb..be655c4c 100644 --- a/examples/one_wire/ds18b20.rb +++ b/examples/one_wire/ds18b20.rb @@ -14,7 +14,7 @@ end # Call #device_present to reset the bus and return presence pulse as a boolean. -if bus.device_present +if bus.device_present? puts "Devices present on bus..."; puts else puts "No devices present on bus... Quitting..." diff --git a/examples/rotary_encoder/rotary_encoder_mac_volume.rb b/examples/rotary_encoder/rotary_encoder_mac_volume.rb index bc80a479..160e60f2 100644 --- a/examples/rotary_encoder/rotary_encoder_mac_volume.rb +++ b/examples/rotary_encoder/rotary_encoder_mac_volume.rb @@ -1,10 +1,5 @@ # -# Example of a smiple rotary encoder polling at ~1ms. -# -# WARNING: This method is not precise at all. Please do not use it for anything -# that requires all steps to be read for precise positioning or high speed. -# -# Works well enough for making knobs like this example though. +# Example using a rotary encoder to control audio output volume on a Mac. # require 'bundler/setup' require 'dino' @@ -50,8 +45,9 @@ def set(value) value = 0 if value < 0 value = 100 if value > 100 volume.set(value) - unused_steps = value - volume.get - puts "Current volume: #{volume.get}%" + current_volume = volume.get + unused_steps = value - current_volume + puts "Current volume: #{current_volume}%" end sleep diff --git a/examples/ser2net.rb b/examples/ser2net.rb deleted file mode 100644 index 17d2afe5..00000000 --- a/examples/ser2net.rb +++ /dev/null @@ -1,31 +0,0 @@ -# -# This example shows how to use an Arduino on a remote machine via ser2net. -# Running ser2net serves up the serial connection over the network interface, -# so you can communicate with the Arduino over your local network or the Internet. -# -# Example ser2net command for an Arduino UNO on Mac: -# ser2net -u -C "3466:raw:0:/dev/cu.usbmodem621:115200" -# -# Note that we're using ser2net in raw TCP mode and not telnet mode which is more common. -# -# Replace /dev/cu.usbmodem621 with your Arduino device. -# Arduino UNOs are usually /dev/ttyACM0 under Linux. -# -# ser2net is preinstalled on many Linuxes. Install ser2net at the Mac Terminal with: -# brew install ser2net -# -# http://sourceforge.net/projects/ser2net/ for more info on installing and configuring ser2net. -# - -require File.expand_path('../../lib/dino', __FILE__) - -# The remote ser2net host is the first argument for a new TxRx::TCP. -# The second argument, port number, is optional. 3466 is default. This must match the port ser2net uses on the remote machine. -connection = Dino::TxRx::TCP.new("127.0.0.1", 3466) -board = Dino::Board.new(connection) -led = Dino::Components::Led.new(pin: 13, board: board) - -[:on, :off].cycle do |switch| - led.send(switch) - sleep 0.5 -end diff --git a/examples/stepper/stepper.rb b/examples/stepper/stepper.rb index 6a403d6c..c79c8b2c 100644 --- a/examples/stepper/stepper.rb +++ b/examples/stepper/stepper.rb @@ -7,12 +7,12 @@ board = Dino::Board.new(Dino::TxRx::Serial.new) stepper = Dino::Components::Stepper.new(board: board, pins: { step: 10, direction: 8 }) - 1600.times do - stepper.step_cc - sleep 0.001 - end +1600.times do + stepper.step_cc + sleep 0.001 +end - 1600.times do - stepper.step_cw - sleep 0.001 - end +1600.times do + stepper.step_cw + sleep 0.001 +end diff --git a/examples/tcp.rb b/examples/tcp.rb new file mode 100644 index 00000000..114b5063 --- /dev/null +++ b/examples/tcp.rb @@ -0,0 +1,34 @@ +require 'bundler/setup' +require 'dino' +# +# This example shows how to use dino when connecting to a board via TCP. +# This applies to the WiFi and Ethernet sketches, or serial sketch + ser2net. +# Port number defaults to 3466 (dino), but may be given as a second argument. +# It must correspond to the listening port set when the board was flashed. +# +connection = Dino::TxRx::TCP.new("127.0.0.1", 3466) +# connection = Dino::TxRx::TCP.new("127.0.0.1") +# connection = Dino::TxRx::TCP.new("192.168.1.2", 3466) +# +board = Dino::Board.new(connection) +led = Dino::Components::Led.new(pin: 13, board: board) + +[:on, :off].cycle do |switch| + led.send(switch) + sleep 0.5 +end +# +# ser2net can be used to simulate a TCP interface from a board running dino serial. +# It serves the serial interface over a TCP port from the machine running ser2net. +# +# Example ser2net command for an Arduino UNO connected to a Mac: +# ser2net -u -C "3466:raw:0:/dev/cu.usbmodem621:115200" +# +# Tell dino to connect to the IP address of the Mac, at port 3466. +# Note: ser2net should be used in raw TCP mode, not telnet mode (more common). +# +# Replace /dev/cu.usbmodem621 with your dino serial device. +# Arduino UNOs should be something like /dev/ttyACM0 under Linux. +# +# http://sourceforge.net/projects/ser2net/ for more info on installing and configuring ser2net. +# diff --git a/lib/dino/components/i2c/bus.rb b/lib/dino/components/i2c/bus.rb index c36db726..bdc6904b 100644 --- a/lib/dino/components/i2c/bus.rb +++ b/lib/dino/components/i2c/bus.rb @@ -22,6 +22,10 @@ def write(address, bytes=[], options={}) board.i2c_write(address, [bytes].flatten, options) end + def transfer(address, options={}) + board.i2c_transfer(address, options) + end + def read(*args) read_using -> { _read(*args) } end @@ -36,15 +40,15 @@ def bubble_callbacks address, data = str.split("-", 2) address = address.to_i data = data.split(",").map(&:to_i) - update_component({address: address, data: data}) + update_component(address, data) end end end - def update_component(hash) + def update_component(address, data) components.each do |component| - if component.address == hash[:address] - component.update(hash[:data]) + if component.address == address + component.update(data) end end end diff --git a/lib/dino/components/one_wire/bus.rb b/lib/dino/components/one_wire/bus.rb index 7b3b81b6..35862815 100644 --- a/lib/dino/components/one_wire/bus.rb +++ b/lib/dino/components/one_wire/bus.rb @@ -40,6 +40,7 @@ def device_present (byte == 0) ? true : false end end + alias :device_present? :device_present def reset(get_presence=0) board.one_wire_reset(pin, get_presence) diff --git a/src/dino_wifi.ino b/src/dino_wifi.ino index 38c95807..6c6e2e04 100644 --- a/src/dino_wifi.ino +++ b/src/dino_wifi.ino @@ -8,7 +8,7 @@ #define LED_PIN 13 #endif -// Configure your WiFi options here. IP address are not configurable. Uses DHCP. +// Configure your WiFi options here. IP address is not configurable. Uses DHCP. int port = 3466; char* ssid = "yourNetwork"; char* pass = "yourPassword"; From fa8d65027412ccf113100848075c4b0491bd8086 Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 2 Dec 2018 01:47:08 -0400 Subject: [PATCH 194/296] Add basic EEPROM read/write functionality --- dino.gemspec | 2 +- lib/dino/api.rb | 1 + lib/dino/api/core.rb | 4 +- lib/dino/api/eeprom.rb | 24 ++++++++++++ lib/dino/board/base.rb | 32 ++++++++++++--- lib/dino/components/basic.rb | 3 +- lib/dino/components/basic/board_eeprom.rb | 47 +++++++++++++++++++++++ lib/dino/components/mixins/reader.rb | 6 ++- lib/dino/tx_rx/base.rb | 5 +-- lib/dino_cli/packages.rb | 1 - src/lib/Dino.cpp | 9 +++-- src/lib/Dino.h | 10 ++++- src/lib/DinoBugWorkaround.cpp | 24 ------------ src/lib/DinoCoreIO.cpp | 47 ++++++++++++++++++++++- src/lib/DinoSPI.cpp | 8 ++-- test/api/core_test.rb | 8 ++-- test/board_test.rb | 15 +++++--- test/txrx/serial_test.rb | 3 +- test/txrx_mock.rb | 2 +- 19 files changed, 188 insertions(+), 63 deletions(-) create mode 100644 lib/dino/api/eeprom.rb create mode 100644 lib/dino/components/basic/board_eeprom.rb delete mode 100644 src/lib/DinoBugWorkaround.cpp diff --git a/dino.gemspec b/dino.gemspec index 7d9d24cb..4b0e967a 100644 --- a/dino.gemspec +++ b/dino.gemspec @@ -34,7 +34,7 @@ Gem::Specification.new do |gem| gem.version = Dino::VERSION gem.executables = ["dino"] - gem.add_dependency 'rubyserial', '~> 0.5.0' + gem.add_dependency 'rubyserial', '~> 0.6.0' gem.add_dependency 'bcd', '~> 0.3.0' gem.add_development_dependency 'rake' diff --git a/lib/dino/api.rb b/lib/dino/api.rb index 8d10255a..94d61e96 100644 --- a/lib/dino/api.rb +++ b/lib/dino/api.rb @@ -2,6 +2,7 @@ module Dino module API require 'dino/api/helper' require 'dino/api/core' + require 'dino/api/eeprom' require 'dino/api/dht' require 'dino/api/i2c' require 'dino/api/infrared' diff --git a/lib/dino/api/core.rb b/lib/dino/api/core.rb index 14eadb3a..609b9834 100644 --- a/lib/dino/api/core.rb +++ b/lib/dino/api/core.rb @@ -38,7 +38,7 @@ def set_pullup(pin, pullup) pullup ? digital_write(pin, @high) : digital_write(pin, @low) end - # CMD = 7 + # CMD = 5 def set_listener(pin, state=:off, options={}) mode = options[:mode] || :digital divider = options[:divider] || 8 @@ -54,7 +54,7 @@ def set_listener(pin, state=:off, options={}) mask |= 0b10000000 if (state == :on) mask |= 0b01000000 if (mode == :analog) - write Message.encode(command: 7, pin: convert_pin(pin), value: mask) + write Message.encode(command: 5, pin: convert_pin(pin), value: mask) end # Convenience methods by wrapping set_listener with old defaults. diff --git a/lib/dino/api/eeprom.rb b/lib/dino/api/eeprom.rb new file mode 100644 index 00000000..2b918220 --- /dev/null +++ b/lib/dino/api/eeprom.rb @@ -0,0 +1,24 @@ +module Dino + module API + module EEPROM + include Helper + + # CMD = 6 + def eeprom_read(address, num_bytes) + address = pack :uint16, address + write Message.encode command: 6, + value: num_bytes, + aux_message: address + end + + # CMD = 7 + def eeprom_write(address, bytes=[]) + address = pack :uint16, address + bytes = pack :uint8, bytes, min: 1, max: 128 + write Message.encode command: 7, + value: bytes.length, + aux_message: address + bytes + end + end + end +end diff --git a/lib/dino/board/base.rb b/lib/dino/board/base.rb index df99bc38..17587dc6 100644 --- a/lib/dino/board/base.rb +++ b/lib/dino/board/base.rb @@ -2,15 +2,18 @@ module Dino module Board class Base include API::Core + include API::EEPROM - attr_reader :high, :low, :analog_high, :components, :analog_zero, :dac_zero + attr_reader :high, :low, :analog_high, :components + attr_reader :analog_zero, :dac_zero, :aux_limit, :eeprom_length def initialize(io, options={}) @io, @components = io, [] - ack = io.handshake.split(",").map { |num| num.to_i } - @aux_limit, @analog_zero, @dac_zero = ack - # Leave room for null termination. + ack = io.handshake.split(",").map(&:to_i) + @aux_limit, @eeprom_length, @analog_zero, @dac_zero = ack + + # Leave room for null termination of aux messages. @aux_limit = @aux_limit - 1 io.add_observer(self) @@ -38,9 +41,26 @@ def write(msg) @io.write(msg) end - def update(pin, msg) + def update(line) + case line + when /\AEE:/ + update_eeprom(line) + else + update_component(line) + end + end + + def update_eeprom(line) + message = line.split(":", 2)[1] + @components.each do |part| + part.update(message) if part.pin == "EE" + end + end + + def update_component(line) + pin, message = line.split(":", 2) @components.each do |part| - part.update(msg) if pin.to_i == part.pin + part.update(message) if pin.to_i == convert_pin(part.pin) end end diff --git a/lib/dino/components/basic.rb b/lib/dino/components/basic.rb index 79463819..61544cd0 100644 --- a/lib/dino/components/basic.rb +++ b/lib/dino/components/basic.rb @@ -5,6 +5,7 @@ module Basic require 'dino/components/basic/analog_input' require 'dino/components/basic/digital_input' require 'dino/components/basic/analog_output' + require 'dino/components/basic/board_eeprom' end end -end \ No newline at end of file +end diff --git a/lib/dino/components/basic/board_eeprom.rb b/lib/dino/components/basic/board_eeprom.rb new file mode 100644 index 00000000..1842e8cc --- /dev/null +++ b/lib/dino/components/basic/board_eeprom.rb @@ -0,0 +1,47 @@ +module Dino + module Components + module Basic + class BoardEEPROM + include Setup::Base + include Mixins::Reader + + public :state= + + def after_initialize(options) + super(options) + self.state = Array.new(board.eeprom_length, nil) + load + end + + def load + state.each_slice(128).with_index do |slice, index| + read_using -> { board.eeprom_read(index * 128, slice.length) } + end + end + + def save + @state_mutex.synchronize do + @state.each_slice(128).with_index do |slice, index| + board.eeprom_write(index * 128, slice) + end + end + load + end + + def pre_callback_filter(message) + address = message.split("-", 2)[0].to_i + bytes = message.split("-", 2)[1].split(",").map(&:to_i) + {address: address, data: bytes} + end + + def update_self(hash) + self.state[hash[:address], hash[:data].length] = hash[:data] + end + + def pin + "EE" + end + end + end + end +end diff --git a/lib/dino/components/mixins/reader.rb b/lib/dino/components/mixins/reader.rb index ef332c9e..d387d17d 100644 --- a/lib/dino/components/mixins/reader.rb +++ b/lib/dino/components/mixins/reader.rb @@ -22,7 +22,11 @@ def read(&block) end def block_until_read - loop { break if !@callbacks[:read] } + loop do + break if !@callbacks[:read] + # EEPROM read won't work without sleeping here. Not sure why. + sleep 0.001 + end end def _read diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index 972d7de0..273bd90f 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -54,10 +54,7 @@ def stop_read end def parse(line) - if line.match(/\A\d+:/) - pin, message = line.split(":", 2) - pin && message && changed && notify_observers(pin, message) - end + changed && notify_observers(line) end end end diff --git a/lib/dino_cli/packages.rb b/lib/dino_cli/packages.rb index 8dc06c35..65dea32c 100644 --- a/lib/dino_cli/packages.rb +++ b/lib/dino_cli/packages.rb @@ -10,7 +10,6 @@ class DinoCLI::Generator "lib/Dino.cpp", "lib/DinoCoreIO.cpp", "lib/DinoIncludes.cpp", - "lib/DinoBugWorkaround.cpp", # See explanation at top of file. ] }, servo: { diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index a444c566..3a37933e 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -97,16 +97,15 @@ void Dino::process() { // Call the command. switch(cmd) { - // See explanation at top of DinoBugWorkaround.cpp - case 999999: bugWorkaround (); break; - // Implemented in DinoCoreIO.cpp case 0: setMode (); break; case 1: dWrite (); break; case 2: dRead (pin); break; case 3: aWrite (); break; case 4: aRead (pin); break; - case 7: setListener (); break; + case 5: setListener (); break; + case 6: eepromRead (); break; + case 7: eepromWrite (); break; case 11: pulseRead (); break; // Implemented in DinoServo.cpp @@ -226,6 +225,8 @@ void Dino::reset() { stream->print("ACK:"); stream->print(AUX_SIZE); stream->print(','); + stream->print(EEPROM.length()); + stream->print(','); stream->print(A0); #if defined(__SAM3X8E__) stream->print(','); diff --git a/src/lib/Dino.h b/src/lib/Dino.h index 77944152..d44f7517 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -4,6 +4,7 @@ #ifndef Dino_h #define Dino_h #include +#include #include "DinoDefines.h" // Figure out how many pins our hardware has. @@ -39,12 +40,16 @@ class Dino { void aRead (int pin); //cmd = 4 // Core IO Listeners - void setListener (); //cmd = 7 + void setListener (); //cmd = 5 void updateListeners (); void updateCoreListeners (byte tickCount); void digitalListenerUpdate (byte index); void clearCoreListeners (); + // EEPROM Access + void eepromRead (); //cmd = 6 + void eepromWrite (); //cmd = 7 + // // Store listeners as a 2 dimensional array where each gets 2 bytes, such that: // @@ -95,10 +100,11 @@ class Dino { void clearSpiListeners (); // I2C - void i2cBegin (); //cmd = 31 + void i2cBegin (); void i2cSearch (); //cmd = 33 void i2cWrite (); //cmd = 34 void i2cRead (); //cmd = 35 + void i2cTransfer (); //cmd = 36 // One Wire void owReset (); //cmd = 41 diff --git a/src/lib/DinoBugWorkaround.cpp b/src/lib/DinoBugWorkaround.cpp deleted file mode 100644 index cd7dc5e3..00000000 --- a/src/lib/DinoBugWorkaround.cpp +++ /dev/null @@ -1,24 +0,0 @@ -// -// After refactoring there's a strange gcc bug referencing the first -// instance of sprintf in Dino.cpp, only when compiling for the 328p/Uno/Nano/Mini. -// -// It only occurs when trying to exlude all the new .cpp files by commenting -// out the #define lines at the top of Dino.h. At least one needed to be left in -// to avoid the error. -// -// Narrowed it down to the following: -// Must declare a function in a separate .cpp file. -// That function must mutate some instance variable in Dino. -// That function must be one of the switch options in the Dino::process case statement. -// -// This file is a stub to meet those requirements. -// Leaving this in until sprintf usage is replaced or bug is fixed. -// -// Similar issues: -// https://github.com/arduino/Arduino/issues/3972 -// https://github.com/CongducPham/LowCostLoRaGw/issues/28 -// -#include "Dino.h" -void Dino::bugWorkaround() { - rval = 0; -} diff --git a/src/lib/DinoCoreIO.cpp b/src/lib/DinoCoreIO.cpp index a9f4bf01..f8d1a9b5 100644 --- a/src/lib/DinoCoreIO.cpp +++ b/src/lib/DinoCoreIO.cpp @@ -73,7 +73,7 @@ void Dino::coreResponse(int p, int v){ stream->print('\n'); } -// CMD = 07 +// CMD = 05 // Set a listener ON or OFF, or change its type, or divider. // Takes settings as mask stored in val and applies to existing listener // if pin was already used, or first inactive. See Dino.h for mask structure. @@ -195,3 +195,48 @@ void Dino::pulseRead(){ } if (pulseCount == 0) stream->print('\n'); } + + +// CMD = 6 +// Read from the microcontroller's EEPROM. +// +// pin = empty +// val = number of bytes to read +// auxMsg[0-1] = start address +// +void Dino::eepromRead(){ + if (val > 0) { + uint16_t startAddress = (uint16_t)auxMsg[1] << 8 | auxMsg[0]; + + // Stream read bytes as if coming from a pin named 'EE'. + stream->print("EE"); + stream->print(':'); + stream->print(startAddress); + stream->print('-'); + + for (byte i = 0; (i < val); i++) { + stream->print(EEPROM.read(startAddress + i)); + stream->print((i+1 == val) ? '\n' : ','); + } + } +} + +// CMD = 7 +// Write to the microcontroller's EEPROM. +// +// pin = empty +// val = number of bytes to write +// auxMsg[0-1] = start address +// auxMsg[2+] = bytes to write +// +void Dino::eepromWrite(){ + if (val > 0) { + uint16_t startAddress = (uint16_t)auxMsg[1] << 8 | auxMsg[0]; + + for (byte i = 0; (i < val); i++) { + if(EEPROM.read(startAddress + i) != auxMsg[2+i]) { + EEPROM.write(startAddress + i, auxMsg[2+i]); + } + } + } +} diff --git a/src/lib/DinoSPI.cpp b/src/lib/DinoSPI.cpp index 2a2292a4..992fd9a2 100644 --- a/src/lib/DinoSPI.cpp +++ b/src/lib/DinoSPI.cpp @@ -42,19 +42,19 @@ void Dino::spiEnd(){ #endif } +// CMD = 26 +// Simultaneous read from and write to an SPI device. // // Request format for SPI 2-way transfers // pin = slave select pin (int) // val = empty -// auxMsg[0] = SPI settings. 2 LSB = SPI mode. Bit 7 = MSB(1) / LSB(0). +// auxMsg[0] = SPI settings. 2 LSB = SPI mode. Bit 7 = MSB(1) or LSB(0). // auxMsg[1] = write length (number of bytes) // auxMsg[2] = read length (number of bytes) // auxMsg[3-6] = clock frequency (uint32_t as 4 bytes) // -// auxMsg[7]+ = data (bytes) (write only) +// auxMsg[7+] = data (bytes) (write only) // -// CMD = 26 -// Write to an SPI device. void Dino::spiTransfer(int selectPin, byte settings, byte rLength, byte wLength, uint32_t clockRate, byte *data) { spiBegin(settings, clockRate); digitalWrite(selectPin, LOW); diff --git a/test/api/core_test.rb b/test/api/core_test.rb index 5511a920..3399fd5e 100644 --- a/test/api/core_test.rb +++ b/test/api/core_test.rb @@ -83,10 +83,10 @@ def test_analog_read def test_set_listener mock = MiniTest::Mock.new - mock.expect(:call, nil, [Dino::Message.encode(command: 7, pin: 1, value: 0b00000011)]) - mock.expect(:call, nil, [Dino::Message.encode(command: 7, pin: 1, value: 0b01000011)]) - mock.expect(:call, nil, [Dino::Message.encode(command: 7, pin: 1, value: 0b11000011)]) - mock.expect(:call, nil, [Dino::Message.encode(command: 7, pin: 1, value: 0b11000000)]) + mock.expect(:call, nil, [Dino::Message.encode(command: 5, pin: 1, value: 0b00000011)]) + mock.expect(:call, nil, [Dino::Message.encode(command: 5, pin: 1, value: 0b01000011)]) + mock.expect(:call, nil, [Dino::Message.encode(command: 5, pin: 1, value: 0b11000011)]) + mock.expect(:call, nil, [Dino::Message.encode(command: 5, pin: 1, value: 0b11000000)]) board.stub(:write, mock) do board.set_listener(1, :off) diff --git a/test/board_test.rb b/test/board_test.rb index 9bdc917b..4e964cfb 100644 --- a/test/board_test.rb +++ b/test/board_test.rb @@ -25,7 +25,7 @@ def test_starts_observing_txrx end def test_calls_handshake_on_txrx - mock = MiniTest::Mock.new.expect(:call, "528,14,20") + mock = MiniTest::Mock.new.expect(:call, "actual ack value in txrx_mock.rb") txrx.stub(:handshake, mock) do Dino::Board.new(txrx) end @@ -36,6 +36,10 @@ def test_set_aux_limit assert_equal 527, board.aux_limit end + def test_set_eeprom_length + assert_equal 1024, board.eeprom_length + end + def test_set_dac_and_analog_zero assert_equal 20, board.dac_zero assert_equal 14, board.analog_zero @@ -70,13 +74,14 @@ def test_write def test_update_passes_messages_to_components mock1 = MiniTest::Mock.new.expect(:update, nil, ["data"]) 3.times { mock1.expect(:pin, 1) } - mock2 = MiniTest::Mock.new.expect(:update, nil, ["byte"]) + # This tests that lines are not split after the first colon delimiter. + mock2 = MiniTest::Mock.new.expect(:update, nil, ["with:colon"]) 3.times { mock2.expect(:pin, 2) } board.add_component(mock1) board.add_component(mock2) - board.update(1, "data") - board.update(2, "byte") - board.update(3, "ignore") + board.update("1:data") + board.update("2:with:colon") + board.update("3:ignore") mock1.verify mock2.verify end diff --git a/test/txrx/serial_test.rb b/test/txrx/serial_test.rb index 29aace3d..defadb80 100644 --- a/test/txrx/serial_test.rb +++ b/test/txrx/serial_test.rb @@ -93,10 +93,9 @@ def test_io_reset end def test_read_and_parse - # Should not split on any colon after the first. txrx.stub(:read, "02:00:00") do txrx.stub(:changed, true) do - mock = MiniTest::Mock.new.expect :call, nil, ['02', '00:00'] + mock = MiniTest::Mock.new.expect :call, nil, ['02:00:00'] txrx.stub(:notify_observers, mock) do txrx.send(:read_and_parse) end diff --git a/test/txrx_mock.rb b/test/txrx_mock.rb index 80edbd40..65d99435 100644 --- a/test/txrx_mock.rb +++ b/test/txrx_mock.rb @@ -5,6 +5,6 @@ def add_observer(board); true; end def read; true; end def write(str); true; end def handshake - "528,14,20" + "528,1024,14,20" end end From 63b302e0a7041500244df8a8c6cf98745ba577aa Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 9 Dec 2018 14:52:38 -0400 Subject: [PATCH 195/296] Callbacks to run local code (on the board) based on listeners --- lib/dino/api/core.rb | 10 +- lib/dino_cli/packages.rb | 2 + src/dino_ethernet.ino | 15 ++- src/dino_serial.ino | 23 +++- src/dino_wifi.ino | 16 ++- src/lib/Dino.cpp | 38 +++--- src/lib/Dino.h | 93 +++++++------ src/lib/DinoCoreIO.cpp | 264 ++++++++++++++++--------------------- src/lib/DinoEEPROM.cpp | 48 +++++++ src/lib/DinoPulseInput.cpp | 57 ++++++++ test/api/core_test.rb | 9 +- 11 files changed, 350 insertions(+), 225 deletions(-) create mode 100644 src/lib/DinoEEPROM.cpp create mode 100644 src/lib/DinoPulseInput.cpp diff --git a/lib/dino/api/core.rb b/lib/dino/api/core.rb index 609b9834..c6116f0c 100644 --- a/lib/dino/api/core.rb +++ b/lib/dino/api/core.rb @@ -50,11 +50,13 @@ def set_listener(pin, state=:off, options={}) raise "Listener divider must be in #{DIVIDERS.inspect}" end - mask = Math.log2(divider).to_i - mask |= 0b10000000 if (state == :on) - mask |= 0b01000000 if (mode == :analog) + exponent = Math.log2(divider).to_i + aux = pack :uint8, [mode == :analog ? 1 : 0, exponent] - write Message.encode(command: 5, pin: convert_pin(pin), value: mask) + write Message.encode command: 5, + pin: convert_pin(pin), + value: (state == :on ? 1 : 0), + aux_message: aux end # Convenience methods by wrapping set_listener with old defaults. diff --git a/lib/dino_cli/packages.rb b/lib/dino_cli/packages.rb index 65dea32c..53b6eac5 100644 --- a/lib/dino_cli/packages.rb +++ b/lib/dino_cli/packages.rb @@ -9,6 +9,8 @@ class DinoCLI::Generator "lib/DinoDefines.h", "lib/Dino.cpp", "lib/DinoCoreIO.cpp", + "lib/DinoPulseInput.cpp", + "lib/DinoEEPROM.cpp", "lib/DinoIncludes.cpp", ] }, diff --git a/src/dino_ethernet.ino b/src/dino_ethernet.ino index cc762432..c5bff54b 100644 --- a/src/dino_ethernet.ino +++ b/src/dino_ethernet.ino @@ -33,6 +33,9 @@ void setup() { Ethernet.begin(mac, ip); server.begin(); printEthernetStatus(); + + dino.digitalListenCallback = onDigitalListen; + dino.analogListenCallback = onAnalogListen; } @@ -41,7 +44,7 @@ void loop() { client = server.available(); // Pass the stream to dino so it can read/write. - dino.setOutputStream(&client); + dino.stream = &client; // Handle a connection. if (client) { @@ -51,3 +54,13 @@ void loop() { // End the connection. client.stop(); } + +// This runs every time a digital pin that dino is listening to changes value. +// p = pin number, v = current value +void onDigitalListen(byte p, byte v){ +} + +// This runs every time an analog pin that dino is listening to gets read. +// p = pin number, v = read value +void onAnalogListen(byte p, int v){ +} diff --git a/src/dino_serial.ino b/src/dino_serial.ino index 1c890f07..c977d571 100644 --- a/src/dino_serial.ino +++ b/src/dino_serial.ino @@ -6,22 +6,35 @@ Dino dino; // Defaults to Native USB port on the Due, whatever class "Serial" is on everything else. // Classes need to inherit from Stream to be compatible with the Dino library. #if defined(__SAM3X8E__) -#define serial SerialUSB -//#define serial Serial + #define serial SerialUSB + //#define serial Serial #else -#define serial Serial + #define serial Serial #endif - void setup() { // Wait for serial ready. serial.begin(115200); while(!serial); // Pass serial stream to dino so it can read/write. - dino.setOutputStream(&serial); + dino.stream = &serial; + + // Add listener callbacks for local logic. + dino.digitalListenCallback = onDigitalListen; + dino.analogListenCallback = onAnalogListen; } void loop() { dino.run(); } + +// This runs every time a digital pin that dino is listening to changes value. +// p = pin number, v = current value +void onDigitalListen(byte p, byte v){ +} + +// This runs every time an analog pin that dino is listening to gets read. +// p = pin number, v = read value +void onAnalogListen(byte p, int v){ +} diff --git a/src/dino_wifi.ino b/src/dino_wifi.ino index 6c6e2e04..646fab5f 100644 --- a/src/dino_wifi.ino +++ b/src/dino_wifi.ino @@ -64,8 +64,10 @@ void setup() { connect(); server.begin(); -} + dino.digitalListenCallback = onDigitalListen; + dino.analogListenCallback = onAnalogListen; +} void loop() { // Reconnect if we've lost WiFi.. @@ -78,7 +80,7 @@ void loop() { client = server.available(); // Pass the stream to dino so it can read/write. - dino.setOutputStream(&client); + dino.stream = &client; // Handle a connection. if (client) { @@ -88,3 +90,13 @@ void loop() { // End the connection. client.stop(); } + +// This runs every time a digital pin that dino is listening to changes value. +// p = pin number, v = current value +void onDigitalListen(byte p, byte v){ +} + +// This runs every time an analog pin that dino is listening to gets read. +// p = pin number, v = read value +void onAnalogListen(byte p, int v){ +} diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 3a37933e..35e15b94 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -90,23 +90,26 @@ void Dino::process() { val = atoi((char *)valStr); #ifdef debug - Serial.print("Received - Command: "); Serial.print(cmdStr); - Serial.print(" Pin: "); Serial.print(pinStr); - Serial.print(" Value: "); Serial.print(valStr); Serial.print("\n") + if (cmd != 90) { + Serial.print("cmd:"); Serial.print(cmd); + Serial.print(", pin:"); Serial.print(pin); + Serial.print(", val:"); Serial.println(val); + } #endif // Call the command. switch(cmd) { // Implemented in DinoCoreIO.cpp - case 0: setMode (); break; - case 1: dWrite (); break; - case 2: dRead (pin); break; - case 3: aWrite (); break; - case 4: aRead (pin); break; - case 5: setListener (); break; + case 0: setMode (pin, val); break; + case 1: dWrite (pin, val, false); break; + case 2: dRead (pin); break; + case 3: aWrite (pin, val, false); break; + case 4: aRead (pin); break; + case 5: setListener (pin, val, auxMsg[0], auxMsg[1], false); break; + + // Implemented in DinoEEPROM.cpp case 6: eepromRead (); break; case 7: eepromWrite (); break; - case 11: pulseRead (); break; // Implemented in DinoServo.cpp #ifdef DINO_SERVO @@ -119,6 +122,9 @@ void Dino::process() { case 10: handleLCD (); break; #endif + // Implemented in DinoPulseInput.cpp + case 11: pulseRead (); break; + // Implemented in DinoSerial.cpp #ifdef DINO_SERIAL case 12: handleSerial (); break; @@ -174,17 +180,6 @@ void Dino::process() { // Should send a "feature not implemented" message as default. default: break; } - - #ifdef debug - Serial.print("Responded with - "); Serial.print(response); Serial.print("\n\n"); - #endif -} - - -// Expect the sketch to pass a pointer to something that inherits from Stream. -// Store it and call ->print, ->write, etc on it to send data. -void Dino::setOutputStream(Stream* callback){ - stream = callback; } // @@ -238,7 +233,6 @@ void Dino::reset() { void Dino::resetState() { clearCoreListeners(); - lastActiveListener = 0; #ifdef DINO_SPI clearSpiListeners(); #endif diff --git a/src/lib/Dino.h b/src/lib/Dino.h index d44f7517..6fcc4016 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -19,45 +19,54 @@ class Dino { public: Dino(); - void setOutputStream(Stream* callback); void run(); - private: - // Main loop functions. - void acknowledge(); - void parse(byte c); - void process(); + // Expect main sketch to pass a reference to a Stream-like object for IO. + // Store it and call ->print, ->write, ->available, ->read etc. on it. + Stream* stream; - // See explanation at top of DinoBugWorkaround.cpp - void bugWorkaround(); + // Callback hooks for local logic (defined in main sketch) based on listeners. + // These should be used if: + // 1) a round-trip to the remote client is too slow, or + // 2) something needs to happen regardless of remote client connection. + // Eg. Instant feedback from a smart light switch. + void (*digitalListenCallback)(byte p, byte v); + void (*analogListenCallback)(byte p, int v); + // Core IO API Functions // Functions with a cmd value can be called through the remote API. - // Core IO Functions - void setMode (); //cmd = 0 - void dWrite (); //cmd = 1 - void dRead (int pin); //cmd = 2 - void aWrite (); //cmd = 3 - void aRead (int pin); //cmd = 4 - - // Core IO Listeners - void setListener (); //cmd = 5 + // + // This subset is made public to allow use by local listener callbacks. + // Reading and writing through the dino library can help maintain + // consistency and notify the remote client of local actions automatically. + // + void setMode (byte p, byte m); //cmd = 0 + void dWrite (byte p, byte v, boolean echo=true); //cmd = 1 + byte dRead (byte p); //cmd = 2 + void aWrite (byte p, int v, boolean echo=true); //cmd = 3 + int aRead (byte p); //cmd = 4 + void setListener (byte p, boolean enabled, byte analog, byte exponent, boolean local=true); //cmd = 5 + + private: + // + // Main loop listen functions. + // void updateListeners (); void updateCoreListeners (byte tickCount); + void analogListenerUpdate (byte index); void digitalListenerUpdate (byte index); void clearCoreListeners (); - - // EEPROM Access - void eepromRead (); //cmd = 6 - void eepromWrite (); //cmd = 7 + void findLastActiveListener(); // - // Store listeners as a 2 dimensional array where each gets 2 bytes, such that: + // Store listeners as a 2 dimensional array where each gets 2 bytes: // - // byte 0, bit 7 : 1 for listener enabled, 0 for listener disabled. - // byte 0, bit 6 : 1 for analog listener, 0 for digital listener. - // byte 0, bit 5 : storage for digital listener state - // byte 0, bits 4-3: unused - // byte 0, bits 2-0: timing divider exponent specific to this pin, 2^0 through 2^8 + // byte 0, bit 7 : 1 = enabled, 0 = disabled + // byte 0, bit 6 : 1 = analog, 0 = digital + // byte 0, bit 5 : digital listener state storage + // byte 0, bit 4 : local flag (remote client cannot modify listener if set) + // byte 0, bit 3 : unused + // byte 0, bits 2-0: timing divider exponent (2^0 through 2^8) // // byte 1 : pin number // @@ -68,10 +77,14 @@ class Dino { // Map 2's exponents for dividers to save time. const byte dividerMap[8] = {1, 2, 4, 8, 16, 32, 64, 128}; - // Storage and response func for features following the pin:rval pattern. - int rval; + // Response func for features following the pin:value pattern. void coreResponse(int p, int v); + // Functions with a cmd value can be called through the remote API. + // EEPROM Access + void eepromRead (); //cmd = 6 + void eepromWrite (); //cmd = 7 + // Included Libraries void servoToggle (); //cmd = 8 void servoWrite (); //cmd = 9 @@ -114,11 +127,23 @@ class Dino { void owWriteBit (byte b); byte owReadBit (); - // API access to timings, resolutions and reset. + // + // Board level timings, resolutions and reset. + // void reset (); //cmd = 90 void resetState (); void setRegisterDivider (); //cmd = 97 void setAnalogResolution (); //cmd = 96 + unsigned long lastTick; + byte tickCount; + byte registerDivider; + + // + // Main loop input functions. + // + void acknowledge(); + void parse(byte c); + void process(); // Parser state storage and utility functions. byte *messageFragments[4]; @@ -144,14 +169,6 @@ class Dino { #endif byte auxMsg[AUX_SIZE]; - // Save a pointer to any stream so we can call ->print and ->write on it. - Stream* stream; - - // Internal timing variables and utility functions. - unsigned long lastTick; - byte tickCount; - byte registerDivider; - // Keep count of bytes as we receive them and send a dino message with how many. uint8_t rcvBytes = 0; uint8_t rcvThreshold = 64; diff --git a/src/lib/DinoCoreIO.cpp b/src/lib/DinoCoreIO.cpp index f8d1a9b5..1b302f3b 100644 --- a/src/lib/DinoCoreIO.cpp +++ b/src/lib/DinoCoreIO.cpp @@ -2,70 +2,86 @@ // CMD = 00 // Set a pin to output (0), or input (1). -void Dino::setMode() { - if (val == 0) { - pinMode(pin, OUTPUT); +void Dino::setMode(byte p, byte m) { + #ifdef debug + Serial.print("setmode, pin:"); + Serial.print(p); + Serial.print(", mode:"); + Serial.println(m); + #endif + + if (m == 0) { + pinMode(p, OUTPUT); } else { - pinMode(pin, INPUT); + pinMode(p, INPUT); } - - #ifdef debug - Serial.print("Called Dino::setMode()\n"); - #endif } // CMD = 01 // Write a digital output pin. 0 for LOW, 1 or >0 for HIGH. -void Dino::dWrite() { - if (val == 0) { - digitalWrite(pin, LOW); +void Dino::dWrite(byte p, byte v, boolean echo) { + #ifdef debug + Serial.print("dwrite, pin:"); + Serial.print(p); + Serial.print(", value:"); + Serial.print(v); + Serial.print(", echo:"); + Serial.println(echo); + #endif + + if (v == 0) { + digitalWrite(p, LOW); } else { - digitalWrite(pin, HIGH); + digitalWrite(p, HIGH); } - - #ifdef debug - Serial.print("Called Dino::dWrite()\n"); - #endif + if (echo) coreResponse(p, v); } // CMD = 02 // Read a digital input pin. 0 for LOW, 1 for HIGH. -void Dino::dRead(int pin) { - rval = digitalRead(pin); - coreResponse(pin, rval); - +byte Dino::dRead(byte p) { #ifdef debug - Serial.print("Called Dino::dRead()\n"); + Serial.print("dread, pin:"); + Serial.println(p); #endif + + byte rval = digitalRead(p); + coreResponse(p, rval); + return rval; } // CMD = 03 // Write an analog output pin. 0 for LOW, up to 255 for HIGH @ 8-bit resolution. -void Dino::aWrite() { - analogWrite(pin,val); - +void Dino::aWrite(byte p, int v, boolean echo) { #ifdef debug - Serial.print("Called Dino::aWrite()\n"); + Serial.print("awrite, pin:"); + Serial.print(p); + Serial.print(", value:"); + Serial.print(v); + Serial.print(", echo:"); + Serial.println(echo); #endif + + analogWrite(p,v); + if (echo) coreResponse(p, v); } // CMD = 04 // Read an analog input pin. 0 for LOW, up to 1023 for HIGH @ 10-bit resolution. -void Dino::aRead(int pin) { - rval = analogRead(pin); - coreResponse(pin, rval); - +int Dino::aRead(byte p) { #ifdef debug - Serial.print("Called Dino::aRead()\n"); + Serial.print("aread, pin:"); + Serial.println(p); #endif -} + int rval = analogRead(p); + coreResponse(p, rval); + return rval; +} -// Send current value of pin and reading value (rval), following the protocol. -// This is for core reads and listeners, but can be used for any function -// following the pin:rval pattern. Set those variables correctly before calling. +// Simple response for core listeners, or any response with the pin:value pattern. void Dino::coreResponse(int p, int v){ stream->print(p); stream->print(':'); @@ -74,39 +90,62 @@ void Dino::coreResponse(int p, int v){ } // CMD = 05 -// Set a listener ON or OFF, or change its type, or divider. -// Takes settings as mask stored in val and applies to existing listener -// if pin was already used, or first inactive. See Dino.h for mask structure. -void Dino::setListener(){ +// Enable, disable and change settings for core (digital/analog) listeners. +// See Dino.h for settings and mask layout. +void Dino::setListener(byte p, boolean enabled, byte analog, byte exponent, boolean local){ + // Pre-format the settings into a mask byte. + byte settingMask = 0; + if (enabled) settingMask = settingMask | B10000000; + if (analog) settingMask = settingMask | B01000000; + if (local) settingMask = settingMask | B00010000; + settingMask = settingMask | dividerMap[exponent]; + + #ifdef debug + Serial.print("setlistener, pin:"); + Serial.print(p); + Serial.print(", enabled:"); + Serial.print(enabled); + Serial.print(", analog:"); + Serial.print(analog); + Serial.print(", exponent:"); + Serial.print(exponent); + Serial.print(", local:"); + Serial.print(local); + Serial.print(", settingMask:"); + Serial.println(settingMask); + #endif + + // If an existing listener was already using this pin, just update settings. boolean found = false; - // Check if previously assigned a listener to this pin and re-use. for(byte i=0; iprint(pin); stream->print(':'); - for (byte i=1; i<=pulseCount; i++){ - stream->print(auxMsg[i+8]); - stream->print((i == pulseCount) ? '\n' : ','); - } - if (pulseCount == 0) stream->print('\n'); + findLastActiveListener(); } - -// CMD = 6 -// Read from the microcontroller's EEPROM. -// -// pin = empty -// val = number of bytes to read -// auxMsg[0-1] = start address -// -void Dino::eepromRead(){ - if (val > 0) { - uint16_t startAddress = (uint16_t)auxMsg[1] << 8 | auxMsg[0]; - - // Stream read bytes as if coming from a pin named 'EE'. - stream->print("EE"); - stream->print(':'); - stream->print(startAddress); - stream->print('-'); - - for (byte i = 0; (i < val); i++) { - stream->print(EEPROM.read(startAddress + i)); - stream->print((i+1 == val) ? '\n' : ','); - } - } -} - -// CMD = 7 -// Write to the microcontroller's EEPROM. -// -// pin = empty -// val = number of bytes to write -// auxMsg[0-1] = start address -// auxMsg[2+] = bytes to write -// -void Dino::eepromWrite(){ - if (val > 0) { - uint16_t startAddress = (uint16_t)auxMsg[1] << 8 | auxMsg[0]; - - for (byte i = 0; (i < val); i++) { - if(EEPROM.read(startAddress + i) != auxMsg[2+i]) { - EEPROM.write(startAddress + i, auxMsg[2+i]); - } +// Track the last active listener whenever changes are made. +// Call this after setting or clearing any listeners. +void Dino::findLastActiveListener(){ + for(byte i=0; i 0) { + uint16_t startAddress = (uint16_t)auxMsg[1] << 8 | auxMsg[0]; + + // Stream read bytes as if coming from a pin named 'EE'. + stream->print("EE"); + stream->print(':'); + stream->print(startAddress); + stream->print('-'); + + for (byte i = 0; (i < val); i++) { + stream->print(EEPROM.read(startAddress + i)); + stream->print((i+1 == val) ? '\n' : ','); + } + } +} + +// CMD = 7 +// Write to the microcontroller's EEPROM. +// +// pin = empty +// val = number of bytes to write +// auxMsg[0-1] = start address +// auxMsg[2+] = bytes to write +// +void Dino::eepromWrite(){ + if (val > 0) { + uint16_t startAddress = (uint16_t)auxMsg[1] << 8 | auxMsg[0]; + + for (byte i = 0; (i < val); i++) { + if(EEPROM.read(startAddress + i) != auxMsg[2+i]) { + EEPROM.write(startAddress + i, auxMsg[2+i]); + } + } + } +} diff --git a/src/lib/DinoPulseInput.cpp b/src/lib/DinoPulseInput.cpp new file mode 100644 index 00000000..79d86c39 --- /dev/null +++ b/src/lib/DinoPulseInput.cpp @@ -0,0 +1,57 @@ +// +// Read chains of input pulses. Only used for DHT sensors right now. +// +#include "Dino.h" + +// CMD = 11 +// +// Rapidly polls a digital input looking for rising or falling edges, +// recording the time in microseconds between consecutive edges. +// There is an optional reset at the beginning if the pin must be held opposite +// to its idle state to trigger a reading. +// +// Max 65535 microseconds reset time. +// Max 255 microseconds per pulse (between 2 consecutive edges). +// Max 255 pulses counted. +// +// val bit 0 : whether to reset the line first or not (0 = no, 1 = yes) +// val bit 1 : direction to pull the line (0 = low, 1 = high) +// auxMsg[0-1] : unsigned 16-bit reset duration +// auxMsg[2-3] : unsigned 16-bit pulse read timeout (in milliseconds) +// auxMsg[4] : unsigned 8-bit pulse count limit +// auxMsg[8+] : reserved as output buffer, will be overwritten +// +void Dino::pulseRead(){ + // Reset + if (bitRead(val, 0)) { + uint16_t resetTime = (auxMsg[1] << 8) | auxMsg[0]; + pinMode(pin, OUTPUT); + digitalWrite(pin, bitRead(val, 1)); + delayMicroseconds(resetTime); + } + pinMode(pin, INPUT); + byte state = digitalRead(pin); + + uint16_t timeout = (auxMsg[3] << 8) | auxMsg[2]; + byte pulseCount = 0; + + uint32_t start = millis(); + uint32_t lastWrite = micros(); + + while ((millis() - start < timeout) && (pulseCount < auxMsg[4])) { + if (digitalRead(pin) != state){ + uint32_t now = micros(); + pulseCount++; + auxMsg[pulseCount+8] = now - lastWrite; + lastWrite = now; + state = state ^ 1; + } + } + + stream->print(pin); stream->print(':'); + for (byte i=1; i<=pulseCount; i++){ + stream->print(auxMsg[i+8]); + stream->print((i == pulseCount) ? '\n' : ','); + } + if (pulseCount == 0) stream->print('\n'); +} diff --git a/test/api/core_test.rb b/test/api/core_test.rb index 3399fd5e..dc24b8ce 100644 --- a/test/api/core_test.rb +++ b/test/api/core_test.rb @@ -83,10 +83,11 @@ def test_analog_read def test_set_listener mock = MiniTest::Mock.new - mock.expect(:call, nil, [Dino::Message.encode(command: 5, pin: 1, value: 0b00000011)]) - mock.expect(:call, nil, [Dino::Message.encode(command: 5, pin: 1, value: 0b01000011)]) - mock.expect(:call, nil, [Dino::Message.encode(command: 5, pin: 1, value: 0b11000011)]) - mock.expect(:call, nil, [Dino::Message.encode(command: 5, pin: 1, value: 0b11000000)]) + + mock.expect(:call, nil, [Dino::Message.encode(command: 5, pin: 1, value: 0, aux_message: "\x00\x03")]) + mock.expect(:call, nil, [Dino::Message.encode(command: 5, pin: 1, value: 0, aux_message: "\x01\x03")]) + mock.expect(:call, nil, [Dino::Message.encode(command: 5, pin: 1, value: 1, aux_message: "\x01\x03")]) + mock.expect(:call, nil, [Dino::Message.encode(command: 5, pin: 1, value: 1, aux_message: "\x01\x00")]) board.stub(:write, mock) do board.set_listener(1, :off) From 429d3a238008d5fa935cd8c9149a2949e6aabc25 Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 10 Dec 2018 12:53:15 -0400 Subject: [PATCH 196/296] Add ESP8266 over the air update. Standardize CLI option format. --- lib/dino_cli/parser.rb | 16 ++++++------- lib/dino_cli/targets.txt | 52 ++++++++++++++++++++++++---------------- src/dino_wifi.ino | 42 +++++++++++++++++++------------- 3 files changed, 65 insertions(+), 45 deletions(-) diff --git a/lib/dino_cli/parser.rb b/lib/dino_cli/parser.rb index a2c578f4..31c5bf05 100644 --- a/lib/dino_cli/parser.rb +++ b/lib/dino_cli/parser.rb @@ -34,21 +34,21 @@ def parse args.shift; set_sketch("ethernet") when 'wifi' args.shift; set_sketch("wifi") - when '-baud' + when '--baud' args.shift; @options[:baud] = args.shift - when '-target' + when '--target' args.shift; set_target(args.shift) - when '-mac' + when '--mac' args.shift; @options[:mac] = args.shift - when '-ip' + when '--ip' args.shift; @options[:ip] = args.shift - when '-ssid' + when '--ssid' args.shift; @options[:ssid] = args.shift - when '-password' + when '--password' args.shift; @options[:password] = args.shift - when '-port' + when '--port' args.shift; @options[:port] = args.shift - when '-debug' + when '--debug' args.shift; @options[:debug] = true when /^-/ error "Invalid argument '#{args[0]}'" diff --git a/lib/dino_cli/targets.txt b/lib/dino_cli/targets.txt index 49087ca7..394d12e1 100644 --- a/lib/dino_cli/targets.txt +++ b/lib/dino_cli/targets.txt @@ -3,8 +3,8 @@ mega (default) - Covers the popular Atmel chips on Arduino boards listed below, and many more. - This is the primary target and all features will be included. Default setting. + Covers popular Atmel chips and associated Arduino boards. This is the + primary target and default setting. All component features are included. Chips: ATmega328p, ATmega32u4, ATmega1280, ATmega2560 Boards: UNO, Nano, Mini, Ethernet @@ -12,22 +12,35 @@ Mega 1280, Mega 2560, Mega ADK Most Lilypads (excluding ones with an ATmega168) + esp8266 + + Based on the ESP8266 chip by Espressif with integrated 2.4Ghz WiFi. + This option includes all component features EXCEPT: + Serial, LCD + + Chips: ES8266, ESP8285 + Boards: WeMos D1, Node MCU, Generic ESP8266 Modules + + Note: Building the (default) serial sketch will only allow connections via + serial. Build the WiFi sketch to connect via the integrated WiFi. + + Note: After the initial upload via serial, the WiFi sketch for this target + supports over-the-air updates from the Arduino IDE using ArduinoOTA. + core - This includes only the core feature set of dino: + This includes only the core component feature set of dino: Set Pin Mode Digital Read/Write/Listen Analog Read/Write/Listen - This is useful if you're getting errors when trying to upload, want to - include specific features by hand, trying to squeeze onto something with - low memory, or testing a new chip. This is the smallest the dino sketch - can get for now, and should be universally compatible. + This is mostly useful for testing, or if severely limited on memory, as it + is the smallest sketch possible and should be universally compatible. mega168 This specifically targets the older ATmega168 chip used in early Arduinos. - They have half the RAM and flash available, so we need to cut the sketch down. + With half the RAM and flash available, we need to cut the sketch down. This option includes core functionality AND: Servo (6 maxmium), DHT, OneWire, IR Out, Tone, SPI, I2C @@ -35,25 +48,22 @@ Boards: Duemilanove, Diecimila, Pro, Pro Mini (Later versions of these may carry an ATmega328) - Note: While you can generate this sketch using any communication type, - it will only fit on the ATmega168 when generated for serial communication. - There is not enough memory available for Wi-Fi or Ethernet. You can - try the core target to tradeoff features for networking instead. - - You can also try to create a serial over Wi-Fi bridge with: - https://github.com/jeelabs/esp-link, if you have an ESP8266 handy. + Note: While you can generate this sketch using any connection type, + only the serial version actually fits on the ATmega168. + There is not enough memory available for Wi-Fi or Ethernet. + Try the core target instead to tradeoff features for networking. Note: Aux message is always limited to 264 bytes or less for the ATmega168. Servo count is always limited to 6 (normally 12) for the ATmega168 chip. - These limits apply regardless of using this build target, as long as - a board with that chip is selected in the IDE. + These limits apply as long as an Atmega168 is selected in the + Arduino IDE, regardless of the options passed to this sketch generator. arm This is the same as mega, but omits libraries specifically known to be incompatible with the Atmel SAMD (ARM Cortex M0) chips. Use this when you need to load a sketch on the Arduino Due or similar boards. - This option includes all features EXCEPT: + This option includes all component features EXCEPT: Serial, Tone, IR Out, I2C Chips: AT91SAM3X8E @@ -61,6 +71,6 @@ NOTE: While different targets include different libraries, you can manually control which libraries are included at compile time by editing DinoDefines.h. - All the library files (even the unused ones) are copied to the destination sketch - folder to enable this. Uncomment a corresponding #define in DinoDefines.h to - include everything needed for that library. Comment it out to exclude the library. + To enable this, libraries unused by the sketch are copied to the sketch output + folder anyway, provided they are not incompatible with the target architecture. + Uncomment or comment #define lines in DinoDefines.h as needed. diff --git a/src/dino_wifi.ino b/src/dino_wifi.ino index 646fab5f..25d79b05 100644 --- a/src/dino_wifi.ino +++ b/src/dino_wifi.ino @@ -1,6 +1,9 @@ #include "Dino.h" #ifdef ESP8266 #include + #include + #include + #include #define LED_PIN 2 #else #include @@ -17,7 +20,6 @@ Dino dino; WiFiServer server(port); WiFiClient client; - // Use the built in LED to indicate WiFi status. void indicate(byte value) { #ifdef ESP8266 @@ -27,7 +29,6 @@ void indicate(byte value) { #endif } - void printWifiStatus() { Serial.println("Connected"); Serial.print("SSID: "); @@ -42,10 +43,13 @@ void printWifiStatus() { indicate(true); } - void connect(){ + Serial.println(); Serial.print("Attempting to connect to SSID: "); - Serial.print(ssid); + Serial.println(ssid); + #ifdef ESP8266 + WiFi.mode(WIFI_STA); + #endif WiFi.begin(ssid, pass); while (WiFi.status() != WL_CONNECTED) { delay(500); @@ -54,7 +58,6 @@ void connect(){ printWifiStatus(); } - void setup() { pinMode(LED_PIN, OUTPUT); @@ -65,30 +68,37 @@ void setup() { connect(); server.begin(); + #ifdef ESP8266 + ArduinoOTA.begin(); + #endif + dino.digitalListenCallback = onDigitalListen; dino.analogListenCallback = onAnalogListen; } void loop() { - // Reconnect if we've lost WiFi.. + // Reconnect if we've lost WiFi. if (WiFi.status() != WL_CONNECTED){ indicate(false); connect(); } - // Listen for connections. - client = server.available(); + // Handle one client at a time. + if (!client){ + client = server.available(); + if (client) dino.stream = &client; + } - // Pass the stream to dino so it can read/write. - dino.stream = &client; + // Run dino. + if (client) dino.run(); - // Handle a connection. - if (client) { - while (client.connected()) dino.run(); - } + // End the connection when client disconnects. + if (client && !client.connected()) client.stop(); - // End the connection. - client.stop(); + // Handle OTA updates. + #ifdef ESP8266 + ArduinoOTA.handle(); + #endif } // This runs every time a digital pin that dino is listening to changes value. From 8ad047f7d46a6f58763d8408f0987a89892adacf Mon Sep 17 00:00:00 2001 From: vickash Date: Thu, 13 Dec 2018 20:54:07 -0400 Subject: [PATCH 197/296] TCP sketches fallback to serial so listeners work when no TCP connection. Enable EEPROM on ESP8266. --- src/dino_ethernet.ino | 43 ++++++++++++++--------- src/dino_wifi.ino | 81 ++++++++++++++++++++++++++++--------------- 2 files changed, 81 insertions(+), 43 deletions(-) diff --git a/src/dino_ethernet.ino b/src/dino_ethernet.ino index c5bff54b..28e37c9c 100644 --- a/src/dino_ethernet.ino +++ b/src/dino_ethernet.ino @@ -2,6 +2,16 @@ #include #include +// Define 'serial' as the serial interface we want to use. +// Defaults to Native USB port on the Due, whatever class "Serial" is on everything else. +// Classes need to inherit from Stream to be compatible with the Dino library. +#if defined(__SAM3X8E__) + #define serial SerialUSB + //#define serial Serial +#else + #define serial Serial +#endif + // Configure your MAC address, IP address, and HTTP port here. byte mac[] = { 0xDE, 0xAD, 0xBE, 0x30, 0x31, 0x32 }; IPAddress ip(192,168,0,77); @@ -11,7 +21,6 @@ Dino dino; EthernetServer server(port); EthernetClient client; - void printEthernetStatus() { Serial.print("IP Address: "); Serial.println(Ethernet.localIP()); @@ -19,10 +28,9 @@ void printEthernetStatus() { Serial.println(port); } - void setup() { - // Start serial for debugging. - Serial.begin(115200); + // Wait for serial ready. + serial.begin(115200); while(!serial); // Explicitly disable the SD card. @@ -32,27 +40,30 @@ void setup() { // Start up the network connection and server. Ethernet.begin(mac, ip); server.begin(); - printEthernetStatus(); + #ifdef debug + printEthernetStatus(); + #endif + // Add listener callbacks for local logic. dino.digitalListenCallback = onDigitalListen; dino.analogListenCallback = onAnalogListen; } - void loop() { - // Listen for connections. - client = server.available(); + // Allow one client at a time to be connected. Set it as the dino IO stream. + if (!client){ + client = server.available(); + if (client) dino.stream = &client; + } - // Pass the stream to dino so it can read/write. - dino.stream = &client; + // Main loop of the dino library. + dino.run(); - // Handle a connection. - if (client) { - while (client.connected()) dino.run(); + // End the connection when client disconnects and revert to serial IO. + if (client && !client.connected()){ + client.stop(); + dino.stream = &serial; } - - // End the connection. - client.stop(); } // This runs every time a digital pin that dino is listening to changes value. diff --git a/src/dino_wifi.ino b/src/dino_wifi.ino index 25d79b05..599c3908 100644 --- a/src/dino_wifi.ino +++ b/src/dino_wifi.ino @@ -4,6 +4,7 @@ #include #include #include + #include #define LED_PIN 2 #else #include @@ -11,6 +12,16 @@ #define LED_PIN 13 #endif +// Define 'serial' as the serial interface we want to use. +// Defaults to Native USB port on the Due, whatever class "Serial" is on everything else. +// Classes need to inherit from Stream to be compatible with the Dino library. +#if defined(__SAM3X8E__) + #define serial SerialUSB + //#define serial Serial +#else + #define serial Serial +#endif + // Configure your WiFi options here. IP address is not configurable. Uses DHCP. int port = 3466; char* ssid = "yourNetwork"; @@ -22,11 +33,12 @@ WiFiClient client; // Use the built in LED to indicate WiFi status. void indicate(byte value) { - #ifdef ESP8266 - digitalWrite(LED_PIN, !value); - #else - digitalWrite(LED_PIN, value); - #endif + pinMode(LED_PIN, OUTPUT); + #ifdef ESP8266 + digitalWrite(LED_PIN, !value); + #else + digitalWrite(LED_PIN, value); + #endif } void printWifiStatus() { @@ -44,56 +56,71 @@ void printWifiStatus() { } void connect(){ - Serial.println(); - Serial.print("Attempting to connect to SSID: "); - Serial.println(ssid); + #ifdef debug + indicate(false); + Serial.println(); + Serial.print("Attempting to connect to SSID: "); + Serial.println(ssid); + #endif + #ifdef ESP8266 WiFi.mode(WIFI_STA); #endif + WiFi.begin(ssid, pass); while (WiFi.status() != WL_CONNECTED) { delay(500); - Serial.print("."); + #ifdef ESP8266 + Serial.print("."); + #endif } - printWifiStatus(); + + #ifdef debug + printWifiStatus(); + #endif } void setup() { - pinMode(LED_PIN, OUTPUT); - - // Start serial for debugging. - Serial.begin(115200); - while(!Serial); - - connect(); - server.begin(); + // Wait for serial ready. + serial.begin(115200); + while(!serial); + // Enable over the air updates and "EEPROM" on the ESP8266. #ifdef ESP8266 + EEPROM.begin(512); ArduinoOTA.begin(); #endif + // Connect to WiFi and start TCP server. + connect(); + server.begin(); + + // Add listener callbacks for local logic. dino.digitalListenCallback = onDigitalListen; dino.analogListenCallback = onAnalogListen; + + // Use serial as the dino IO stream until we get a TCP connection. + dino.stream = &serial; } void loop() { // Reconnect if we've lost WiFi. - if (WiFi.status() != WL_CONNECTED){ - indicate(false); - connect(); - } + if (WiFi.status() != WL_CONNECTED) connect(); - // Handle one client at a time. + // Allow one client at a time to be connected. Set it as the dino IO stream. if (!client){ client = server.available(); if (client) dino.stream = &client; } - // Run dino. - if (client) dino.run(); + // Main loop of the dino library. + dino.run(); - // End the connection when client disconnects. - if (client && !client.connected()) client.stop(); + // End the connection when client disconnects and revert to serial IO. + if (client && !client.connected()){ + client.stop(); + dino.stream = &serial; + } // Handle OTA updates. #ifdef ESP8266 From 835f60f843d2d0145d869ff367a2e1acedc9eb40 Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 16 Dec 2018 15:24:12 -0400 Subject: [PATCH 198/296] Let basic outputs receive state updates from the board --- lib/dino/components/basic/digital_output.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/dino/components/basic/digital_output.rb b/lib/dino/components/basic/digital_output.rb index 3b3be84d..280694c4 100644 --- a/lib/dino/components/basic/digital_output.rb +++ b/lib/dino/components/basic/digital_output.rb @@ -4,14 +4,21 @@ module Basic class DigitalOutput include Setup::SinglePin include Setup::Output + include Mixins::Callbacks include Mixins::Threaded interrupt_with :digital_write def after_initialize(options={}) - low + super(options) + board.digital_read(pin) + end + + def pre_callback_filter(board_state) + board_state.to_i end def digital_write(value) + value = board.high unless (value == board.low) board.digital_write(pin, @state = value) end From 1afce17fcfe89c034f1c2df0420ccffea9065fd4 Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 16 Dec 2018 15:36:04 -0400 Subject: [PATCH 199/296] Fix parser bug that would re-run commands if sent only newline API access for reset without acknowledgement when disconnecting --- lib/dino/tx_rx/base.rb | 2 +- src/lib/Dino.cpp | 12 +++++++----- src/lib/Dino.h | 4 ++-- test/components/basic/digital_output_test.rb | 6 +++--- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index 273bd90f..267feff7 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -37,7 +37,7 @@ def flush_read def start_read @thread ||= Thread.new do trap("INT") do - io.write("\n90\n") + io.write("\n91\n") raise Interrupt end diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 35e15b94..22a69bab 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -51,7 +51,7 @@ void Dino::parse(byte c) { // If EOL, process and reset. else if (c == '\n'){ append('\0'); - process(); + if ((fragmentIndex > 0) || (charIndex > 1)) process(); fragmentIndex = 0; charIndex = 0; } @@ -81,7 +81,8 @@ void Dino::parse(byte c) { } void Dino::append(byte c) { - messageFragments[fragmentIndex][charIndex++] = c; + messageFragments[fragmentIndex][charIndex] = c; + charIndex++; } void Dino::process() { @@ -173,7 +174,8 @@ void Dino::process() { #endif // Implemented in this file. - case 90: reset (); break; + case 90: handshake (); break; + case 91: resetState (); break; case 95: setRegisterDivider (); break; case 96: setAnalogResolution (); break; @@ -211,7 +213,7 @@ void Dino::updateListeners() { // CMD = 90 -void Dino::reset() { +void Dino::handshake() { resetState(); // Reset this so we never send RCV: along with ACK: @@ -230,7 +232,7 @@ void Dino::reset() { stream->print('\n'); } - +// CMD = 91 void Dino::resetState() { clearCoreListeners(); #ifdef DINO_SPI diff --git a/src/lib/Dino.h b/src/lib/Dino.h index 6fcc4016..4039592d 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -130,8 +130,8 @@ class Dino { // // Board level timings, resolutions and reset. // - void reset (); //cmd = 90 - void resetState (); + void handshake (); //cmd = 90 + void resetState (); //cmd = 91 void setRegisterDivider (); //cmd = 97 void setAnalogResolution (); //cmd = 96 unsigned long lastTick; diff --git a/test/components/basic/digital_output_test.rb b/test/components/basic/digital_output_test.rb index 919727aa..64c69527 100644 --- a/test/components/basic/digital_output_test.rb +++ b/test/components/basic/digital_output_test.rb @@ -11,9 +11,9 @@ def part @part ||= Dino::Components::Basic::DigitalOutput.new(board: board, pin: 14) end - def test_low_on_initialize - mock = MiniTest::Mock.new.expect :call, nil, [14, board.low] - board.stub(:digital_write, mock) do + def read_state_on_initialize + mock = MiniTest::Mock.new.expect :call, nil, [14] + board.stub(:digital_read, mock) do part end mock.verify From 1b71fe53906160e606781aaf97bb0698739f209b Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 1 Jan 2019 13:49:48 -0400 Subject: [PATCH 200/296] Let WiFi connect fail without blocking main loop --- src/dino_wifi.ino | 86 +++++++++++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/src/dino_wifi.ino b/src/dino_wifi.ino index 599c3908..8fd53a08 100644 --- a/src/dino_wifi.ino +++ b/src/dino_wifi.ino @@ -4,12 +4,11 @@ #include #include #include - #include - #define LED_PIN 2 + #define WIFI_STATUS_LED 2 #else #include #include - #define LED_PIN 13 + #define WIFI_STATUS_LED 13 #endif // Define 'serial' as the serial interface we want to use. @@ -26,58 +25,62 @@ int port = 3466; char* ssid = "yourNetwork"; char* pass = "yourPassword"; +boolean connected = false; +long lastConnectAttempt; +int WiFiConnectTimeout = 10000; Dino dino; WiFiServer server(port); WiFiClient client; // Use the built in LED to indicate WiFi status. -void indicate(byte value) { - pinMode(LED_PIN, OUTPUT); +void indicateWiFi(byte value) { + pinMode(WIFI_STATUS_LED, OUTPUT); #ifdef ESP8266 - digitalWrite(LED_PIN, !value); + digitalWrite(WIFI_STATUS_LED, !value); #else - digitalWrite(LED_PIN, value); + digitalWrite(WIFI_STATUS_LED, value); #endif } void printWifiStatus() { - Serial.println("Connected"); - Serial.print("SSID: "); - Serial.println(WiFi.SSID()); - Serial.print("Signal Strength (RSSI):"); - Serial.print(WiFi.RSSI()); - Serial.println(" dBm"); - Serial.print("IP Address: "); - Serial.println(WiFi.localIP()); - Serial.print("Dino TCP Port: "); - Serial.println(port); - indicate(true); + serial.println("WiFi Connected"); + serial.print("SSID: "); + serial.println(WiFi.SSID()); + serial.print("Signal Strength (RSSI):"); + serial.print(WiFi.RSSI()); + serial.println(" dBm"); + serial.print("IP Address: "); + serial.println(WiFi.localIP()); + serial.print("Dino TCP Port: "); + serial.println(port); + indicateWiFi(true); } void connect(){ - #ifdef debug - indicate(false); - Serial.println(); - Serial.print("Attempting to connect to SSID: "); - Serial.println(ssid); - #endif - #ifdef ESP8266 WiFi.mode(WIFI_STA); #endif - - WiFi.begin(ssid, pass); - while (WiFi.status() != WL_CONNECTED) { - delay(500); - #ifdef ESP8266 - Serial.print("."); - #endif + if (millis() - lastConnectAttempt > WiFiConnectTimeout){ + WiFi.begin(ssid, pass); + lastConnectAttempt = millis(); } +} - #ifdef debug +void maintainWiFi(){ + if (connected == true){ + if (WiFi.status() == WL_CONNECTED) return; + connected = false; + connect(); + } + if (connected == false){ + if (WiFi.status() != WL_CONNECTED) { + connect(); + return; + } + connected = true; printWifiStatus(); - #endif + } } void setup() { @@ -90,10 +93,19 @@ void setup() { EEPROM.begin(512); ArduinoOTA.begin(); #endif + // Start the dino TCP server. + server.begin(); + + delay(2000); - // Connect to WiFi and start TCP server. + // Attempt initial WiFi connection. + #ifdef debug + indicateWiFi(false); + serial.println(); + serial.print("Attempting to connect to SSID: "); + serial.println(ssid); + #endif connect(); - server.begin(); // Add listener callbacks for local logic. dino.digitalListenCallback = onDigitalListen; @@ -105,7 +117,7 @@ void setup() { void loop() { // Reconnect if we've lost WiFi. - if (WiFi.status() != WL_CONNECTED) connect(); + maintainWiFi(); // Allow one client at a time to be connected. Set it as the dino IO stream. if (!client){ From a696e015199598fea7149d03133d2cf1b73b025d Mon Sep 17 00:00:00 2001 From: vickash Date: Fri, 3 Feb 2023 23:45:56 -0400 Subject: [PATCH 201/296] Fix a race condition in receiving handshake acknowledgement --- lib/dino/tx_rx/handshake.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dino/tx_rx/handshake.rb b/lib/dino/tx_rx/handshake.rb index 9b2361a4..e0a77764 100644 --- a/lib/dino/tx_rx/handshake.rb +++ b/lib/dino/tx_rx/handshake.rb @@ -8,9 +8,9 @@ def initialize end def update(sender, result) - @acknowledged = true sender.delete_observer(self) @result = result + @acknowledged = true end end From e3a4282bb1b565498838125e3a02981328e70dac Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 4 Feb 2023 01:52:49 -0400 Subject: [PATCH 202/296] Pass tests --- lib/dino/api/core.rb | 2 +- lib/dino/components/one_wire/helper.rb | 4 +--- lib/dino/components/servo.rb | 2 +- test/api/core_test.rb | 11 ++++++----- test/components/register/shift_in_test.rb | 23 ++++++++++++++++++++--- test/components/servo_test.rb | 6 +++--- 6 files changed, 32 insertions(+), 16 deletions(-) diff --git a/lib/dino/api/core.rb b/lib/dino/api/core.rb index c6116f0c..3980008a 100644 --- a/lib/dino/api/core.rb +++ b/lib/dino/api/core.rb @@ -41,7 +41,7 @@ def set_pullup(pin, pullup) # CMD = 5 def set_listener(pin, state=:off, options={}) mode = options[:mode] || :digital - divider = options[:divider] || 8 + divider = options[:divider] || 16 unless [:digital, :analog].include? mode raise "Mode must be either digital or analog" diff --git a/lib/dino/components/one_wire/helper.rb b/lib/dino/components/one_wire/helper.rb index 1bfff474..b4fcbd29 100644 --- a/lib/dino/components/one_wire/helper.rb +++ b/lib/dino/components/one_wire/helper.rb @@ -2,14 +2,12 @@ module Dino module Components module OneWire class Helper - ADDRESS_TO_BYTES_CLASSES = [Integer, Bignum, Fixnum].freeze - def self.address_to_bytes(address) [address].pack('Q<').split("").map(&:ord) end def self.crc_check(data) - if ADDRESS_TO_BYTES_CLASSES.include?(data.class) + if data.class == Integer bytes = address_to_bytes(data) else bytes = data diff --git a/lib/dino/components/servo.rb b/lib/dino/components/servo.rb index 4768347a..cdc96eb5 100644 --- a/lib/dino/components/servo.rb +++ b/lib/dino/components/servo.rb @@ -16,7 +16,7 @@ def attach end def detach - board.servo_toggle(pin, :off, min: @min, max: @max) + board.servo_toggle(pin, :off) end def position=(value) diff --git a/test/api/core_test.rb b/test/api/core_test.rb index dc24b8ce..b7213b2a 100644 --- a/test/api/core_test.rb +++ b/test/api/core_test.rb @@ -84,9 +84,10 @@ def test_analog_read def test_set_listener mock = MiniTest::Mock.new - mock.expect(:call, nil, [Dino::Message.encode(command: 5, pin: 1, value: 0, aux_message: "\x00\x03")]) - mock.expect(:call, nil, [Dino::Message.encode(command: 5, pin: 1, value: 0, aux_message: "\x01\x03")]) - mock.expect(:call, nil, [Dino::Message.encode(command: 5, pin: 1, value: 1, aux_message: "\x01\x03")]) + # \x00\x04 corresponds to the default divider of 16 (2^4) + mock.expect(:call, nil, [Dino::Message.encode(command: 5, pin: 1, value: 0, aux_message: "\x00\x04")]) + mock.expect(:call, nil, [Dino::Message.encode(command: 5, pin: 1, value: 0, aux_message: "\x01\x04")]) + mock.expect(:call, nil, [Dino::Message.encode(command: 5, pin: 1, value: 1, aux_message: "\x01\x04")]) mock.expect(:call, nil, [Dino::Message.encode(command: 5, pin: 1, value: 1, aux_message: "\x01\x00")]) board.stub(:write, mock) do @@ -100,7 +101,7 @@ def test_set_listener def test_digital_listen mock = MiniTest::Mock.new - mock.expect(:call, nil, [1, :on, {mode: :digital, divider: 4}]) + mock.expect(:call, nil, [1, :on], mode: :digital, divider: 4) board.stub(:set_listener, mock) do board.digital_listen(1) @@ -109,7 +110,7 @@ def test_digital_listen def test_analog_listen mock = MiniTest::Mock.new - mock.expect(:call, nil, [1, :on, {mode: :analog, divider: 16}]) + mock.expect(:call, nil, [1, :on], mode: :analog, divider: 16) board.stub(:set_listener, mock) do board.analog_listen(1) diff --git a/test/components/register/shift_in_test.rb b/test/components/register/shift_in_test.rb index 551cc0ce..1f60b16f 100644 --- a/test/components/register/shift_in_test.rb +++ b/test/components/register/shift_in_test.rb @@ -33,15 +33,32 @@ def test_rising_clock end def test_read - # mock = MiniTest::Mock.new.expect :call, nil, ["22.8.1.#{[11,12,0].pack('C<*')}\n"] - mock = MiniTest::Mock.new.expect :call, nil, [8, 11, 12, 2, {preclock_high: true}] + mock = MiniTest::Mock.new.expect :call, nil, [8, 11, 12, 2], preclock_high: true board.stub(:shift_read, mock) do new_part = Dino::Components::Register::ShiftIn.new(options.merge(bytes: 2, rising_clock: true)) new_part.read end mock.verify end - + + def test_listen + mock = MiniTest::Mock.new.expect :call, nil, [8, 11, 12, 2], preclock_high: true + board.stub(:shift_listen, mock) do + new_part = Dino::Components::Register::ShiftIn.new(options.merge(bytes: 2, rising_clock: true)) + new_part.listen + end + mock.verify + end + + def test_stop + mock = MiniTest::Mock.new.expect :call, nil, [8] + board.stub(:shift_stop, mock) do + new_part = Dino::Components::Register::ShiftIn.new(options.merge(bytes: 2, rising_clock: true)) + new_part.stop + end + mock.verify + end + def test_callback_bubble mock = MiniTest::Mock.new.expect :call, nil, ["127,255"] part.stub(:update, mock) do diff --git a/test/components/servo_test.rb b/test/components/servo_test.rb index 3cf78407..24158b68 100644 --- a/test/components/servo_test.rb +++ b/test/components/servo_test.rb @@ -12,7 +12,7 @@ def part end def test_toggle_on_initialize - mock = MiniTest::Mock.new.expect(:call, nil, [1, :on, Hash]) + mock = MiniTest::Mock.new.expect(:call, nil, [1, :on], min: 544, max: 2400) board.stub(:servo_toggle, mock) do Dino::Components::Servo.new(board: board, pin:1) end @@ -20,14 +20,14 @@ def test_toggle_on_initialize def test_attach part - mock = MiniTest::Mock.new.expect(:call, nil, [1, :on, Hash]) + mock = MiniTest::Mock.new.expect(:call, nil, [1, :on], min: 0, max: 360) board.stub(:servo_toggle, mock) { part.attach } mock.verify end def test_detach part - mock = MiniTest::Mock.new.expect(:call, nil, [1, :off, Hash]) + mock = MiniTest::Mock.new.expect(:call, nil, [1, :off]) board.stub(:servo_toggle, mock) { part.detach } mock.verify end From 207584890f3db09d3a44d1d8928e217eaca054b4 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 4 Feb 2023 22:57:34 -0400 Subject: [PATCH 203/296] Add EEPROM convenience methods and an example --- examples/eeprom/eeprom.rb | 34 +++++++++++++++++++++++ lib/dino/components/basic/board_eeprom.rb | 24 +++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 examples/eeprom/eeprom.rb diff --git a/examples/eeprom/eeprom.rb b/examples/eeprom/eeprom.rb new file mode 100644 index 00000000..959d11bc --- /dev/null +++ b/examples/eeprom/eeprom.rb @@ -0,0 +1,34 @@ +# +# This is a simple example to blink an led +# every half a second +# +require 'bundler/setup' +require 'dino' + +board = Dino::Board.new(Dino::TxRx::Serial.new) + +# Initialization automatically gets all EEPROM data from thee board. +eeprom = Dino::Components::Basic::BoardEEPROM.new(board: board) + +# EEPROM size reported by the board. +puts "EEPROM Size: #{eeprom.length} bytes" + +# Write values like an array. +eeprom[0] = 128 +eeprom[1] = 127 + +# Changes do not save to the board automatically. +# Call #save to write to the board, and automatically reload from it. +eeprom.save + +# Read values like an array. +puts "Address 0 contains: #{eeprom[0]}" + +# Enumerate like an array. +eeprom.each_with_index do |byte, address| + if address == 1 + puts "Address #{address} contains #{byte}" + end +end + + diff --git a/lib/dino/components/basic/board_eeprom.rb b/lib/dino/components/basic/board_eeprom.rb index 1842e8cc..c6c3dbc4 100644 --- a/lib/dino/components/basic/board_eeprom.rb +++ b/lib/dino/components/basic/board_eeprom.rb @@ -27,7 +27,29 @@ def save end load end - + + # + # Specific Array-like methods for convenience. + # + def length; board.eeprom_length; end + alias :count :length + + def [](index) + @state_mutex.synchronize { @state.send :[], index } + end + + def []=(index, value) + @state_mutex.synchronize { @state.send :[]=, index, value } + end + + def each(&block) + @state_mutex.synchronize { @state.send :each, &block } + end + + def each_with_index(&block) + @state_mutex.synchronize { @state.send :each_with_index, &block } + end + def pre_callback_filter(message) address = message.split("-", 2)[0].to_i bytes = message.split("-", 2)[1].split(",").map(&:to_i) From 8d682f3ff5ae45fcda3038a18810dfa431bc695a Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 4 Feb 2023 23:39:28 -0400 Subject: [PATCH 204/296] Clean up the I2C interface --- examples/i2c/ds3231.rb | 30 ++++++++++++++------- lib/dino/api/i2c.rb | 26 +++++++++++------- lib/dino/components/i2c/bus.rb | 10 ++++--- lib/dino/components/i2c/ds3231.rb | 25 ++++++++++------- lib/dino/components/i2c/slave.rb | 11 +++++--- src/lib/Dino.cpp | 1 - src/lib/DinoI2C.cpp | 45 +++++++++++++++++++------------ 7 files changed, 94 insertions(+), 54 deletions(-) diff --git a/examples/i2c/ds3231.rb b/examples/i2c/ds3231.rb index f19f4481..676486a1 100644 --- a/examples/i2c/ds3231.rb +++ b/examples/i2c/ds3231.rb @@ -1,14 +1,14 @@ # -# Somewhat pointless example to ensure I2C is working. Sets the time on a -# connected DS3231 real-time-clock and reads it back every 5 seconds. +# Small example to show how I2C works. Sets time on a DS3231 +# real-time-clock, and reads it back every 5 seconds. # require 'bundler/setup' require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) -# Only pass in the SDA pin of the I2C bus. SCL (clock) pin MUST be properly -# connected for things to work, but we don't need to control it. +# Only pass the SDA pin of the I2C bus. SCL (clock) pin must be +# connected for it to work, but we don't need to control it. # # Most Arduinos: SDA = 'A4' SCL = 'A5' # Leonardo: SDA = 2 SCL = 3 @@ -19,11 +19,23 @@ # This is for convenience when working with common development boards. # bus = Dino::Components::I2C::Bus.new(board: board, pin: 4) -rtc = Dino::Components::I2C::DS3231.new(bus: bus, address: 104) -rtc.time = Time.now +# The bus auto searches for devices on intiailization. +puts "No I2C devices connected!" if bus.found_devices.empty? +bus.found_devices.each do |address| + puts "I2C device connected with address: 0x#{address.to_s(16)}" +end + +# 0x68 or 140 is the I2C address for most real time clocks. +unless (bus.found_devices.include? 0x68) + puts "No real time clock found!" unless bus.found_devices.empty? +else + puts; puts "Using real time clock at address 0x68"; puts + rtc = Dino::Components::I2C::DS3231.new(bus: bus, address: 0x68) + rtc.time = Time.now -loop do - puts rtc.time - sleep 5 + 5.times do + puts rtc.time + sleep 5 + end end diff --git a/lib/dino/api/i2c.rb b/lib/dino/api/i2c.rb index 17d833ea..783e14e0 100644 --- a/lib/dino/api/i2c.rb +++ b/lib/dino/api/i2c.rb @@ -10,23 +10,29 @@ def i2c_search # CMD = 34 def i2c_write(address, bytes=[], options={}) - aux = pack :uint8, [address, bytes.length, bytes].flatten + + # Bit 1 of settings controls repeated start. + settings = 0b00 + settings |= 0b01 if options[:repeated_start] + + aux = pack :uint8, [address, 0, bytes.length, bytes].flatten write Message.encode command: 34, - value: options[:repeated_start] ? 1 : 0, + value: settings, aux_message: aux end # CMD = 35 def i2c_read(address, register, num_bytes, options={}) - settings = options[:repeated_start] ? 1 : 0 - - # Won't write anything if no start register was given. - unless register - settings = settings | 0b10 - register = 0 - end + raise ArgumentError, 'maximum read length from I2C devices is 32 bytes' if num_bytes > 32 + + # Bit 1 of settings controls repeated start. + settings = 0b00 + settings |= 0b01 if options[:repeated_start] + + # Bit 2 of settings controls whether to write a start register before reading. + settings |= 0b10 if register - aux = pack :uint8, [address, register, num_bytes] + aux = pack :uint8, [address, 0, register, num_bytes] write Message.encode command: 35, value: settings, aux_message: aux diff --git a/lib/dino/components/i2c/bus.rb b/lib/dino/components/i2c/bus.rb index bdc6904b..7c5330b2 100644 --- a/lib/dino/components/i2c/bus.rb +++ b/lib/dino/components/i2c/bus.rb @@ -10,12 +10,16 @@ class Bus def after_initialize(options={}) super(options) + @found_devices = [] + search bubble_callbacks end def search addresses = read_using -> { board.i2c_search } - @found_devices = addresses.split(":").map(&:to_i) + if addresses + @found_devices = addresses.split(":").map(&:to_i) + end end def write(address, bytes=[], options={}) @@ -30,13 +34,13 @@ def read(*args) read_using -> { _read(*args) } end - def _read(address, register, num_bytes=1, options={}) + def _read(address, register=nil, num_bytes=1, options={}) board.i2c_read(address, register, num_bytes, options) end def bubble_callbacks add_callback(:bus_master) do |str| - if str.match(/d*-/) + if str && str.match(/d*-/) address, data = str.split("-", 2) address = address.to_i data = data.split(",").map(&:to_i) diff --git a/lib/dino/components/i2c/ds3231.rb b/lib/dino/components/i2c/ds3231.rb index 4185cee4..ad6b7ed8 100644 --- a/lib/dino/components/i2c/ds3231.rb +++ b/lib/dino/components/i2c/ds3231.rb @@ -3,18 +3,12 @@ module Components module I2C class DS3231 < Slave require 'bcd' - - def pre_callback_filter(bytes) - t = bytes.map { |b| BCD.encode(b) } - Time.new t[6] + 1970, t[5], t[4], t[2], t[1], t[0] - end - + def time - # Return of read_bytes is still unfiltered string from the bus... - read_using -> { read_bytes(nil, 7) } - @state + read_using -> { _read } + state end - + def time=(time) bytes = [ 0, BCD.decode(time.sec), @@ -27,6 +21,17 @@ def time=(time) write(bytes) time end + + # Time data starts at register 0 and is 7 bytes long. + def _read + read_bytes(0, 7) + end + + # Convert raw bytes from the I2C bus into a Ruby Time object. + def pre_callback_filter(bytes) + t = bytes.map { |b| BCD.encode(b) } + Time.new t[6] + 1970, t[5], t[4], t[2], t[1], t[0] + end end end end diff --git a/lib/dino/components/i2c/slave.rb b/lib/dino/components/i2c/slave.rb index 14cf1e14..e0b50419 100644 --- a/lib/dino/components/i2c/slave.rb +++ b/lib/dino/components/i2c/slave.rb @@ -3,12 +3,15 @@ module Components module I2C class Slave include Mixins::BusSlave - include Mixins::Poller + include Mixins::Reader - def repeated_start - false + def initialize(options) + super(options) + @repeated_start = options[:repeated_start] || false end - + + attr_accessor :repeated_start + def write(bytes=[]) bus.write(address, bytes, repeated_start: repeated_start) end diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 22a69bab..72db040c 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -162,7 +162,6 @@ void Dino::process() { case 33: i2cSearch (); break; case 34: i2cWrite (); break; case 35: i2cRead (); break; - case 36: i2cTransfer (); break; #endif // Implemented in DinoOneWire.cpp diff --git a/src/lib/DinoI2C.cpp b/src/lib/DinoI2C.cpp index 316f1fc8..1492221d 100644 --- a/src/lib/DinoI2C.cpp +++ b/src/lib/DinoI2C.cpp @@ -23,8 +23,9 @@ void Dino::i2cSearch() { stream->print(SDA); i2cBegin(); - for (byte i = 0x08; i < 0x78; i++) { - Wire.requestFrom(i, 1); + for (uint8_t i = 0x08; i < 0x78; i++) { + uint8_t length = 1; + Wire.requestFrom(i, length); if (Wire.available()){ stream->print(':'); stream->print(i); while(Wire.available()) Wire.read(); @@ -37,43 +38,53 @@ void Dino::i2cSearch() { // CMD = 34 // Write to an I2C device. All params as binary in auxMsg. // -// val = repeated starts? -// auxMsg[0] = device address -// auxMsg[1] = number of bytes -// auxMsg[2]+ = data +// val = Settings +// val bit 0 = repeated start +// val bit 1 = write register address before reading +// val bit 2+ = unused // -// Max 256 bytes. Validate remotely. +// auxMsg[0] = 7-bit device address +// auxMsg[1] = reserved +// auxMsg[2] = data length +// auxMsg[3]+ = data +// +// Max limited by aux message size. // void Dino::i2cWrite() { + byte repeatedStart = bitRead(val, 0); i2cBegin(); Wire.beginTransmission(auxMsg[0]); Wire.write(&auxMsg[2], auxMsg[1]); - Wire.endTransmission(val); + Wire.endTransmission(repeatedStart); } // CMD = 35 // Read from an I2C device. All params as binary in auxMsg. // -// val = repeated starts? -// auxMsg[0] = device address -// auxMsg[1] = start register address -// auxMsg[2] = number of bytes +// val = Settings +// val bit 0 = repeated start +// val bit 1 = write register address before reading +// val bit 2+ = unused +// +// auxMsg[0] = 7-bit device address +// auxMsg[1] = reserved +// auxMsg[2] = register address +// auxMsg[3] = number of bytes // // Max 32 bytes, limited by Wire library buffer. Validate remotely. // void Dino::i2cRead() { - if (auxMsg[2] > 32) auxMsg[2] = 32; + if (auxMsg[3] > 32) auxMsg[3] = 32; byte repeatedStart = bitRead(val, 0); i2cBegin(); - // Bit 1 of val is flag for whether to write register address first or not. + // Optionally write a register address before reading. if (bitRead(val, 1)) { Wire.beginTransmission(auxMsg[0]); Wire.write(auxMsg[1]); Wire.endTransmission(repeatedStart); } - - Wire.requestFrom(auxMsg[0], auxMsg[2], repeatedStart); + Wire.requestFrom(auxMsg[0], auxMsg[3], repeatedStart); // Send data as if coming from SDA pin. Prefix with device adddress. // Fail silently if no bytes read / invalid device address. @@ -85,7 +96,7 @@ void Dino::i2cRead() { while(Wire.available()){ currentByte++; stream->print(Wire.read()); - stream->print((currentByte == auxMsg[2]) ? '\n' : ','); + stream->print((currentByte == auxMsg[3]) ? '\n' : ','); } } #endif From c51d4be4279e5cca69474d20bf0ed11e8dfbde1e Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 5 Feb 2023 00:46:49 -0400 Subject: [PATCH 205/296] Readers and pollers handle arguments better --- examples/one_wire/ds18b20.rb | 12 +++++----- lib/dino/components/i2c/bus.rb | 8 ------- lib/dino/components/i2c/ds3231.rb | 9 ++++---- lib/dino/components/mixins/poller.rb | 10 ++++----- lib/dino/components/mixins/reader.rb | 33 +++++++++++++++++++--------- lib/dino/components/one_wire/bus.rb | 4 ++-- 6 files changed, 41 insertions(+), 35 deletions(-) diff --git a/examples/one_wire/ds18b20.rb b/examples/one_wire/ds18b20.rb index be655c4c..afe24c07 100644 --- a/examples/one_wire/ds18b20.rb +++ b/examples/one_wire/ds18b20.rb @@ -37,7 +37,7 @@ end # Format a reading for printing on a line. -def print_reading(reading, sensor, i) +def print_reading(reading, sensor) print "#{Time.now.strftime '%Y-%m-%d %H:%M:%S'} - " print "Serial(HEX): #{sensor.serial_number} | Res: #{sensor.resolution} bits | " @@ -49,8 +49,10 @@ def print_reading(reading, sensor, i) end end -# Read the temp from each sensor in a simple loop. -loop do - ds18b20s.each_with_index { |s,i| print_reading(s.read, s, i) } - sleep 5 +ds18b20s.each do |sensor| + sensor.poll(5) do |reading| + print_reading(reading, sensor) + end end + +sleep diff --git a/lib/dino/components/i2c/bus.rb b/lib/dino/components/i2c/bus.rb index 7c5330b2..e687896a 100644 --- a/lib/dino/components/i2c/bus.rb +++ b/lib/dino/components/i2c/bus.rb @@ -26,14 +26,6 @@ def write(address, bytes=[], options={}) board.i2c_write(address, [bytes].flatten, options) end - def transfer(address, options={}) - board.i2c_transfer(address, options) - end - - def read(*args) - read_using -> { _read(*args) } - end - def _read(address, register=nil, num_bytes=1, options={}) board.i2c_read(address, register, num_bytes, options) end diff --git a/lib/dino/components/i2c/ds3231.rb b/lib/dino/components/i2c/ds3231.rb index ad6b7ed8..ef2a36e3 100644 --- a/lib/dino/components/i2c/ds3231.rb +++ b/lib/dino/components/i2c/ds3231.rb @@ -4,11 +4,7 @@ module I2C class DS3231 < Slave require 'bcd' - def time - read_using -> { _read } - state - end - + # Set the time. def time=(time) bytes = [ 0, BCD.decode(time.sec), @@ -22,6 +18,9 @@ def time=(time) time end + # Read the time. + alias :time :read + # Time data starts at register 0 and is 7 bytes long. def _read read_bytes(0, 7) diff --git a/lib/dino/components/mixins/poller.rb b/lib/dino/components/mixins/poller.rb index 8f9fb4b9..aa8e3c04 100644 --- a/lib/dino/components/mixins/poller.rb +++ b/lib/dino/components/mixins/poller.rb @@ -4,19 +4,19 @@ module Mixins module Poller include Reader include Threaded - - def poll_using(method, interval=1, &block) + + def poll_using(method, interval=3, *args, &block) stop add_callback(:poll, &block) if block_given? threaded_loop do - method.call + method.call(*args) sleep interval end end - def poll(interval=1, &block) - poll_using(method(:_read), interval, &block) + def poll(interval=3, *args, &block) + poll_using(self.method(:_read), interval, *args, &block) end def stop diff --git a/lib/dino/components/mixins/reader.rb b/lib/dino/components/mixins/reader.rb index d387d17d..43643f9b 100644 --- a/lib/dino/components/mixins/reader.rb +++ b/lib/dino/components/mixins/reader.rb @@ -4,23 +4,36 @@ module Mixins module Reader include Callbacks - def read_using(method, &block) + # + # Defalt behavior for #read is to delegate to #_read. + # Define #_read in including classes. + # + def read(*args, &block) + read_using(self.method(:_read), *args, &block) + end + + # + # Delegate reading to another method that sends a command to the board. + # Accepts blocks as one-time callbacks stored in the :read key. + # Blocks until a value is recieved from the board. + # Returns the value after #pre_callback_filter runs on it. + # + # Give procs as methods to build more complex functionality for buses. + # + def read_using(method, *args, &block) add_callback(:read, &block) if block_given? - # Block and catches read value for return, AFTER the pre-filter. value = nil - add_callback(:read) { |data| value = data } - - method.call + add_callback(:read) do |filtered_data| + value = filtered_data + end + + method.call(*args) block_until_read value end - - def read(&block) - read_using(self.method(:_read), &block) - end - + def block_until_read loop do break if !@callbacks[:read] diff --git a/lib/dino/components/one_wire/bus.rb b/lib/dino/components/one_wire/bus.rb index 35862815..881e37a5 100644 --- a/lib/dino/components/one_wire/bus.rb +++ b/lib/dino/components/one_wire/bus.rb @@ -46,8 +46,8 @@ def reset(get_presence=0) board.one_wire_reset(pin, get_presence) end - def read(num_bytes) - read_using -> { board.one_wire_read(pin, num_bytes) } + def _read(num_bytes) + board.one_wire_read(pin, num_bytes) end def write(*bytes) From 728568de9894efd9dd86e3566774e6a944cdf4a0 Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 5 Feb 2023 21:00:10 -0400 Subject: [PATCH 206/296] Fix I2C --- lib/dino/api/i2c.rb | 2 +- src/lib/DinoI2C.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/dino/api/i2c.rb b/lib/dino/api/i2c.rb index 783e14e0..fe7a4b41 100644 --- a/lib/dino/api/i2c.rb +++ b/lib/dino/api/i2c.rb @@ -10,7 +10,6 @@ def i2c_search # CMD = 34 def i2c_write(address, bytes=[], options={}) - # Bit 1 of settings controls repeated start. settings = 0b00 settings |= 0b01 if options[:repeated_start] @@ -31,6 +30,7 @@ def i2c_read(address, register, num_bytes, options={}) # Bit 2 of settings controls whether to write a start register before reading. settings |= 0b10 if register + register = 0 unless register aux = pack :uint8, [address, 0, register, num_bytes] write Message.encode command: 35, diff --git a/src/lib/DinoI2C.cpp b/src/lib/DinoI2C.cpp index 1492221d..32778b3c 100644 --- a/src/lib/DinoI2C.cpp +++ b/src/lib/DinoI2C.cpp @@ -54,7 +54,7 @@ void Dino::i2cWrite() { byte repeatedStart = bitRead(val, 0); i2cBegin(); Wire.beginTransmission(auxMsg[0]); - Wire.write(&auxMsg[2], auxMsg[1]); + Wire.write(&auxMsg[3], auxMsg[2]); Wire.endTransmission(repeatedStart); } @@ -81,9 +81,10 @@ void Dino::i2cRead() { // Optionally write a register address before reading. if (bitRead(val, 1)) { Wire.beginTransmission(auxMsg[0]); - Wire.write(auxMsg[1]); + Wire.write(auxMsg[2]); Wire.endTransmission(repeatedStart); } + Wire.beginTransmission(auxMsg[0]); Wire.requestFrom(auxMsg[0], auxMsg[3], repeatedStart); // Send data as if coming from SDA pin. Prefix with device adddress. @@ -98,5 +99,6 @@ void Dino::i2cRead() { stream->print(Wire.read()); stream->print((currentByte == auxMsg[3]) ? '\n' : ','); } + Wire.endTransmission(repeatedStart); } #endif From 88ef099f7f087bca928fad09948b5af663e94061 Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 5 Feb 2023 23:50:55 -0400 Subject: [PATCH 207/296] Add tests and remove unused methods for DHT sensors --- examples/dht/dht.rb | 4 +-- lib/dino/api.rb | 1 - lib/dino/api/dht.rb | 12 ------- test/components/dht_test.rb | 66 +++++++++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 15 deletions(-) delete mode 100644 lib/dino/api/dht.rb create mode 100644 test/components/dht_test.rb diff --git a/examples/dht/dht.rb b/examples/dht/dht.rb index 113e71c9..63d127b7 100644 --- a/examples/dht/dht.rb +++ b/examples/dht/dht.rb @@ -4,8 +4,8 @@ require 'bundler/setup' require 'dino' -board = Dino::Board.new(Dino::TxRx::Serial.new(device: "/dev/cu.usbmodem1431")) -dht = Dino::Components::DHT.new(pin: 7, board: board) +board = Dino::Board.new(Dino::TxRx::Serial.new) +dht = Dino::Components::DHT.new(pin: 4, board: board) # The DHT class pre-processes raw data from the board. When it reaches callbacks # it's already hash of :temperature and :humidity keys, both with Float values. diff --git a/lib/dino/api.rb b/lib/dino/api.rb index 94d61e96..bdb0143f 100644 --- a/lib/dino/api.rb +++ b/lib/dino/api.rb @@ -3,7 +3,6 @@ module API require 'dino/api/helper' require 'dino/api/core' require 'dino/api/eeprom' - require 'dino/api/dht' require 'dino/api/i2c' require 'dino/api/infrared' require 'dino/api/one_wire' diff --git a/lib/dino/api/dht.rb b/lib/dino/api/dht.rb deleted file mode 100644 index 413db961..00000000 --- a/lib/dino/api/dht.rb +++ /dev/null @@ -1,12 +0,0 @@ -module Dino - module API - module DHT - include Helper - - # CMD = 13 - def dht_read(pin) - write Message.encode command: 13, pin: convert_pin(pin) - end - end - end -end diff --git a/test/components/dht_test.rb b/test/components/dht_test.rb new file mode 100644 index 00000000..21c1182e --- /dev/null +++ b/test/components/dht_test.rb @@ -0,0 +1,66 @@ +require 'dino' +require 'board_mock' +require 'minitest/autorun' + +class DHTTest < MiniTest::Test + PIN = 1 + + # Actual raw data from a sensor + GOOD_STRING = "14,82,77,57,26,52,26,57,26,51,32,51,26,57,26,52,72,57,26,67,72,57,26,51,27,56,72,52,77,52,77,51,26,57,72,67,26,52,26,57,26,52,26,57,26,51,31,52,26,57,72,67,26,52,26,56,72,57,26,52,26,57,72,52,77,51,72,67,72,57,72,51,32,51,26,57,26,52,72,57,72,56,72,47" + GOOD_ARRAY = [14, 82, 77, 57, 26, 52, 26, 57, 26, 51, 32, 51, 26, 57, 26, 52, 72, 57, 26, 67, 72, 57, 26, 51, 27, 56, 72, 52, 77, 52, 77, 51, 26, 57, 72, 67, 26, 52, 26, 57, 26, 52, 26, 57, 26, 51, 31, 52, 26, 57, 72, 67, 26, 52, 26, 56, 72, 57, 26, 52, 26, 57, 72, 52, 77, 51, 72, 67, 72, 57, 72, 51, 32, 51, 26, 57, 26, 52, 72, 57, 72, 56, 72, 47] + SHORT_ARRAY = [1,2,3,4,5,6] + + # Make test data with a bad CRC byte (all 1s = 255). + def bad_crc + bad_crc = GOOD_ARRAY.dup + (67..82).each { |i| bad_crc[i] = 56 } + bad_crc + end + + def board + @board ||= BoardMock.new + end + + def part + @part ||= Dino::Components::DHT.new(board: board, pin:PIN) + end + + # It should tell the board to do a #pulse_read + def test__read + part + mock = MiniTest::Mock.new.expect(:call, nil, [PIN], reset: board.low, reset_time: 1000, pulse_limit: 84) + board.stub(:pulse_read, mock) do + part._read + end + mock.verify + end + + # Callback pre filter should convert string of bytes to array and call #decode with it. + def test_pre_callback_filter + part + mock = MiniTest::Mock.new.expect(:call, nil, [GOOD_ARRAY]) + part.stub(:decode, mock) do + part.update(GOOD_STRING) + end + end + + def test_decode + # Error message in output if data is missing. + result = part.decode(SHORT_ARRAY) + assert result.keys.include? :error + assert result[:error]. match /missing/i + + # Error message in output if bad CRC. + result = part.decode(bad_crc) + assert result.keys.include? :error + assert result[:error]. match /crc/i + + # It should calculate output correctly. + result = part.decode(GOOD_ARRAY) + assert_equal result, {celsius: 29.5, farenheit: 85.1, humidity: 66.9} + end + + def test_crc + refute part.crc(bad_crc) + end +end From f606fadf3cbe586bad835377c2d1d34368bf6de4 Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 6 Feb 2023 00:44:43 -0400 Subject: [PATCH 208/296] Cleanup some tests --- lib/dino/board/base.rb | 2 +- lib/dino/tx_rx/base.rb | 2 +- lib/dino/tx_rx/serial.rb | 2 +- test/components/dht_test.rb | 4 ++-- test/components/rgb_led_test.rb | 2 +- test/components/setup/input_test.rb | 2 +- test/components/ssd_test.rb | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/dino/board/base.rb b/lib/dino/board/base.rb index 17587dc6..82bde344 100644 --- a/lib/dino/board/base.rb +++ b/lib/dino/board/base.rb @@ -5,7 +5,7 @@ class Base include API::EEPROM attr_reader :high, :low, :analog_high, :components - attr_reader :analog_zero, :dac_zero, :aux_limit, :eeprom_length + attr_reader :analog_zero, :dac_zero, :eeprom_length def initialize(io, options={}) @io, @components = io, [] diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index 267feff7..1b861ba7 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -30,7 +30,7 @@ def io_reset def flush_read Timeout.timeout(5) { read until read == nil } - rescue Timeout::Error + rescue Timeout::Error raise RxFlushTimeout "Cannot read from device, or device not running dino" end diff --git a/lib/dino/tx_rx/serial.rb b/lib/dino/tx_rx/serial.rb index 44554744..5a99be8b 100644 --- a/lib/dino/tx_rx/serial.rb +++ b/lib/dino/tx_rx/serial.rb @@ -71,7 +71,7 @@ def tty_devices end def on_windows? - RUBY_PLATFORM.match /mswin|mingw/i + RUBY_PLATFORM.match(/mswin|mingw/i) end end end diff --git a/test/components/dht_test.rb b/test/components/dht_test.rb index 21c1182e..2da6b874 100644 --- a/test/components/dht_test.rb +++ b/test/components/dht_test.rb @@ -48,12 +48,12 @@ def test_decode # Error message in output if data is missing. result = part.decode(SHORT_ARRAY) assert result.keys.include? :error - assert result[:error]. match /missing/i + assert result[:error].match(/missing/i) # Error message in output if bad CRC. result = part.decode(bad_crc) assert result.keys.include? :error - assert result[:error]. match /crc/i + assert result[:error].match(/crc/i) # It should calculate output correctly. result = part.decode(GOOD_ARRAY) diff --git a/test/components/rgb_led_test.rb b/test/components/rgb_led_test.rb index 0b55bf1f..a3d8c176 100644 --- a/test/components/rgb_led_test.rb +++ b/test/components/rgb_led_test.rb @@ -3,7 +3,7 @@ require 'minitest/autorun' class RGBLedTest < MiniTest::Test - def board + def board @board ||= BoardMock.new end diff --git a/test/components/setup/input_test.rb b/test/components/setup/input_test.rb index b65678ab..49a767c5 100644 --- a/test/components/setup/input_test.rb +++ b/test/components/setup/input_test.rb @@ -36,7 +36,7 @@ def test_pullup def test_pullup_in_options mock = Minitest::Mock.new.expect :call, nil, [2, true] board.stub(:set_pullup, mock) do - new_part = InputComponent.new(board: board, pin: 2, pullup: true) + InputComponent.new(board: board, pin: 2, pullup: true) end end end diff --git a/test/components/ssd_test.rb b/test/components/ssd_test.rb index 23b386a2..9f553fd5 100644 --- a/test/components/ssd_test.rb +++ b/test/components/ssd_test.rb @@ -3,7 +3,7 @@ require 'minitest/autorun' class SSDTest < MiniTest::Test - def board + def board @board ||= BoardMock.new end From faa361a3ba474047e07fdd999864019ea2cb03b3 Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 6 Feb 2023 01:54:42 -0400 Subject: [PATCH 209/296] Start using simplecov and cleanup test helper --- .gitignore | 2 ++ dino.gemspec | 5 +++-- test/api/core_test.rb | 4 +--- test/board_mock.rb | 8 ------- test/board_test.rb | 4 +--- test/components/basic/analog_input_test.rb | 4 +--- test/components/basic/analog_output_test.rb | 4 +--- test/components/basic/digital_input_test.rb | 4 +--- test/components/basic/digital_output_test.rb | 4 +--- test/components/dht_test.rb | 4 +--- test/components/ir_emitter_test.rb | 4 +--- test/components/lcd_test.rb | 4 +--- test/components/led_test.rb | 4 +--- test/components/mixins/callbacks_test.rb | 4 +--- test/components/mixins/listener_test.rb | 4 +--- test/components/mixins/poller_test.rb | 4 +--- test/components/mixins/reader_test.rb | 4 +--- test/components/mixins/threaded_test.rb | 4 +--- test/components/one_wire/ds18b20_test.rb | 4 +--- test/components/one_wire/enumerator_test.rb | 4 +--- test/components/one_wire/helper_test.rb | 1 + test/components/one_wire/slave_test.rb | 4 +--- test/components/register/select_test.rb | 4 +--- test/components/register/shift_in_test.rb | 4 +--- test/components/register/shift_out_test.rb | 4 +--- test/components/rgb_led_test.rb | 4 +--- test/components/servo_test.rb | 4 +--- test/components/setup/base_test.rb | 4 +--- test/components/setup/input_test.rb | 4 +--- test/components/setup/multi_pin_test.rb | 4 +--- test/components/setup/output_test.rb | 4 +--- test/components/setup/single_pin_test.rb | 4 +--- test/components/software_serial_test.rb | 4 +--- test/components/ssd_test.rb | 4 +--- test/components/stepper_test.rb | 4 +--- test/message_test.rb | 3 +-- test/test_helper.rb | 22 ++++++++++++++++++++ test/txrx/serial_test.rb | 3 --- test/txrx/tcp_test.rb | 3 --- test/txrx_mock.rb | 10 --------- 40 files changed, 60 insertions(+), 121 deletions(-) delete mode 100644 test/board_mock.rb delete mode 100644 test/txrx_mock.rb diff --git a/.gitignore b/.gitignore index 973343b7..6519d022 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ test/tmp test/version_tmp tmp *DS_Store* +.ruby-version +.rvmrc \ No newline at end of file diff --git a/dino.gemspec b/dino.gemspec index 4b0e967a..0a0b2d10 100644 --- a/dino.gemspec +++ b/dino.gemspec @@ -34,9 +34,10 @@ Gem::Specification.new do |gem| gem.version = Dino::VERSION gem.executables = ["dino"] - gem.add_dependency 'rubyserial', '~> 0.6.0' - gem.add_dependency 'bcd', '~> 0.3.0' + gem.add_dependency 'rubyserial' + gem.add_dependency 'bcd' gem.add_development_dependency 'rake' gem.add_development_dependency 'minitest' + gem.add_development_dependency 'simplecov' end diff --git a/test/api/core_test.rb b/test/api/core_test.rb index b7213b2a..fa7a04a7 100644 --- a/test/api/core_test.rb +++ b/test/api/core_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'txrx_mock' -require 'minitest/autorun' +require 'test_helper' class APICoreTest < Minitest::Test def txrx diff --git a/test/board_mock.rb b/test/board_mock.rb deleted file mode 100644 index ce0ecb7b..00000000 --- a/test/board_mock.rb +++ /dev/null @@ -1,8 +0,0 @@ -require 'dino' -require 'txrx_mock' - -class BoardMock < Dino::Board::Default - def initialize - super(TxRxMock.new) - end -end diff --git a/test/board_test.rb b/test/board_test.rb index 4e964cfb..c83912c7 100644 --- a/test/board_test.rb +++ b/test/board_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'txrx_mock' -require 'minitest/autorun' +require 'test_helper' class BoardTest < Minitest::Test def txrx diff --git a/test/components/basic/analog_input_test.rb b/test/components/basic/analog_input_test.rb index e97bd9b6..48e469d0 100644 --- a/test/components/basic/analog_input_test.rb +++ b/test/components/basic/analog_input_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class AnalogInputTest < Minitest::Test def board diff --git a/test/components/basic/analog_output_test.rb b/test/components/basic/analog_output_test.rb index e007988d..16eb63d3 100644 --- a/test/components/basic/analog_output_test.rb +++ b/test/components/basic/analog_output_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class AnalogOutputTest < Minitest::Test def board diff --git a/test/components/basic/digital_input_test.rb b/test/components/basic/digital_input_test.rb index 22929dd4..e630e4cb 100644 --- a/test/components/basic/digital_input_test.rb +++ b/test/components/basic/digital_input_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class DigitalInputTest < Minitest::Test def board diff --git a/test/components/basic/digital_output_test.rb b/test/components/basic/digital_output_test.rb index 64c69527..3b5d30b9 100644 --- a/test/components/basic/digital_output_test.rb +++ b/test/components/basic/digital_output_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class DigitalOutputTest < Minitest::Test def board diff --git a/test/components/dht_test.rb b/test/components/dht_test.rb index 2da6b874..911638d3 100644 --- a/test/components/dht_test.rb +++ b/test/components/dht_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class DHTTest < MiniTest::Test PIN = 1 diff --git a/test/components/ir_emitter_test.rb b/test/components/ir_emitter_test.rb index 35e6e62d..70013155 100644 --- a/test/components/ir_emitter_test.rb +++ b/test/components/ir_emitter_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class IREmitterTest < MiniTest::Test def board diff --git a/test/components/lcd_test.rb b/test/components/lcd_test.rb index 902571e2..892d2d81 100644 --- a/test/components/lcd_test.rb +++ b/test/components/lcd_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class LCDTest < MiniTest::Test def board diff --git a/test/components/led_test.rb b/test/components/led_test.rb index 39bb328b..ba8c2ebf 100644 --- a/test/components/led_test.rb +++ b/test/components/led_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class LedTest < MiniTest::Test def board diff --git a/test/components/mixins/callbacks_test.rb b/test/components/mixins/callbacks_test.rb index 734cbaa9..0d304eca 100644 --- a/test/components/mixins/callbacks_test.rb +++ b/test/components/mixins/callbacks_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class CallbackComponent include Dino::Components::Setup::Base diff --git a/test/components/mixins/listener_test.rb b/test/components/mixins/listener_test.rb index 30b9a9c8..801bda2f 100644 --- a/test/components/mixins/listener_test.rb +++ b/test/components/mixins/listener_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class ListenerComponent include Dino::Components::Setup::Base diff --git a/test/components/mixins/poller_test.rb b/test/components/mixins/poller_test.rb index 0972f390..b7ec82e6 100644 --- a/test/components/mixins/poller_test.rb +++ b/test/components/mixins/poller_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class PollerComponent include Dino::Components::Setup::Base diff --git a/test/components/mixins/reader_test.rb b/test/components/mixins/reader_test.rb index 1ea84e0a..19dac6e5 100644 --- a/test/components/mixins/reader_test.rb +++ b/test/components/mixins/reader_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class ReaderComponent include Dino::Components::Setup::Base diff --git a/test/components/mixins/threaded_test.rb b/test/components/mixins/threaded_test.rb index d12183df..a44b82ae 100644 --- a/test/components/mixins/threaded_test.rb +++ b/test/components/mixins/threaded_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class ThreadedComponent include Dino::Components::Setup::Base diff --git a/test/components/one_wire/ds18b20_test.rb b/test/components/one_wire/ds18b20_test.rb index 768f7efe..8a6c1499 100644 --- a/test/components/one_wire/ds18b20_test.rb +++ b/test/components/one_wire/ds18b20_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class DS18B20Test < MiniTest::Test def bus diff --git a/test/components/one_wire/enumerator_test.rb b/test/components/one_wire/enumerator_test.rb index c7e17429..04d69a86 100644 --- a/test/components/one_wire/enumerator_test.rb +++ b/test/components/one_wire/enumerator_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' # State machine simulating a bus during address search. Initialize with n for # n devices with random (CRC-invalid) addresses. Call #reset before each search. diff --git a/test/components/one_wire/helper_test.rb b/test/components/one_wire/helper_test.rb index 1acf3f67..152fe234 100644 --- a/test/components/one_wire/helper_test.rb +++ b/test/components/one_wire/helper_test.rb @@ -1 +1,2 @@ +require 'test_helper' # Test crc check. diff --git a/test/components/one_wire/slave_test.rb b/test/components/one_wire/slave_test.rb index 3369b746..e8d49d96 100644 --- a/test/components/one_wire/slave_test.rb +++ b/test/components/one_wire/slave_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' module Dino module Components diff --git a/test/components/register/select_test.rb b/test/components/register/select_test.rb index 955e9e6d..2ea99ea6 100644 --- a/test/components/register/select_test.rb +++ b/test/components/register/select_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class RegisterSelectTest < Minitest::Test def board diff --git a/test/components/register/shift_in_test.rb b/test/components/register/shift_in_test.rb index 1f60b16f..68618214 100644 --- a/test/components/register/shift_in_test.rb +++ b/test/components/register/shift_in_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class RegisterShiftInTest < Minitest::Test def board diff --git a/test/components/register/shift_out_test.rb b/test/components/register/shift_out_test.rb index 711cc7c3..5ad79f5c 100644 --- a/test/components/register/shift_out_test.rb +++ b/test/components/register/shift_out_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class RegisterShiftOutTest < Minitest::Test def board diff --git a/test/components/rgb_led_test.rb b/test/components/rgb_led_test.rb index a3d8c176..75443540 100644 --- a/test/components/rgb_led_test.rb +++ b/test/components/rgb_led_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class RGBLedTest < MiniTest::Test def board diff --git a/test/components/servo_test.rb b/test/components/servo_test.rb index 24158b68..dd44098d 100644 --- a/test/components/servo_test.rb +++ b/test/components/servo_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class ServoTest < MiniTest::Test def board diff --git a/test/components/setup/base_test.rb b/test/components/setup/base_test.rb index 4a7b3434..d16848c2 100644 --- a/test/components/setup/base_test.rb +++ b/test/components/setup/base_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class BaseComponent include Dino::Components::Setup::Base diff --git a/test/components/setup/input_test.rb b/test/components/setup/input_test.rb index 49a767c5..d960c2b6 100644 --- a/test/components/setup/input_test.rb +++ b/test/components/setup/input_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class InputComponent include Dino::Components::Setup::Input diff --git a/test/components/setup/multi_pin_test.rb b/test/components/setup/multi_pin_test.rb index 40bb6140..3c7b4bda 100644 --- a/test/components/setup/multi_pin_test.rb +++ b/test/components/setup/multi_pin_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class MultiPinComponent include Dino::Components::Setup::MultiPin diff --git a/test/components/setup/output_test.rb b/test/components/setup/output_test.rb index 59c44c5d..8ac78e92 100644 --- a/test/components/setup/output_test.rb +++ b/test/components/setup/output_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class OutputComponent include Dino::Components::Setup::Output diff --git a/test/components/setup/single_pin_test.rb b/test/components/setup/single_pin_test.rb index 9308d825..a46ecaa1 100644 --- a/test/components/setup/single_pin_test.rb +++ b/test/components/setup/single_pin_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class SinglePinComponent include Dino::Components::Setup::Input diff --git a/test/components/software_serial_test.rb b/test/components/software_serial_test.rb index da07697d..9a66d086 100644 --- a/test/components/software_serial_test.rb +++ b/test/components/software_serial_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class SoftwareSerialTest < MiniTest::Test def board diff --git a/test/components/ssd_test.rb b/test/components/ssd_test.rb index 9f553fd5..f8542316 100644 --- a/test/components/ssd_test.rb +++ b/test/components/ssd_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class SSDTest < MiniTest::Test def board diff --git a/test/components/stepper_test.rb b/test/components/stepper_test.rb index 7e17c3f7..c12e6c9b 100644 --- a/test/components/stepper_test.rb +++ b/test/components/stepper_test.rb @@ -1,6 +1,4 @@ -require 'dino' -require 'board_mock' -require 'minitest/autorun' +require 'test_helper' class StepperTest < MiniTest::Test def board diff --git a/test/message_test.rb b/test/message_test.rb index d56294da..c6559bb5 100644 --- a/test/message_test.rb +++ b/test/message_test.rb @@ -1,5 +1,4 @@ -require 'dino' -require 'minitest/autorun' +require 'test_helper' class MessageTest < Minitest::Test def test_require_a_command diff --git a/test/test_helper.rb b/test/test_helper.rb index b5b6a24b..58cc8b41 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,3 +1,9 @@ +require "minitest/autorun" +require 'simplecov' +SimpleCov.start do + add_filter "test" + track_files "lib/**/*.rb" +end require 'dino' # Nice little helper module to redefine constants quietly. @@ -27,3 +33,19 @@ def suppress_output end retval end + +class TxRxMock + def add_observer(board); true; end + def read; true; end + def write(str); true; end + def handshake + "528,1024,14,20" + end +end + +class BoardMock < Dino::Board::Default + def initialize + super(TxRxMock.new) + end +end + diff --git a/test/txrx/serial_test.rb b/test/txrx/serial_test.rb index defadb80..16034a2d 100644 --- a/test/txrx/serial_test.rb +++ b/test/txrx/serial_test.rb @@ -1,6 +1,3 @@ -require 'dino' -require 'minitest/autorun' -require 'txrx_mock' require 'test_helper' class DummySerial diff --git a/test/txrx/tcp_test.rb b/test/txrx/tcp_test.rb index a10f4958..4d8c97a4 100644 --- a/test/txrx/tcp_test.rb +++ b/test/txrx/tcp_test.rb @@ -1,6 +1,3 @@ -require 'dino' -require 'minitest/autorun' -require 'txrx_mock' require 'test_helper' class DummySerial diff --git a/test/txrx_mock.rb b/test/txrx_mock.rb deleted file mode 100644 index 65d99435..00000000 --- a/test/txrx_mock.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'dino' - -class TxRxMock - def add_observer(board); true; end - def read; true; end - def write(str); true; end - def handshake - "528,1024,14,20" - end -end From b7fb1f59d092c287d94eb62caf71a0478e0cf1b7 Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 6 Feb 2023 10:57:25 -0400 Subject: [PATCH 210/296] Tests for core and one wire api --- lib/dino/api/core.rb | 19 ++++++++--- test/api/core_test.rb | 37 +++++++++++++++++++++ test/api/one_wire_test.rb | 69 +++++++++++++++++++++++++++++++++++++++ test/test_helper.rb | 5 ++- 4 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 test/api/one_wire_test.rb diff --git a/lib/dino/api/core.rb b/lib/dino/api/core.rb index 3980008a..f2c334ec 100644 --- a/lib/dino/api/core.rb +++ b/lib/dino/api/core.rb @@ -73,13 +73,24 @@ def stop_listener(pin) end def pulse_read(pin, options={}) - reset = options[:reset] || false - reset_time = options[:reset_time] || 0 + # Hold the input pin high or low (give as values) before reading + reset = options[:reset] || false + + # How long to reset the pin for in ms + reset_time = options[:reset_time] || 0 + + # Maximum number of pulses to capture pulse_limit = options[:pulse_limit] || 100 - timeout = options[:timeout] || 200 + + # A pulse of this length will end the read + timeout = options[:timeout] || 200 + + raise ArgumentError("reset time must be betwen 0 and 65535 ms")if reset_time > 0xFFFF + raise ArgumentError("timeout must be betwen 0 and 65535 ms")if timeout > 0xFFFF + raise ArgumentError("pulse limit must be betwen 0 and 255")if pulse_limit > 0xFF settings = reset ? 1 : 0 - settings = settings | 0b10 if reset == high + settings = settings | 0b10 if (reset && reset != low) aux = pack :uint16, [reset_time, timeout] aux << pack(:uint8, pulse_limit) diff --git a/test/api/core_test.rb b/test/api/core_test.rb index fa7a04a7..1627bf1e 100644 --- a/test/api/core_test.rb +++ b/test/api/core_test.rb @@ -1,6 +1,8 @@ require 'test_helper' class APICoreTest < Minitest::Test + include Dino::API::Helper + def txrx @txrx ||= TxRxMock.new end @@ -133,4 +135,39 @@ def test_analog_resolution end mock.verify end + + def test_pulse_read + # Default settings + mock = MiniTest::Mock.new + aux = pack(:uint16, [0, 200]) << pack(:uint8, 100) + message = Dino::Message.encode command: 11, pin: 4, value: 0b00, aux_message: aux + + mock.expect :call, nil, [message] + board.stub(:write, mock) do + board.pulse_read(4) + end + mock.verify + + # Good options + mock = MiniTest::Mock.new + aux = pack(:uint16, [1000, 200]) << pack(:uint8, 160) + message1 = Dino::Message.encode command: 11, pin: 4, value: 0b01, aux_message: aux + message2 = Dino::Message.encode command: 11, pin: 4, value: 0b11, aux_message: aux + + mock.expect :call, nil, [message1] + mock.expect :call, nil, [message2] + board.stub(:write, mock) do + board.pulse_read(4, reset: board.low, reset_time: 1000, timeout: 200, pulse_limit: 160) + board.pulse_read(4, reset: board.high, reset_time: 1000, timeout: 200, pulse_limit: 160) + end + mock.verify + + # Bad options + assert_raises(ArgumentError) { board.one_wire_write(4, reset_time: 65536) } + assert_raises(ArgumentError) { board.one_wire_write(4, reset_time: -1) } + assert_raises(ArgumentError) { board.one_wire_write(4, timeout: 65536) } + assert_raises(ArgumentError) { board.one_wire_write(4, timeout: -1) } + assert_raises(ArgumentError) { board.one_wire_write(4, pulse_limit: 256) } + assert_raises(ArgumentError) { board.one_wire_write(4, pulse_limit: -1) } + end end diff --git a/test/api/one_wire_test.rb b/test/api/one_wire_test.rb new file mode 100644 index 00000000..4fcb88ca --- /dev/null +++ b/test/api/one_wire_test.rb @@ -0,0 +1,69 @@ +require 'test_helper' + +class APIOneWireTest < Minitest::Test + include Dino::API::Helper + + def txrx + @txrx ||= TxRxMock.new + end + + def board + @board ||= Dino::Board.new(txrx) + end + + def test_one_wire_reset + board + message = Dino::Message.encode command: 41, pin: 1, value: 255 + + mock = MiniTest::Mock.new.expect :call, nil, [message] + txrx.stub(:write, mock) do + board.one_wire_reset(1, 255) + end + mock.verify + end + + def test_one_wire_search + board + message = Dino::Message.encode command: 42, pin: 1, aux_message: pack(:uint64, 128, max:8) + + mock = MiniTest::Mock.new.expect :call, nil, [message] + txrx.stub(:write, mock) do + board.one_wire_search(1, 128) + end + mock.verify + end + + def test_one_wire_write + board + + # Calculate length and parasite power properly. + message1 = Dino::Message.encode command: 43, pin: 1, value: 0b10000000 | 3, aux_message: pack(:uint8, [1,2,3]) + message2 = Dino::Message.encode command: 43, pin: 1, value: 4, aux_message: pack(:uint8, [1,2,3,4]) + + mock = MiniTest::Mock.new + mock.expect :call, nil, [message1] + mock.expect :call, nil, [message2] + txrx.stub(:write, mock) do + board.one_wire_write(1, true, [1,2,3]) + board.one_wire_write(1, nil, [1,2,3,4]) + end + mock.verify + + # Don't allow more than 127 bytes of data. + assert_raises(ArgumentError) do + too_big = Array.new(128).map { 42 } + board.one_wire_write(1, true, too_big) + end + end + + def test_one_wire_read + board + message = Dino::Message.encode command: 44, pin: 1, value: 9 + + mock = MiniTest::Mock.new.expect :call, nil, [message] + txrx.stub(:write, mock) do + board.one_wire_read(1, 9) + end + mock.verify + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 58cc8b41..d02cb904 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,9 +1,12 @@ require "minitest/autorun" + require 'simplecov' SimpleCov.start do - add_filter "test" track_files "lib/**/*.rb" + add_filter "test" + add_filter "lib/dino_cli" end + require 'dino' # Nice little helper module to redefine constants quietly. From 652c72671a0849146693a1088dfe0acad08ea92a Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 7 Feb 2023 12:13:48 -0400 Subject: [PATCH 211/296] EEPROM API and binary packing tests --- lib/dino/api/core.rb | 13 ++++++++++--- lib/dino/api/eeprom.rb | 2 +- lib/dino/api/helper.rb | 2 +- test/api/core_test.rb | 12 ++++++------ test/api/eeprom_test.rb | 34 ++++++++++++++++++++++++++++++++++ test/api/helper_test.rb | 38 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 90 insertions(+), 11 deletions(-) create mode 100644 test/api/eeprom_test.rb create mode 100644 test/api/helper_test.rb diff --git a/lib/dino/api/core.rb b/lib/dino/api/core.rb index f2c334ec..f79b359b 100644 --- a/lib/dino/api/core.rb +++ b/lib/dino/api/core.rb @@ -85,9 +85,16 @@ def pulse_read(pin, options={}) # A pulse of this length will end the read timeout = options[:timeout] || 200 - raise ArgumentError("reset time must be betwen 0 and 65535 ms")if reset_time > 0xFFFF - raise ArgumentError("timeout must be betwen 0 and 65535 ms")if timeout > 0xFFFF - raise ArgumentError("pulse limit must be betwen 0 and 255")if pulse_limit > 0xFF + # Validate + if reset_time < 0 || reset_time > 0xFFFF + raise ArgumentError, "reset time must be betwen 0 and 65535 ms" + end + if timeout < 0 || timeout > 0xFFFF + raise ArgumentError, "timeout must be betwen 0 and 65535 ms" + end + if pulse_limit < 0 || pulse_limit > 0xFF + raise ArgumentError, "pulse limit must be betwen 0 and 255" + end settings = reset ? 1 : 0 settings = settings | 0b10 if (reset && reset != low) diff --git a/lib/dino/api/eeprom.rb b/lib/dino/api/eeprom.rb index 2b918220..0eb02f97 100644 --- a/lib/dino/api/eeprom.rb +++ b/lib/dino/api/eeprom.rb @@ -12,7 +12,7 @@ def eeprom_read(address, num_bytes) end # CMD = 7 - def eeprom_write(address, bytes=[]) + def eeprom_write(address, bytes) address = pack :uint16, address bytes = pack :uint8, bytes, min: 1, max: 128 write Message.encode command: 7, diff --git a/lib/dino/api/helper.rb b/lib/dino/api/helper.rb index 3692a8c9..6dc91178 100644 --- a/lib/dino/api/helper.rb +++ b/lib/dino/api/helper.rb @@ -8,7 +8,7 @@ def pack(type, data, options={}) when :uint32 then 'L<*' when :uint16 then 'S<*' when :uint8 then 'C*' - else raise ArgumentError "unsupported pack format '#{type}'" + else raise ArgumentError, "unsupported pack format '#{type}'" end # Can pass a single integer to get packed if we always [] then flatten. diff --git a/test/api/core_test.rb b/test/api/core_test.rb index 1627bf1e..5cdd33d4 100644 --- a/test/api/core_test.rb +++ b/test/api/core_test.rb @@ -163,11 +163,11 @@ def test_pulse_read mock.verify # Bad options - assert_raises(ArgumentError) { board.one_wire_write(4, reset_time: 65536) } - assert_raises(ArgumentError) { board.one_wire_write(4, reset_time: -1) } - assert_raises(ArgumentError) { board.one_wire_write(4, timeout: 65536) } - assert_raises(ArgumentError) { board.one_wire_write(4, timeout: -1) } - assert_raises(ArgumentError) { board.one_wire_write(4, pulse_limit: 256) } - assert_raises(ArgumentError) { board.one_wire_write(4, pulse_limit: -1) } + assert_raises(ArgumentError) { board.pulse_read(4, reset_time: 65536) } + assert_raises(ArgumentError) { board.pulse_read(4, reset_time: -1) } + assert_raises(ArgumentError) { board.pulse_read(4, timeout: 65536) } + assert_raises(ArgumentError) { board.pulse_read(4, timeout: -1) } + assert_raises(ArgumentError) { board.pulse_read(4, pulse_limit: 256) } + assert_raises(ArgumentError) { board.pulse_read(4, pulse_limit: -1) } end end diff --git a/test/api/eeprom_test.rb b/test/api/eeprom_test.rb new file mode 100644 index 00000000..0b63a9f8 --- /dev/null +++ b/test/api/eeprom_test.rb @@ -0,0 +1,34 @@ +require 'test_helper' + +class APIEEPROMTest < Minitest::Test + include Dino::API::Helper + + def txrx + @txrx ||= TxRxMock.new + end + + def board + @board ||= Dino::Board.new(txrx) + end + + def test_eeprom_read + mock = MiniTest::Mock.new + mock.expect :call, nil, [(Dino::Message.encode command: 6, value: 16, aux_message: pack(:uint16, 15))] + + board.stub(:write, mock) do + board.eeprom_read(15, 16) + end + mock.verify + end + + def test_eeprom_write + data = (1..16).to_a + mock = MiniTest::Mock.new + mock.expect :call, nil, [(Dino::Message.encode command: 7, value: data.length, aux_message: pack(:uint16, 15) + pack(:uint8, data))] + + board.stub(:write, mock) do + board.eeprom_write(15, data) + end + mock.verify + end +end diff --git a/test/api/helper_test.rb b/test/api/helper_test.rb new file mode 100644 index 00000000..eee9f35a --- /dev/null +++ b/test/api/helper_test.rb @@ -0,0 +1,38 @@ +# encoding: ascii-8bit +# For convenience when validating longer data types. + +require 'test_helper' + +class APIHelperTest < Minitest::Test + include Dino::API::Helper + + def test_single_integers + assert_equal pack(:uint8, 25), "\x19" + end + + def test_other_formats + assert_equal pack(:uint16, 5*10**4), "P\xC3" + assert_equal pack(:uint32, 4*10**9), "\x00(k\xEE" + assert_equal pack(:uint64, 9*10**18), "\x00\x00\x84\xE2Pl\xE6|" + end + + def test_array + assert_equal pack(:uint8, [25,26]), "\x19\x1A" + end + + def test_padding + assert_equal pack(:uint8, 25, pad: 2), "\x19\x00" + end + + def test_min + assert_raises(ArgumentError) { pack(:uint8, 25, min: 2) } + end + + def test_max + assert_raises(ArgumentError) { pack(:uint8, [25,26,27], max: 2) } + end + + def test_invalid_formats + assert_raises(ArgumentError) { pack(:uint128, 25) } + end +end From 35a605f3a26edda6afac0be83a8c5c655f4b074b Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 7 Feb 2023 14:38:41 -0400 Subject: [PATCH 212/296] API tests for I2C, IR emit, servo and tone --- examples/ir_emitter/ir_emitter.rb | 2 +- lib/dino/api/infrared.rb | 2 +- lib/dino/api/servo.rb | 2 +- lib/dino/api/tone.rb | 3 ++ lib/dino/components/ir_emitter.rb | 6 +-- test/api/i2c_test.rb | 76 ++++++++++++++++++++++++++++++ test/api/infrared_test.rb | 37 +++++++++++++++ test/api/servo_test.rb | 49 +++++++++++++++++++ test/api/tone_test.rb | 33 +++++++++++++ test/components/ir_emitter_test.rb | 37 ++++++++++----- 10 files changed, 228 insertions(+), 19 deletions(-) create mode 100644 test/api/i2c_test.rb create mode 100644 test/api/infrared_test.rb create mode 100644 test/api/servo_test.rb create mode 100644 test/api/tone_test.rb diff --git a/examples/ir_emitter/ir_emitter.rb b/examples/ir_emitter/ir_emitter.rb index 8a82f564..ed51e779 100644 --- a/examples/ir_emitter/ir_emitter.rb +++ b/examples/ir_emitter/ir_emitter.rb @@ -52,4 +52,4 @@ 560, 1690, 560, 560, 560, 560, 560, 560, 560] -ir.send(code) +ir.emit(code) diff --git a/lib/dino/api/infrared.rb b/lib/dino/api/infrared.rb index bdaa0ff5..68fa112e 100644 --- a/lib/dino/api/infrared.rb +++ b/lib/dino/api/infrared.rb @@ -3,7 +3,7 @@ module API module Infrared include Helper - def infrared_send(pin, frequency, pulses) + def infrared_emit(pin, frequency, pulses) # Need to start using length - 1, but doesn't work on board yet. # 0 = 1 pulse, 255 = 256 pulses. length = pack :uint8, pulses.length, max: 1 diff --git a/lib/dino/api/servo.rb b/lib/dino/api/servo.rb index 487c4185..d3726384 100644 --- a/lib/dino/api/servo.rb +++ b/lib/dino/api/servo.rb @@ -11,7 +11,7 @@ def servo_toggle(pin, value=:off, options={}) write Message.encode command: 8, pin: convert_pin(pin), value: (value == :off) ? 0 : 1, - aux: aux + aux_message: aux end def servo_write(pin, value=0) diff --git a/lib/dino/api/tone.rb b/lib/dino/api/tone.rb index 4b59bd42..9f4a7170 100644 --- a/lib/dino/api/tone.rb +++ b/lib/dino/api/tone.rb @@ -3,6 +3,9 @@ module API module Tone include Helper + # value = tone frequency in Hz (should be sent as binary to allow higher than 9999, and limit to above 30Hz) + # duration = tone duration in ms + # duration currently sent as string. Should be binary and max limited to uint32 def tone(pin, value, duration) write Dino::Message.encode command: 17, pin: convert_pin(pin), diff --git a/lib/dino/components/ir_emitter.rb b/lib/dino/components/ir_emitter.rb index 687c3d17..15428f99 100644 --- a/lib/dino/components/ir_emitter.rb +++ b/lib/dino/components/ir_emitter.rb @@ -1,7 +1,7 @@ module Dino module Components class IREmitter < Basic::DigitalOutput - def send(pulses=[], options={}) + def emit(pulses=[], options={}) if pulses.length > 256 || pulses.length < 1 raise ArgumentError, 'wrong number of IR pulses (expected 1 to 256)' end @@ -9,11 +9,11 @@ def send(pulses=[], options={}) pulses.each_with_index do |pulse, index| raise ArgumentError, 'non Numeric data in IR signal' unless pulse.is_a? Numeric pulses[index] = pulse.round unless pulse.is_a? Integer - raise ArgumentError 'pulse too long (max 65536 microsec)' if pulse > 65536 + raise ArgumentError, 'pulse too long (max 65535 ms)' if pulse > 65535 end frequency = options[:frequency] || 38 - board.infrared_send(pin, frequency, pulses) + board.infrared_emit(pin, frequency, pulses) end end end diff --git a/test/api/i2c_test.rb b/test/api/i2c_test.rb new file mode 100644 index 00000000..1aa84eb4 --- /dev/null +++ b/test/api/i2c_test.rb @@ -0,0 +1,76 @@ +require 'test_helper' + +class APII2CTest < Minitest::Test + include Dino::API::Helper + + def txrx + @txrx ||= TxRxMock.new + end + + def board + @board ||= Dino::Board.new(txrx) + end + + def test_search + board + message = Dino::Message.encode command: 33 + + mock = MiniTest::Mock.new.expect :call, nil, [message] + txrx.stub(:write, mock) do + board.i2c_search + end + mock.verify + end + + def test_write + board + aux = pack :uint8, [0x30, 0, 4, [1,2,3,4]] + # Normal + message1 = Dino::Message.encode command: 34, value: 0b00, aux_message: aux + # Repeated start + message2 = Dino::Message.encode command: 34, value: 0b01, aux_message: aux + + mock = MiniTest::Mock.new + mock.expect :call, nil, [message1] + mock.expect :call, nil, [message2] + + txrx.stub(:write, mock) do + board.i2c_write(0x30, [1,2,3,4]) + board.i2c_write(0x30, [1,2,3,4], repeated_start: true) + end + mock.verify + end + + def test_read + board + aux = pack :uint8, [0x30, 0, 0x03, 4] + # Normal + message1 = Dino::Message.encode command: 35, value: 0b10, aux_message: aux + # Repeated start + message2 = Dino::Message.encode command: 35, value: 0b11, aux_message: aux + + mock = MiniTest::Mock.new + mock.expect :call, nil, [message1] + mock.expect :call, nil, [message2] + + txrx.stub(:write, mock) do + board.i2c_read(0x30, 0x03, 4) + board.i2c_read(0x30, 0x03, 4, repeated_start: true) + end + mock.verify + end + + def test_read_without_register + board + aux = pack :uint8, [0x30, 0, 0, 4] + message = Dino::Message.encode command: 35, value: 0b00, aux_message: aux + + mock = MiniTest::Mock.new + mock.expect :call, nil, [message] + + txrx.stub(:write, mock) do + board.i2c_read(0x30, nil, 4) + end + mock.verify + end +end diff --git a/test/api/infrared_test.rb b/test/api/infrared_test.rb new file mode 100644 index 00000000..2493cdfa --- /dev/null +++ b/test/api/infrared_test.rb @@ -0,0 +1,37 @@ +require 'test_helper' + +class APIInfraredTest < Minitest::Test + include Dino::API::Helper + + def txrx + @txrx ||= TxRxMock.new + end + + def board + @board ||= Dino::Board.new(txrx) + end + + def test_infrared_emit + board + aux = pack(:uint8, 4) + pack(:uint16, [255,0,255,0]) + message = Dino::Message.encode command: 16, pin: 8, value: 38, aux_message: aux + + mock = MiniTest::Mock.new.expect :call, nil, [message] + txrx.stub(:write, mock) do + board.infrared_emit 8, 38, [255,0,255,0] + end + mock.verify + end + + def test_minimum_pulses + assert_raises(ArgumentError) do + board.infrared_emit 8, 38, [] + end + end + + def test_maximum_pulses + assert_raises(ArgumentError) do + board.infrared_emit 8, 38, Array.new(513) { 128 } + end + end +end diff --git a/test/api/servo_test.rb b/test/api/servo_test.rb new file mode 100644 index 00000000..76f1b6e7 --- /dev/null +++ b/test/api/servo_test.rb @@ -0,0 +1,49 @@ +# For convenience when validating longer data types. + +require 'test_helper' + +class APIServoTest < Minitest::Test + include Dino::API::Helper + + def txrx + @txrx ||= TxRxMock.new + end + + def board + @board ||= Dino::Board.new(txrx) + end + + def test_on_off + mock = MiniTest::Mock.new + aux = pack :uint16, [544, 2400] + mock.expect :call, nil, [Dino::Message.encode(command: 8, pin: 9, value: 1, aux_message: aux)] + mock.expect :call, nil, [Dino::Message.encode(command: 8, pin: 9, value: 0, aux_message: aux)] + + board.stub(:write, mock) do + board.servo_toggle(9, :on) + board.servo_toggle(9) + end + mock.verify + end + + def test_min_max + mock = MiniTest::Mock.new + aux = pack :uint16, [360, 2100] + mock.expect :call, nil, [Dino::Message.encode(command: 8, pin: 9, value: 1, aux_message: aux)] + + board.stub(:write, mock) do + board.servo_toggle(9, :on, min: 360, max: 2100) + end + mock.verify + end + + def test_write + mock = MiniTest::Mock.new + mock.expect :call, nil, [Dino::Message.encode(command: 9, pin: 9, aux_message: pack(:uint16, 180))] + + board.stub(:write, mock) do + board.servo_write(9, 180) + end + mock.verify + end +end diff --git a/test/api/tone_test.rb b/test/api/tone_test.rb new file mode 100644 index 00000000..e5d160fb --- /dev/null +++ b/test/api/tone_test.rb @@ -0,0 +1,33 @@ +require 'test_helper' + +class APIToneTest < Minitest::Test + include Dino::API::Helper + + def txrx + @txrx ||= TxRxMock.new + end + + def board + @board ||= Dino::Board.new(txrx) + end + + def test_tone + mock = MiniTest::Mock.new + mock.expect :call, nil, [Dino::Message.encode(command: 17, pin: 10, value: 150, aux_message: 2000)] + + board.stub(:write, mock) do + board.tone(10, 150, 2000) + end + mock.verify + end + + def test_no_tone + mock = MiniTest::Mock.new + mock.expect :call, nil, [Dino::Message.encode(command: 18, pin: 10)] + + board.stub(:write, mock) do + board.no_tone(10) + end + mock.verify + end +end diff --git a/test/components/ir_emitter_test.rb b/test/components/ir_emitter_test.rb index 70013155..ee91a4bc 100644 --- a/test/components/ir_emitter_test.rb +++ b/test/components/ir_emitter_test.rb @@ -8,20 +8,31 @@ def board def part @part ||= Dino::Components::IREmitter.new(board: board, pin:1) end - - # These are really API tests. - # Should move these and test that API methods get called here instead. - def test_packs_pulses_correctly - part - string = "16.1.38.#{[4].pack('C')}#{[100,200,300,400].pack('S<*')}\n" - mock = MiniTest::Mock.new.expect(:call, nil, [string]) - board.stub(:write, mock) { part.send [100,200,300,400] } + + def test_pulse_count_validation + assert_raises(ArgumentError) do + part.emit Array.new(257) { 0 } + end end - - def test_accepts_modulation_frequency_as_option + + def test_numeric_validation + assert_raises(ArgumentError) do + part.emit ["a", "b", "c"] + end + end + + def test_pulse_length_validation + assert_raises(ArgumentError) do + part.emit [65536] + end + end + + def test_emits part - string = "16.1.40.#{[4].pack('C')}#{[100,200,300,400].pack('S<*')}\n" - mock = MiniTest::Mock.new.expect(:call, nil, [string]) - board.stub(:write, mock) { part.send [100,200,300,400], frequency: 40 } + mock = MiniTest::Mock.new.expect(:call, nil, [1, 38, [127,0]]) + board.stub(:infrared_emit, mock) do + part.emit([127,0]) + end + mock.verify end end From 60b4fb0e360441f4c7498431fd06c7a4e764b29b Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 7 Feb 2023 18:35:35 -0400 Subject: [PATCH 213/296] SPI testing and cleanup --- lib/dino/api/spi.rb | 57 +++++++++++++++++++++---------- src/lib/DinoSPI.cpp | 26 +++++++------- test/api/spi_test.rb | 80 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 29 deletions(-) create mode 100644 test/api/spi_test.rb diff --git a/lib/dino/api/spi.rb b/lib/dino/api/spi.rb index b023c0d8..40449a04 100644 --- a/lib/dino/api/spi.rb +++ b/lib/dino/api/spi.rb @@ -4,28 +4,50 @@ module SPI include Helper def spi_header(options) + options[:read] ||= 0 + options[:write] = [options[:write]].flatten.compact || [] options[:mode] ||= 0 options[:frequency] ||= 3000000 - options[:bit_order] ||= :lsbfirst - raise ArgumentError, - 'invalid SPI mode' unless (0..3).include? options[:mode] - - # Flag unused high bit of mode if we need to transfer MSBFIRST. - settings = options[:mode] - settings = settings | 0b10000000 if options[:bit_order] == :msbfirst - + options[:bit_order] ||= :msbfrst + + # Bit 0..3 of settings control the SPI mode + # + # 0000 = SPI_MODE0 + # 0100 = SPI_MODE1 + # 1000 = SPI_MODE2 + # 1100 = SPI_MODE3 + # + settings = case options[:mode] + when 0 + 0b0000 + when 1 + 0b0100 + when 2 + 0b1000 + when 3 + 0b1100 + else + raise ArgumentError, "invalid SPI mode. Must be 0, 1, 2, or 3" + end + + # Bit 7 of settings toggles MSBFIRST (1) or LSBFIRST (0) transmission order. + settings = settings | 0b10000000 unless options[:bit_order] == :lsbfirst + + raise ArgumentError, "can't read more than 255 SPI bytes at a time" if options[:read] > 255 + raise ArgumentError, "can't write more than 255 SPI bytes at a time" if options[:write].length > 255 + uint8 = pack(:uint8, [settings, options[:read], options[:write].length]) - uint8 + pack(:uint32, options[:frequency]) + [uint8 + pack(:uint32, options[:frequency]), options] end # CMD = 26 def spi_transfer(pin, options={}) - options[:read] ||= 0 - options[:write] = [options[:write]].flatten.compact || [] - - return if (options[:read] == 0) && (options[:write].empty?) - - header = spi_header(options) + header, options = spi_header(options) + + if (options[:read] == 0) && (options[:write].empty?) + raise ArgumentError, "no SPI bytes to read or write given" + end + write Message.encode command: 26, pin: pin, aux_message: header + pack(:uint8, options[:write]) @@ -33,9 +55,10 @@ def spi_transfer(pin, options={}) # CMD = 27 def spi_listen(pin, options={}) + header, options = spi_header(options) + raise ArgumentError, 'no SPI bytes to read' unless (options[:read] > 0) - - header = spi_header(options) + write Message.encode command: 27, pin: pin, aux_message: header diff --git a/src/lib/DinoSPI.cpp b/src/lib/DinoSPI.cpp index 992fd9a2..5dafe09e 100644 --- a/src/lib/DinoSPI.cpp +++ b/src/lib/DinoSPI.cpp @@ -21,15 +21,13 @@ SpiListener spiListeners[SPI_LISTENER_COUNT]; void Dino::spiBegin(byte settings, uint32_t clockRate){ SPI.begin(); - bool msbfirst = bitRead(settings, 7); + // SPI mode is the lowest 4 bits of settings. byte mode = settings; - bitClear(mode, 7); + mode << 4; + mode >> 4; - if (msbfirst) { - SPI.beginTransaction(SPISettings(clockRate, MSBFIRST, mode)); - } else { - SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, mode)); - } + // Bit 7 of settings toggles MSBFIRST. + SPI.beginTransaction(SPISettings(clockRate, bitRead(settings, 7), mode)); } // Convenience wrapper for SPI.end @@ -48,16 +46,20 @@ void Dino::spiEnd(){ // Request format for SPI 2-way transfers // pin = slave select pin (int) // val = empty -// auxMsg[0] = SPI settings. 2 LSB = SPI mode. Bit 7 = MSB(1) or LSB(0). -// auxMsg[1] = write length (number of bytes) -// auxMsg[2] = read length (number of bytes) +// auxMsg[0] = SPI settings +// Bit 0..3 = SPI mode +// Bit 7 = MSBFIRST(1) or LSBFIRST(0) +// auxMsg[1] = read length (number of bytes) +// auxMsg[2] = write length (number of bytes) // auxMsg[3-6] = clock frequency (uint32_t as 4 bytes) -// // auxMsg[7+] = data (bytes) (write only) // void Dino::spiTransfer(int selectPin, byte settings, byte rLength, byte wLength, uint32_t clockRate, byte *data) { - spiBegin(settings, clockRate); + // Pull select pin low. + pinMode(selectPin, OUTPUT); digitalWrite(selectPin, LOW); + + spiBegin(settings, clockRate); if (rLength > 0) { // Stream read bytes as if coming from select pin for easy identification. diff --git a/test/api/spi_test.rb b/test/api/spi_test.rb new file mode 100644 index 00000000..af824f7e --- /dev/null +++ b/test/api/spi_test.rb @@ -0,0 +1,80 @@ +# For convenience when validating longer data types. + +require 'test_helper' + +class APISPITest < Minitest::Test + include Dino::API::Helper + + def txrx + @txrx ||= TxRxMock.new + end + + def board + @board ||= Dino::Board.new(txrx) + end + + def test_spi_modes + assert_equal (board.spi_header(mode: nil)[0][0]), (pack :uint8, 0b10000000) + assert_equal (board.spi_header(mode: 1 )[0][0]), (pack :uint8, 0b10000100) + assert_equal (board.spi_header(mode: 2 )[0][0]), (pack :uint8, 0b10001000) + assert_equal (board.spi_header(mode: 3 )[0][0]), (pack :uint8, 0b10001100) + assert_raises(ArgumentError) { board.spi_header(mode: 4) } + end + + def test_spi_lsbfirst + assert_equal (board.spi_header(bit_order: :lsbfirst)[0][0]), (pack :uint8, 0b00000000) + end + + def test_spi_frequency + assert_equal (board.spi_header(frequency: nil )[0][3..6]), (pack :uint32, 3000000) + assert_equal (board.spi_header(frequency: 8000000)[0][3..6]), (pack :uint32, 8000000) + end + + def test_spi_too_many_bytes + assert_raises(ArgumentError) { board.spi_header(read: 256) } + assert_raises(ArgumentError) { board.spi_header(write: Array.new(256){0})} + end + + def test_spi_no_bytes + assert_raises(ArgumentError) { board.spi_transfer(3, read: 0) } + assert_raises(ArgumentError) { board.spi_listen(3, read: 0) } + end + + def test_spi_transfer + board + options = { write: [1,2,3,4], read: 4, bit_order: :lsbfirst, frequency: 8000000, mode: 2 } + header = board.spi_header(options)[0] + aux = header + pack(:uint8, options[:write]) + + mock = MiniTest::Mock.new + mock.expect :call, nil, [Dino::Message.encode(command: 26, pin: 3, aux_message: aux)] + + board.stub(:write, mock) do + board.spi_transfer(3, options) + end + mock.verify + end + + def test_spi_listen + board + options = { read: 8, bit_order: :lsbfirst } + header = board.spi_header(options)[0] + + mock = MiniTest::Mock.new + mock.expect :call, nil, [Dino::Message.encode(command: 27, pin: 3, aux_message: header)] + + board.stub(:write, mock) do + board.spi_listen(3, options) + end + mock.verify + end + + def test_spi_stop + board + mock = MiniTest::Mock.new.expect :call, nil, [Dino::Message.encode(command: 28, pin: 3)] + board.stub(:write, mock) do + board.spi_stop(3) + end + mock.verify + end +end From 8560b463552958cd0727c1e9c2de3649075cfdf5 Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 7 Feb 2023 19:53:44 -0400 Subject: [PATCH 214/296] Simplify EEPROM updating from board and board tests --- lib/dino/board/base.rb | 17 +++----------- lib/dino/board/default.rb | 1 + lib/dino/board/esp8266.rb | 1 + test/board_test.rb | 47 ++++++++++++++++++++++++++------------- 4 files changed, 37 insertions(+), 29 deletions(-) diff --git a/lib/dino/board/base.rb b/lib/dino/board/base.rb index 82bde344..9c4531cc 100644 --- a/lib/dino/board/base.rb +++ b/lib/dino/board/base.rb @@ -42,25 +42,14 @@ def write(msg) end def update(line) - case line - when /\AEE:/ - update_eeprom(line) - else - update_component(line) - end - end - - def update_eeprom(line) - message = line.split(":", 2)[1] - @components.each do |part| - part.update(message) if part.pin == "EE" - end + update_component(line) end def update_component(line) pin, message = line.split(":", 2) + pin = pin.to_i unless pin == "EE" @components.each do |part| - part.update(message) if pin.to_i == convert_pin(part.pin) + part.update(message) if pin == convert_pin(part.pin) end end diff --git a/lib/dino/board/default.rb b/lib/dino/board/default.rb index 57d19b91..c7358539 100644 --- a/lib/dino/board/default.rb +++ b/lib/dino/board/default.rb @@ -18,6 +18,7 @@ def convert_pin(pin) return pin.to_i if pin.match(DIGITAL_REGEX) return analog_pin_to_i(pin) if pin.match(ANALOG_REGEX) return dac_pin_to_i(pin) if pin.match(DAC_REGEX) + return "EE" if pin == "EE" raise ArgumentError, "incorrect pin format" end diff --git a/lib/dino/board/esp8266.rb b/lib/dino/board/esp8266.rb index 7ebca523..cbd28e5c 100644 --- a/lib/dino/board/esp8266.rb +++ b/lib/dino/board/esp8266.rb @@ -34,6 +34,7 @@ def convert_pin(pin) return gpio_pin_to_i(pin) if pin.match(GPIO_REGEX) return analog_pin_to_i(pin) if pin.match(ANALOG_REGEX) return digital_pin_to_i(pin) if pin.match(DIGITAL_REGEX) + return "EE" if pin == "EE" raise ArgumentError, "incorrect pin format" end diff --git a/test/board_test.rb b/test/board_test.rb index c83912c7..49470bb9 100644 --- a/test/board_test.rb +++ b/test/board_test.rb @@ -23,7 +23,7 @@ def test_starts_observing_txrx end def test_calls_handshake_on_txrx - mock = MiniTest::Mock.new.expect(:call, "actual ack value in txrx_mock.rb") + mock = MiniTest::Mock.new.expect(:call, "528,1024,14,20") txrx.stub(:handshake, mock) do Dino::Board.new(txrx) end @@ -42,11 +42,25 @@ def test_set_dac_and_analog_zero assert_equal 20, board.dac_zero assert_equal 14, board.analog_zero end - - def test_set_low_high_analog_high + + def test_set_low_high assert_equal 0, board.low assert_equal 1, board.high + end + + def test_analog_resolution assert_equal 255, board.analog_high + assert_equal 8, board.analog_resolution + + mock = MiniTest::Mock.new.expect(:call, nil, [Dino::Message.encode(command:96, value:10)]) + board.stub(:write, mock) do + board.analog_resolution = 10 + end + mock.verify + + assert_equal 10, board.analog_resolution + assert_equal 0, board.low + assert_equal 1023, board.analog_high end def test_add_remove_component @@ -69,19 +83,29 @@ def test_write mock.verify end - def test_update_passes_messages_to_components + def test_update_passes_messages_to_correct_components mock1 = MiniTest::Mock.new.expect(:update, nil, ["data"]) - 3.times { mock1.expect(:pin, 1) } - # This tests that lines are not split after the first colon delimiter. + 4.times { mock1.expect(:pin, 1) } + + # Make sure lines are split only on the first colon. + # Tests for string based pine names too. mock2 = MiniTest::Mock.new.expect(:update, nil, ["with:colon"]) - 3.times { mock2.expect(:pin, 2) } + 4.times { mock2.expect(:pin, 'A0') } + + # Special EEPROM mock. + mock3 = MiniTest::Mock.new.expect(:update, nil, ["bytes"]) + 4.times { mock3.expect(:pin, 'EE') } + board.add_component(mock1) board.add_component(mock2) + board.add_component(mock3) board.update("1:data") - board.update("2:with:colon") + board.update("14:with:colon") board.update("3:ignore") + board.update("EE:bytes") mock1.verify mock2.verify + mock3.verify end def test_convert_pin @@ -106,11 +130,4 @@ def test_incorrect_pin_formats board.instance_variable_set(:@dac_zero, nil) assert_raises(ArgumentError) { board.convert_pin('DAC1') } end - - def test_analog_resolution - board.analog_resolution = 10 - assert_equal 0, board.low - assert_equal 1023, board.analog_high - assert_equal 10, board.analog_resolution - end end From 9fffa9bab666b9c72d1c0acc28700870b5c3cc62 Mon Sep 17 00:00:00 2001 From: vickash Date: Wed, 8 Feb 2023 02:54:15 -0400 Subject: [PATCH 215/296] Fixes to multipin proxy building and register state management. Base component initializes with nil state. --- lib/dino/components/lcd.rb | 2 +- lib/dino/components/register/output.rb | 22 ++++++++----- lib/dino/components/register/shift_in.rb | 4 +++ lib/dino/components/register/shift_out.rb | 4 +++ lib/dino/components/setup/base.rb | 1 + lib/dino/components/setup/multi_pin.rb | 38 ++++++++++++++-------- lib/dino/components/softserial.rb | 2 +- lib/dino/components/ssd.rb | 2 +- lib/dino/tx_rx/base.rb | 2 +- test/components/register/shift_in_test.rb | 4 +++ test/components/register/shift_out_test.rb | 4 +++ test/components/setup/base_test.rb | 4 +++ test/components/setup/multi_pin_test.rb | 7 +++- 13 files changed, 69 insertions(+), 27 deletions(-) diff --git a/lib/dino/components/lcd.rb b/lib/dino/components/lcd.rb index 81778671..128c937a 100644 --- a/lib/dino/components/lcd.rb +++ b/lib/dino/components/lcd.rb @@ -22,7 +22,7 @@ class LCD # rows: 2 # ) # - def after_initialize(options) + def after_initialize(options={}) board.write Dino::Message.encode command: 10, value: 0, aux_message: encoded_pins @cols, @rows = options[:cols], options[:rows] board.write Dino::Message.encode command: 10, value: 1, aux_message: "#{@cols},#{@rows}" diff --git a/lib/dino/components/register/output.rb b/lib/dino/components/register/output.rb index 16f9bc96..64ca7adf 100644 --- a/lib/dino/components/register/output.rb +++ b/lib/dino/components/register/output.rb @@ -17,7 +17,7 @@ def after_initialize(options={}) # When used as a board proxy, store the state of each register # pin as a 0 or 1 in an array that is (@bytes * 8) long. Zero out to start. # - @state = Array.new(@bytes*8) {|i| 0} + @state = Array.new(@bytes*8) { 0 } write_state # @@ -39,9 +39,12 @@ def write # include Mixins::BoardProxy def digital_write(pin, value) - # puts @state.inspect - @state[pin] = value - delayed_write(@state) + state[pin] = value + delayed_write(state) + end + + def digital_read(pin) + state[pin] end # @@ -49,10 +52,11 @@ def digital_write(pin, value) # Lets us catch multiple changed bits, like when hosting an SSD. # include Mixins::Threaded - def delayed_write(state) + def delayed_write(old_state) threaded do sleep @write_delay - write_state if (state == @state) + # Keep delaying if state has changed. + write_state if (old_state == state) end end @@ -61,8 +65,10 @@ def delayed_write(state) # def write_state bytes = [] - @state.each_slice(8) do |slice| - bytes << slice.join("").to_i(2) + state.each_slice(8) do |slice| + # Convert nil to 0 to ensure bit order is consistent. + zeroed = slice.map { |bit| bit.to_i }.join.to_i(2) + bytes << zeroed end write(bytes) end diff --git a/lib/dino/components/register/shift_in.rb b/lib/dino/components/register/shift_in.rb index deec557d..84420bb7 100644 --- a/lib/dino/components/register/shift_in.rb +++ b/lib/dino/components/register/shift_in.rb @@ -20,6 +20,10 @@ def after_initialize(options={}) self.rising_clock = options[:rising_clock] bubble_callbacks end + + def pin + latch.pin + end # # Some registers use rising edges for clock signals. Unless we pull clock diff --git a/lib/dino/components/register/shift_out.rb b/lib/dino/components/register/shift_out.rb index b3de0fc4..fd545c89 100644 --- a/lib/dino/components/register/shift_out.rb +++ b/lib/dino/components/register/shift_out.rb @@ -18,6 +18,10 @@ class ShiftOut def write(*bytes) board.shift_write(latch.pin, data.pin, clock.pin, bytes) end + + def pin + latch.pin + end end end end diff --git a/lib/dino/components/setup/base.rb b/lib/dino/components/setup/base.rb index 802ef181..09f51df0 100644 --- a/lib/dino/components/setup/base.rb +++ b/lib/dino/components/setup/base.rb @@ -9,6 +9,7 @@ def state end def initialize(options={}) + @state = nil @state_mutex = Mutex.new initialize_board(options) initialize_pins(options) diff --git a/lib/dino/components/setup/multi_pin.rb b/lib/dino/components/setup/multi_pin.rb index 9cf1c440..301c65b5 100644 --- a/lib/dino/components/setup/multi_pin.rb +++ b/lib/dino/components/setup/multi_pin.rb @@ -57,13 +57,9 @@ def require_pins(*args) # '::proxy_pins' in the class definition. See RgbLed class for examples. # def proxy_pins(options={}) - if options[:optional] - options.reject! { |k| k == :optional } - else - options.reject! { |k| k == :optional } if (options[:optional] == false) - require_pins(*options.keys) - end - + require_pins(*options.keys) unless options[:optional] + options.reject! { |k| k == :optional } + proxied_pins = self.class_eval('@@proxied_pins') rescue {} proxied_pins.merge!(options) self.class_variable_set(:@@proxied_pins, proxied_pins) @@ -82,17 +78,31 @@ def initialize_pins(options={}) def validate_pins required_pins = self.class.class_eval('@@required_pins') rescue [] required_pins.each do |key| - raise ArgumentError, 'missing pins[:#{key}] pin' unless pins[key] + raise ArgumentError, "missing pins[:#{key}] pin" unless pins[key] end end def build_proxies - proxied_pins = self.class.class_eval('@@proxied_pins') rescue {} - proxied_pins.each_pair do |key, klass| - component = klass.new(board: board, pin: pins[key], pullup: pullups[key]) rescue nil - self.proxies[key] = component - instance_variable_set("@#{key}", component) - singleton_class.class_eval { attr_reader key } + proxy_classes = self.class.class_eval('@@proxied_pins') rescue {} + + # Build proxies for named pins once a pin number was given. + pins.each_pair do |pin_name, pin_number| + if proxy_classes[pin_name] + component = proxy_classes[pin_name].new(board: board, pin: pin_number, pullup: pullups[pin_name]) + self.proxies[pin_name] = component + instance_variable_set("@#{pin_name}", component) + singleton_class.class_eval { attr_reader pin_name } + else + # Fail silently when given pins that don't map to a proxy class + # Components can use pins without setting up a subcomponent + end + end + + # attr_reader nil for optional pins when not given. + proxy_classes.each_key do |pin_name| + unless self.proxies[pin_name] + singleton_class.class_eval { attr_reader pin_name } + end end end end diff --git a/lib/dino/components/softserial.rb b/lib/dino/components/softserial.rb index 1dd2db38..71aed3af 100644 --- a/lib/dino/components/softserial.rb +++ b/lib/dino/components/softserial.rb @@ -13,7 +13,7 @@ class SoftwareSerial # baud: 9600 # ) # - def after_initialize(options) + def after_initialize(options={}) self.baud = options[:baud] board.write Dino::Message.encode command: COMMAND, value: 0, aux_message: encoded_pins board.write Dino::Message.encode command: COMMAND, value: 1, aux_message: self.baud diff --git a/lib/dino/components/ssd.rb b/lib/dino/components/ssd.rb index c363cd21..df24d7ab 100644 --- a/lib/dino/components/ssd.rb +++ b/lib/dino/components/ssd.rb @@ -17,7 +17,7 @@ class SSD # ssd = SevenSegmentDisplay.new( # board: board, - # pins: {anode: 11, a: 12, b: 13, c: 3,d: 4,e: 5,f: 10,g: 9} + # pins: {anode: 11, a: 12, b: 13, c: 3,d: 4, e: 5, f: 10, g: 9} # ) def after_initialize(options={}) @segments = [a,b,c,d,e,f,g] diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index 1b861ba7..58252930 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -31,7 +31,7 @@ def io_reset def flush_read Timeout.timeout(5) { read until read == nil } rescue Timeout::Error - raise RxFlushTimeout "Cannot read from device, or device not running dino" + raise RxFlushTimeout, "Cannot read from device, or device not running dino" end def start_read diff --git a/test/components/register/shift_in_test.rb b/test/components/register/shift_in_test.rb index 68618214..8a83fa3e 100644 --- a/test/components/register/shift_in_test.rb +++ b/test/components/register/shift_in_test.rb @@ -18,6 +18,10 @@ def test_proxies assert_equal Dino::Components::Basic::DigitalInput, part.data.class assert_equal Dino::Components::Register::Select, part.latch.class end + + def test_identifies_with_latch_pin + assert_equal 8, part.pin + end def test_byte_length new_part = Dino::Components::Register::ShiftIn.new(options.merge(bytes: 2)) diff --git a/test/components/register/shift_out_test.rb b/test/components/register/shift_out_test.rb index 5ad79f5c..35969007 100644 --- a/test/components/register/shift_out_test.rb +++ b/test/components/register/shift_out_test.rb @@ -18,6 +18,10 @@ def test_proxies assert_equal Dino::Components::Basic::DigitalOutput, part.data.class assert_equal Dino::Components::Register::Select, part.latch.class end + + def test_identifies_with_latch_pin + assert_equal 8, part.pin + end def test_write # mock = MiniTest::Mock.new.expect :call, nil, ["21.8.1.#{[11,12,0,255,127].pack('C*')}\n"] diff --git a/test/components/setup/base_test.rb b/test/components/setup/base_test.rb index d16848c2..ff18b02e 100644 --- a/test/components/setup/base_test.rb +++ b/test/components/setup/base_test.rb @@ -17,4 +17,8 @@ def test_registers_with_board part = BaseComponent.new(board: board) assert_equal board.components, [part] end + + def test_start_with_nil_state + assert_nil BaseComponent.new(board: board).state + end end diff --git a/test/components/setup/multi_pin_test.rb b/test/components/setup/multi_pin_test.rb index 3c7b4bda..7c1a0734 100644 --- a/test/components/setup/multi_pin_test.rb +++ b/test/components/setup/multi_pin_test.rb @@ -32,7 +32,7 @@ def test_validate_pins MultiPinComponent.new board: board, pins: { one: 9, maybe: 11 } end assert_raises(ArgumentError) do - MultiPinComponent.new board: board, pins: { one: 10, maybe: 11 } + MultiPinComponent.new board: board, pins: { two: 10, maybe: 11 } end MultiPinComponent.new board: board, pins: { one: 9, two:10 } end @@ -41,6 +41,11 @@ def test_build_proxies assert_equal Dino::Components::Basic::DigitalOutput, part.proxies[:two].class assert_equal Dino::Components::Basic::DigitalInput, part.proxies[:maybe].class end + + def attr_reader_exists_for_optional_pins + part = MultiPinComponent.new board: board, pins: { one: 9, two:10 } + assert_nil part.maybe + end def test_proxy_reader_methods assert_equal part.proxies[:two], part.two From 4c175fd00cb40ced02b77a62281f3ae75fe094b6 Mon Sep 17 00:00:00 2001 From: vickash Date: Wed, 8 Feb 2023 12:58:11 -0400 Subject: [PATCH 216/296] Let SPI registers control MSBFIRST/LSBFIRST. Improved examples. --- examples/register/shift_in.rb | 12 ++++---- examples/register/shift_out.rb | 8 ++++-- examples/register/shift_ssd.rb | 16 ++++++----- examples/register/spi_combined.rb | 27 ++++++++---------- examples/register/spi_in.rb | 37 +++++++++++++------------ examples/register/spi_ssd.rb | 27 ++++++++++++------ lib/dino/components/register/spi_in.rb | 9 +++--- lib/dino/components/register/spi_out.rb | 15 +++++----- 8 files changed, 85 insertions(+), 66 deletions(-) diff --git a/examples/register/shift_in.rb b/examples/register/shift_in.rb index 03576c03..9d0520f8 100644 --- a/examples/register/shift_in.rb +++ b/examples/register/shift_in.rb @@ -1,11 +1,13 @@ # -# Example showing how to read an input shift register using Arduino's "shiftIn". +# Example using the Arduino shiftIn function to read data from a shift register. +# SPI is more efficient and may work with the same hardware, so use that if possible. +# See examples/spi_in.rb # -# The register implements #digital_read and other methods expected by Components, -# and makes its parallel pins addressable (zero index), so it can proxy the Board class. +# The register is a BoardProxy, and implements enough Board methods that +# DigitalInput components can use its pins directly. # -# The Button object is created by passing the register instead of the board, and -# the register's parallel output pin that the button is connected to. +# The Button object is created by using the register in place of board, and +# the register output pin that it's connected to. # # Note: rising_clock must be set to true if using TI CD4021B register or similar. # This should apply to any register which outputs on a rising clock edge. diff --git a/examples/register/shift_out.rb b/examples/register/shift_out.rb index 6729d0df..6f92daca 100644 --- a/examples/register/shift_out.rb +++ b/examples/register/shift_out.rb @@ -1,13 +1,17 @@ # -# Example showing how to set up an output shift register. +# Example using the Arduino shiftOut function to write data to a shift register. +# SPI is more efficient and may work with the same hardware, so use that if possible. +# # Multiple bytes may be written in one operation. +# Register instance stores state, and its size can be set by including :bytes when intiailizing. # require 'bundler/setup' require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) register = Dino::Components::Register::ShiftOut.new board: board, - pins: {latch: 9, data: 11, clock: 13} + pins: {latch: 9, data: 11, clock: 13} + # bytes: 1 # Write a single byte register.write(255) diff --git a/examples/register/shift_ssd.rb b/examples/register/shift_ssd.rb index 6bfd0e82..a6b80a6e 100644 --- a/examples/register/shift_ssd.rb +++ b/examples/register/shift_ssd.rb @@ -1,19 +1,21 @@ # -# Example showing how to use an output shift register to drive a seven segment display. +# Example using the Arduino shiftOut function to drive a seven segment display. +# SPI is more efficient and may work with the same hardware, so use that if possible. +# See examples/spi_ssd.rb # -# The register implements #digital_write and other methods expected by Components, -# and makes its parallel pins addressable (zero index), so it can proxy the Board class. -# -# The SSD object is created by passing the register instead of the board, and -# the registers's parallel pin number that each SSD pin is connected to. +# The register is a BoardProxy, and implements enough Board methods that +# DigitalOutput components can use its pins directly. # +# The SSD object is created by using the register in place of board, and +# the register output pins that the SSD is connected to. # require 'bundler/setup' require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) shift_register = Dino::Components::Register::ShiftOut.new board: board, - pins: {data: 11, clock: 13, latch: 9} + pins: {data: 11, clock: 13, latch: 9}, + # bytes: 1 ssd = Dino::Components::SSD.new board: shift_register, pins: { cathode: 0, a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7 } diff --git a/examples/register/spi_combined.rb b/examples/register/spi_combined.rb index 32bfa994..e981e52b 100644 --- a/examples/register/spi_combined.rb +++ b/examples/register/spi_combined.rb @@ -1,30 +1,27 @@ # -# Example showing how to use an output shift register to drive a seven segment display. -# -# The register implements #digital_write and other methods expected by Components, -# and makes its parallel pins addressable (zero index), so it can proxy the Board class. -# -# The SSD object is created by passing the register instead of the board, and -# the registers's parallel pin number that each SSD pin is connected to. -# +# Example showing 2 SPI devices on the same bus with different select pins. +# Combination of examples/spi_ssd.rb and examples/spi_in.rb # require 'bundler/setup' require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) -output_register = Dino::Components::Register::SPIOut.new board: board, - pin: 9, - frequency: 3000000, - spi_mode: 3 +output_register = Dino::Components::Register::SPIOut.new board: board, + pin: 9 + # frequency: 3000000, + # spi_mode: 0, + # bytes: 1 + # bit_order: :lsbfirst ssd = Dino::Components::SSD.new board: output_register, pins: { cathode: 0, a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7 } input_register = Dino::Components::Register::SPIIn.new board: board, - pin: 10, - bytes: 1, - spi_mode: 3, + pin: 8, + spi_mode: 0, frequency: 3000000 + # bytes: 1 + # bit_order: :lsbfirst button = Dino::Components::Button.new(pin: 0, board: input_register) diff --git a/examples/register/spi_in.rb b/examples/register/spi_in.rb index 6bacdd9c..bdf177d4 100644 --- a/examples/register/spi_in.rb +++ b/examples/register/spi_in.rb @@ -1,33 +1,34 @@ # -# Example showing how to read an input shift register using SPI. +# Example of input components connected to an input SPI shift register. # -# The register implements #digital_read and other methods expected by Components, -# and makes its parallel pins addressable (zero index), so it can proxy the Board class. +# The register is a BoardProxy, and implements enough Board methods that +# DigitalInput components can be attached to its pins. # -# The Button object is created by passing the register instead of the board, and -# the register's parallel output pin that the button is connected to. +# The Button object is created by using the register in place of board, and +# the register output pin that it's connected to. +# +# The board pin connected to the register's select/latch pin is required to initialize. +# Each device connected to the SPI bus needs a unique select pin on the board. +# +# Clock and data pins do not need to be specified when using SPI, since they are +# predetermined on the board and shared by all SPI devices, but they must be connected. +# Pin mapping varies depending on the board. +# A reference can be found here: https://www.arduino.cc/en/Reference/SPI +# +# SPI output (MISO) and SPI clock (SCLK) map to pins 12 and 13 respectively on the Arduino UNO. # require 'bundler/setup' require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) -# -# Pin 10 is default slave select on Arduino UNO. Connect to register "latch" input. -# Register data and clock pins go to 12 (MISO) and 13 respectively on the Arduino UNO. -# -# Clock and data pins do not need to be given when using SPI, since they are -# predetermined based on the board you are using, and dealt with by the library. -# But they still must be connected. The exact pins vary depending on the board, -# and a reference can be found here: https://www.arduino.cc/en/Reference/SPI -# # SPI mode and frequency are specific to a TI CD4021B register. Change as needed. -# shift_register = Dino::Components::Register::SPIIn.new board: board, - pin: 10, - bytes: 1, - spi_mode: 3, + pin: 8, + spi_mode: 0, frequency: 3000000 + # bytes: 1 + # bit_order: :lsbfirst button = Dino::Components::Button.new(pin: 0, board: shift_register) diff --git a/examples/register/spi_ssd.rb b/examples/register/spi_ssd.rb index 079e0915..1d01a076 100644 --- a/examples/register/spi_ssd.rb +++ b/examples/register/spi_ssd.rb @@ -1,21 +1,32 @@ # -# Example showing how to use an output shift register to drive a seven segment display. +# Example of an output SPI shift register driving a seven segment display. # -# The register implements #digital_write and other methods expected by Components, -# and makes its parallel pins addressable (zero index), so it can proxy the Board class. +# The register is a BoardProxy, and implements enough Board methods that +# DigitalOutput components can use its pins directly. # -# The SSD object is created by passing the register instead of the board, and -# the registers's parallel pin number that each SSD pin is connected to. +# The SSD object is created by using the register in place of board, and +# the register output pins that the SSD is connected to. # +# The board pin connected to the register's select/latch pin is required to initialize. +# Each device connected to the SPI bus needs a unique select pin on the board. +# +# Clock and data pins do not need to be specified when using SPI, since they are +# predetermined on the board and shared by all SPI devices, but they must be connected. +# Pin mapping varies depending on the board. +# A reference can be found here: https://www.arduino.cc/en/Reference/SPI +# +# SPI output (MISO) and SPI clock (SCLK) map to pins 12 and 13 respectively on the Arduino UNO. # require 'bundler/setup' require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) shift_register = Dino::Components::Register::SPIOut.new board: board, - pin: 9, - frequency: 3000000, - spi_mode: 3 + pin: 9 + # frequency: 3000000, + # spi_mode: 0, + # bytes: 1 + # bit_order: :lsbfirst ssd = Dino::Components::SSD.new board: shift_register, pins: { cathode: 0, a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7 } diff --git a/lib/dino/components/register/spi_in.rb b/lib/dino/components/register/spi_in.rb index 36298543..d3c1d966 100644 --- a/lib/dino/components/register/spi_in.rb +++ b/lib/dino/components/register/spi_in.rb @@ -2,21 +2,22 @@ module Dino module Components module Register # - # Model SPI registers as single pin. Data comes back on the select pin, - # so just inherit from Select. + # Model SPI registers as single pin. MOSI and MISO are shared + # and predetermined, so we only need to care about the select pin. # - # options = {board: my_board, pin: slave_select_pin} + # options = {board: my_board, pin: register_select_pin} # class SPIIn < Select include Input - attr_reader :spi_mode, :frequency + attr_reader :spi_mode, :frequency, :bit_order def after_initialize(options={}) super(options) if defined?(super) @spi_mode = options[:spi_mode] || 0 @frequency = options[:frequency] || 3000000 + @bit_order = options[:bit_order] || :lsbfirst end def read diff --git a/lib/dino/components/register/spi_out.rb b/lib/dino/components/register/spi_out.rb index 14cf87af..8d5922a2 100644 --- a/lib/dino/components/register/spi_out.rb +++ b/lib/dino/components/register/spi_out.rb @@ -2,25 +2,26 @@ module Dino module Components module Register # - # Model SPI registers as single pin. Data comes back on the select pin, - # so just inherit from Select. + # Model SPI registers as single pin. MOSI and MISO are shared + # and predetermined, so we only need to care about the select pin. # - # options = {board: my_board, pin: slave_select_pin} + # options = {board: my_board, pin: register_select_pin} # class SPIOut < Select include Output - attr_reader :spi_mode, :frequency + attr_reader :spi_mode, :frequency, :bit_order def after_initialize(options={}) + super(options) if defined?(super) + @spi_mode = options[:spi_mode] || 0 @frequency = options[:frequency] || 3000000 - - super(options) if defined?(super) + @bit_order = options[:bit_order] || :lsbfirst end def write(*bytes) - board.spi_transfer(pin, mode: spi_mode, frequency: frequency, write: bytes.flatten) + board.spi_transfer(pin, mode: spi_mode, frequency: frequency, write: bytes.flatten, bit_order: bit_order) end end end From 278f8a521ffeee323bbcb9f62e9cfb77fe3bc62c Mon Sep 17 00:00:00 2001 From: vickash Date: Thu, 9 Feb 2023 13:38:38 -0400 Subject: [PATCH 217/296] Don't fix external OneWire library since it's no longer used --- lib/dino_cli/generator.rb | 3 --- lib/dino_cli/packages.rb | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/dino_cli/generator.rb b/lib/dino_cli/generator.rb index af02ecb9..a386aa34 100644 --- a/lib/dino_cli/generator.rb +++ b/lib/dino_cli/generator.rb @@ -40,9 +40,6 @@ def read if f.match /\Avendor/ directive = @packages[k][:directive] contents = "#include \"DinoDefines.h\"\n#ifdef #{directive}\n" << contents << "\n#endif\n" - - # Hack to workaround /util folder includes in latest OneWire library. - contents.gsub!("util/OneWire_direct_gpio.h", "OneWire_direct_gpio.h") if f.match /OneWire/ end { path: f, contents: contents } end diff --git a/lib/dino_cli/packages.rb b/lib/dino_cli/packages.rb index 53b6eac5..bb4238e6 100644 --- a/lib/dino_cli/packages.rb +++ b/lib/dino_cli/packages.rb @@ -84,7 +84,7 @@ class DinoCLI::Generator ] }, one_wire: { - description: "OneWire bus support (Just DS18B20 for now)", + description: "OneWire bus support", directive: "DINO_ONE_WIRE", files: [ "lib/DinoOneWire.cpp", From 59674ae7feb342110387c76a96bdf9a33711beb7 Mon Sep 17 00:00:00 2001 From: vickash Date: Thu, 9 Feb 2023 17:43:17 -0400 Subject: [PATCH 218/296] Add Board#eeprom convenience method --- examples/eeprom/eeprom.rb | 5 +++-- lib/dino/board/base.rb | 4 ++++ test/board_test.rb | 8 ++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/examples/eeprom/eeprom.rb b/examples/eeprom/eeprom.rb index 959d11bc..09821f3a 100644 --- a/examples/eeprom/eeprom.rb +++ b/examples/eeprom/eeprom.rb @@ -7,8 +7,9 @@ board = Dino::Board.new(Dino::TxRx::Serial.new) -# Initialization automatically gets all EEPROM data from thee board. -eeprom = Dino::Components::Basic::BoardEEPROM.new(board: board) +# Initialization automatically gets all EEPROM data from the board. +# eeprom = Dino::Components::Basic::BoardEEPROM.new(board: board) +eeprom = board.eeprom # EEPROM size reported by the board. puts "EEPROM Size: #{eeprom.length} bytes" diff --git a/lib/dino/board/base.rb b/lib/dino/board/base.rb index 9c4531cc..84656af3 100644 --- a/lib/dino/board/base.rb +++ b/lib/dino/board/base.rb @@ -36,6 +36,10 @@ def analog_resolution=(value) def aux_limit @aux_limit ||= 39 end + + def eeprom + @eeprom ||= Components::Basic::BoardEEPROM.new(board: self) + end def write(msg) @io.write(msg) diff --git a/test/board_test.rb b/test/board_test.rb index 49470bb9..d5c72110 100644 --- a/test/board_test.rb +++ b/test/board_test.rb @@ -62,6 +62,14 @@ def test_analog_resolution assert_equal 0, board.low assert_equal 1023, board.analog_high end + + def test_eeprom + mock = MiniTest::Mock.new.expect(:call, "test eeprom", [], board: board) + Dino::Components::Basic::BoardEEPROM.stub(:new, mock) do + board.eeprom + end + mock.verify + end def test_add_remove_component mock = MiniTest::Mock.new From 1b833e7098dc7f7ea07cb5e63d7de5862fc33e66 Mon Sep 17 00:00:00 2001 From: vickash Date: Fri, 10 Feb 2023 10:33:02 -0400 Subject: [PATCH 219/296] Store listener dividers correctly on board. Store DigitalInput state as int, not string --- lib/dino/components/basic/analog_input.rb | 2 +- lib/dino/components/basic/digital_input.rb | 2 +- src/lib/DinoCoreIO.cpp | 4 ++-- test/components/basic/digital_input_test.rb | 6 ++++++ 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/dino/components/basic/analog_input.rb b/lib/dino/components/basic/analog_input.rb index 86b3c54b..78e32127 100644 --- a/lib/dino/components/basic/analog_input.rb +++ b/lib/dino/components/basic/analog_input.rb @@ -13,7 +13,7 @@ def _read end def _listen(divider=16) - @divider = divider || 16 + @divider = divider board.analog_listen(pin, @divider) end end diff --git a/lib/dino/components/basic/digital_input.rb b/lib/dino/components/basic/digital_input.rb index 3ef4558a..77cf3b44 100644 --- a/lib/dino/components/basic/digital_input.rb +++ b/lib/dino/components/basic/digital_input.rb @@ -18,7 +18,7 @@ def _read end def _listen(divider=4) - @divider = divider || 4 + @divider = divider board.digital_listen(pin, @divider) end diff --git a/src/lib/DinoCoreIO.cpp b/src/lib/DinoCoreIO.cpp index 1b302f3b..2b89950a 100644 --- a/src/lib/DinoCoreIO.cpp +++ b/src/lib/DinoCoreIO.cpp @@ -98,7 +98,7 @@ void Dino::setListener(byte p, boolean enabled, byte analog, byte exponent, bool if (enabled) settingMask = settingMask | B10000000; if (analog) settingMask = settingMask | B01000000; if (local) settingMask = settingMask | B00010000; - settingMask = settingMask | dividerMap[exponent]; + settingMask = settingMask | exponent; #ifdef debug Serial.print("setlistener, pin:"); @@ -151,7 +151,7 @@ void Dino::updateCoreListeners(byte tickCount){ // Check if active. if (bitRead(listeners[i][0], 7) == 1){ // Check if to update it on this tick. - byte exponent = 0b111 & listeners[i][0]; + byte exponent = (listeners[i][0] << 5) >> 5; byte divider = dividerMap[exponent]; if(tickCount % divider == 0){ // Check if digital or analog. diff --git a/test/components/basic/digital_input_test.rb b/test/components/basic/digital_input_test.rb index e630e4cb..c25b6761 100644 --- a/test/components/basic/digital_input_test.rb +++ b/test/components/basic/digital_input_test.rb @@ -16,6 +16,12 @@ def test_start_listening_immediately end mock.verify end + + def test_converts_to_integer + part + part.update("1") + assert_equal part.state, 1 + end def test__read mock = MiniTest::Mock.new.expect :call, nil, [14] From 4e841081adbcd108400ed5782d19ea9be99bc29f Mon Sep 17 00:00:00 2001 From: vickash Date: Fri, 10 Feb 2023 13:10:33 -0400 Subject: [PATCH 220/296] Listeners still wrong with bitshift and (). Bitmasks work instead. --- lib/dino/components/basic/digital_input.rb | 4 ++++ lib/dino/components/setup/base.rb | 8 ++++---- src/lib/DinoCoreIO.cpp | 3 ++- src/lib/DinoSPI.cpp | 4 +--- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/dino/components/basic/digital_input.rb b/lib/dino/components/basic/digital_input.rb index 77cf3b44..2e3c4343 100644 --- a/lib/dino/components/basic/digital_input.rb +++ b/lib/dino/components/basic/digital_input.rb @@ -33,6 +33,10 @@ def on_low(&block) block.call(data) if data.to_i == board.low end end + + def pre_callback_filter(data) + data.to_i + end def high?; state == board.high end def low?; state == board.low end diff --git a/lib/dino/components/setup/base.rb b/lib/dino/components/setup/base.rb index 09f51df0..06d7b161 100644 --- a/lib/dino/components/setup/base.rb +++ b/lib/dino/components/setup/base.rb @@ -4,10 +4,6 @@ module Setup module Base attr_reader :board - def state - @state_mutex.synchronize { @state } - end - def initialize(options={}) @state = nil @state_mutex = Mutex.new @@ -16,6 +12,10 @@ def initialize(options={}) register after_initialize(options) end + + def state + @state_mutex.synchronize { @state } + end protected diff --git a/src/lib/DinoCoreIO.cpp b/src/lib/DinoCoreIO.cpp index 2b89950a..2c6c75ce 100644 --- a/src/lib/DinoCoreIO.cpp +++ b/src/lib/DinoCoreIO.cpp @@ -151,7 +151,8 @@ void Dino::updateCoreListeners(byte tickCount){ // Check if active. if (bitRead(listeners[i][0], 7) == 1){ // Check if to update it on this tick. - byte exponent = (listeners[i][0] << 5) >> 5; + // Divider exponent is last 3 bits of settings. + byte exponent = listeners[i][0] & 0B00000111; byte divider = dividerMap[exponent]; if(tickCount % divider == 0){ // Check if digital or analog. diff --git a/src/lib/DinoSPI.cpp b/src/lib/DinoSPI.cpp index 5dafe09e..092983de 100644 --- a/src/lib/DinoSPI.cpp +++ b/src/lib/DinoSPI.cpp @@ -22,9 +22,7 @@ void Dino::spiBegin(byte settings, uint32_t clockRate){ SPI.begin(); // SPI mode is the lowest 4 bits of settings. - byte mode = settings; - mode << 4; - mode >> 4; + byte mode = settings & 0B00001111; // Bit 7 of settings toggles MSBFIRST. SPI.beginTransaction(SPISettings(clockRate, bitRead(settings, 7), mode)); From a3736087c55f7429bdb0982dc29bac416512da6b Mon Sep 17 00:00:00 2001 From: vickash Date: Fri, 10 Feb 2023 13:13:52 -0400 Subject: [PATCH 221/296] Improve RotaryEncoder so #steps and #angle give state. Updated examples. --- examples/rotary_encoder/rotary_encoder.rb | 4 +- .../rotary_encoder_mac_volume.rb | 9 +- lib/dino/components/rotary_encoder.rb | 90 +++++++++++-------- 3 files changed, 61 insertions(+), 42 deletions(-) diff --git a/examples/rotary_encoder/rotary_encoder.rb b/examples/rotary_encoder/rotary_encoder.rb index e5a26af3..2f97afb1 100644 --- a/examples/rotary_encoder/rotary_encoder.rb +++ b/examples/rotary_encoder/rotary_encoder.rb @@ -10,8 +10,8 @@ board = Dino::Board.new(Dino::TxRx::Serial.new) encoder = Dino::Components::RotaryEncoder.new board: board, pins:{ clock: 4, data: 5 }, - divider: 1, # (default) read approx every divider ms - steps: 30 # (default) steps / revolution + divider: 1, # (default) read approx every divider ms + degrees_per_step: 12 # (default) angular degrees the encoder moves between steps encoder.add_callback do |data| puts "Encoder position: #{data[:position]}°" diff --git a/examples/rotary_encoder/rotary_encoder_mac_volume.rb b/examples/rotary_encoder/rotary_encoder_mac_volume.rb index 160e60f2..b0df871a 100644 --- a/examples/rotary_encoder/rotary_encoder_mac_volume.rb +++ b/examples/rotary_encoder/rotary_encoder_mac_volume.rb @@ -7,8 +7,8 @@ board = Dino::Board.new(Dino::TxRx::Serial.new) encoder = Dino::Components::RotaryEncoder.new board: board, pins:{ clock: 4, data: 5 }, - divider: 1, # (default) read approx every divider ms - steps: 30 # (default) steps / revolution + divider: 1, # (default) read approx every divider ms + degrees_per_step: 12 # (default) angular degrees the encoder moves between steps # Set up a pseudo terminal with osascript (AppleScript) in interactive mode. # Calling a separate script each update is too slow. @@ -38,15 +38,14 @@ def set(value) # Some values from 0-100 aren't applied so the encoder gets stuck in a # small range if we write every step. Track when a step has no effect # and apply it later with this. -unused_steps = 0 encoder.add_callback do |update| - value = volume.get + update[:change] + unused_steps + # Increase by 2% for every step so it responds faster. + value = (volume.get + (update[:change] * 2)) value = 0 if value < 0 value = 100 if value > 100 volume.set(value) current_volume = volume.get - unused_steps = value - current_volume puts "Current volume: #{current_volume}%" end diff --git a/lib/dino/components/rotary_encoder.rb b/lib/dino/components/rotary_encoder.rb index 14d03aff..9ea7764e 100644 --- a/lib/dino/components/rotary_encoder.rb +++ b/lib/dino/components/rotary_encoder.rb @@ -4,57 +4,77 @@ class RotaryEncoder include Setup::MultiPin include Mixins::Callbacks - proxy_pins data: Basic::DigitalInput, - clock: Basic::DigitalInput - - attr_reader :position, :steps, :degrees_per_step - alias :position :state - + proxy_pins clock: Basic::DigitalInput, + data: Basic::DigitalInput + + attr_accessor :degrees_per_step + def after_initialize(options={}) super(options) - + + self.degrees_per_step = options[:degrees_per_step] || 12 + reset + + # DigitalInputs listen with default divider automatically. Override here. divider = options[:divider] || 1 clock.listen(divider) data.listen(divider) - @steps = options[:steps] || 30 - @degrees_per_step = (360 / steps).to_f - @state = 0.0 + observe_pins + end + + def angle + state[:angle] + end - start + def steps + state[:steps] + end + + def reset + self.state = {steps: 0, angle: 0} end + + private - def start - clock.add_callback do |clock_state| - (data.state == clock_state) ? self.update(-1) : self.update(1) + def observe_pins + # + # This is a quirk of how listeners work. + # When observing the pins, attach a callback to the higher numbered pin, + # then read state from the lower. If not, direction will be reversed. + # + if board.convert_pin(clock.pin) > board.convert_pin(data.pin) + trailing = clock + leading = data + else + trailing = data + leading = clock + end + + trailing.add_callback do |trailing_state| + (leading.state == trailing_state) ? self.update(1) : self.update(-1) end end - - # - # Callbacks#update calls these before and after callbacks respectively. + # - # Take data (+/- 1 step change) and calculate new position (state) in degrees. - # Leave old position in @state for now, so callbacks can compare to it. + # Take data (+/- 1 step change) and calculate new state. + # Return a hash with the new :steps and :angle. Pass through raw + # value in :change, so callbacks can use any of these. # - # Return a hash with the new :position and pass through :change, overriding - # the data param we took, which would have passed directly to callbacks. - # Callbacks can use either :position (in degrees) or :change (in steps). - # - def pre_callback_filter(data) - { change: data, position: (state + (data * degrees_per_step)) % 360 } + def pre_callback_filter(step) + # Copy old state through the mutex wrapped reader. + temp_state = state.dup + temp_state[:change] = step + temp_state[:steps] = temp_state[:steps] + step + temp_state[:angle] = (temp_state[:angle] + (step * degrees_per_step)) + temp_state end + # - # Callbacks run now, receiving only the value of #pre_callback_filter - # - # After callbacks, set @state to the position calculated earlier. - # This method also receives the result of #pre_callback_filter. + # After callbacks, set state to the hash from before, except change. # - def update_self(data) - @state = data[:position] - end - - def reset_position - @callbacks_mutex.synchronize { @state = 0 } + def update_self(new_state) + self.state = new_state.except(:change) end end end From 15be763782efdf3c3c002dfd8f4f88b9c888fa16 Mon Sep 17 00:00:00 2001 From: vickash Date: Fri, 10 Feb 2023 13:14:35 -0400 Subject: [PATCH 222/296] #convert_pin can return nil for parts not directly using pins --- lib/dino/board/default.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/dino/board/default.rb b/lib/dino/board/default.rb index c7358539..271e4fac 100644 --- a/lib/dino/board/default.rb +++ b/lib/dino/board/default.rb @@ -14,12 +14,13 @@ class Default < Base DAC_REGEX = /\A(dac)\d+\z/i def convert_pin(pin) + return nil if pin == nil pin = pin.to_s return pin.to_i if pin.match(DIGITAL_REGEX) return analog_pin_to_i(pin) if pin.match(ANALOG_REGEX) return dac_pin_to_i(pin) if pin.match(DAC_REGEX) return "EE" if pin == "EE" - raise ArgumentError, "incorrect pin format" + raise ArgumentError, "incorrect pin format: #{pin.inspect}" end def analog_pin_to_i(pin) From 0bb2c78a1bb5789ba0a64037827dfc367a20382c Mon Sep 17 00:00:00 2001 From: vickash Date: Fri, 10 Feb 2023 13:38:11 -0400 Subject: [PATCH 223/296] Use steps per rev instead of degrees per step to set up RotaryEncoder --- examples/rotary_encoder/rotary_encoder.rb | 9 ++++----- examples/rotary_encoder/rotary_encoder_mac_volume.rb | 8 ++------ lib/dino/components/rotary_encoder.rb | 4 ++-- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/examples/rotary_encoder/rotary_encoder.rb b/examples/rotary_encoder/rotary_encoder.rb index 2f97afb1..0fef0e93 100644 --- a/examples/rotary_encoder/rotary_encoder.rb +++ b/examples/rotary_encoder/rotary_encoder.rb @@ -10,12 +10,11 @@ board = Dino::Board.new(Dino::TxRx::Serial.new) encoder = Dino::Components::RotaryEncoder.new board: board, pins:{ clock: 4, data: 5 }, - divider: 1, # (default) read approx every divider ms - degrees_per_step: 12 # (default) angular degrees the encoder moves between steps + divider: 1, # (default) read approx every divider ms + steps_per_revolution: 30 # (default) -encoder.add_callback do |data| - puts "Encoder position: #{data[:position]}°" - puts "Encoder change: #{data[:change]}" +encoder.add_callback do |state| + puts "Encoder moved #{state[:change]} steps | CW step count: #{state[:steps]} | Current angle: #{state[:angle]}\xC2\xB0" end sleep diff --git a/examples/rotary_encoder/rotary_encoder_mac_volume.rb b/examples/rotary_encoder/rotary_encoder_mac_volume.rb index b0df871a..5612ab2c 100644 --- a/examples/rotary_encoder/rotary_encoder_mac_volume.rb +++ b/examples/rotary_encoder/rotary_encoder_mac_volume.rb @@ -7,8 +7,8 @@ board = Dino::Board.new(Dino::TxRx::Serial.new) encoder = Dino::Components::RotaryEncoder.new board: board, pins:{ clock: 4, data: 5 }, - divider: 1, # (default) read approx every divider ms - degrees_per_step: 12 # (default) angular degrees the encoder moves between steps + divider: 1, # (default) read approx every divider ms + steps_per_revolution: 30 # (default) # Set up a pseudo terminal with osascript (AppleScript) in interactive mode. # Calling a separate script each update is too slow. @@ -35,10 +35,6 @@ def set(value) volume = AppleVolumeWrapper.new puts "Current volume: #{volume.get}%" -# Some values from 0-100 aren't applied so the encoder gets stuck in a -# small range if we write every step. Track when a step has no effect -# and apply it later with this. - encoder.add_callback do |update| # Increase by 2% for every step so it responds faster. value = (volume.get + (update[:change] * 2)) diff --git a/lib/dino/components/rotary_encoder.rb b/lib/dino/components/rotary_encoder.rb index 9ea7764e..4b93ac45 100644 --- a/lib/dino/components/rotary_encoder.rb +++ b/lib/dino/components/rotary_encoder.rb @@ -12,7 +12,7 @@ class RotaryEncoder def after_initialize(options={}) super(options) - self.degrees_per_step = options[:degrees_per_step] || 12 + self.degrees_per_step = (360 / options[:steps_per_revolution]) || 30 reset # DigitalInputs listen with default divider automatically. Override here. @@ -66,7 +66,7 @@ def pre_callback_filter(step) temp_state = state.dup temp_state[:change] = step temp_state[:steps] = temp_state[:steps] + step - temp_state[:angle] = (temp_state[:angle] + (step * degrees_per_step)) + temp_state[:angle] = (temp_state[:angle] + (step * degrees_per_step)) % 360 temp_state end From 43638fec8b9296e39a0fed72e884493f1f643c6f Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 11 Feb 2023 11:17:21 -0400 Subject: [PATCH 224/296] Correctly set default steps per rev for rotary encoders --- lib/dino/components/rotary_encoder.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/dino/components/rotary_encoder.rb b/lib/dino/components/rotary_encoder.rb index 4b93ac45..477d369d 100644 --- a/lib/dino/components/rotary_encoder.rb +++ b/lib/dino/components/rotary_encoder.rb @@ -12,7 +12,11 @@ class RotaryEncoder def after_initialize(options={}) super(options) - self.degrees_per_step = (360 / options[:steps_per_revolution]) || 30 + if options[:steps_per_revolution] + self.degrees_per_step = (360 / options[:steps_per_revolution]) + else + self.degrees_per_step = 30 + end reset # DigitalInputs listen with default divider automatically. Override here. From b690d210bf53a955b4266d4435dac3ed3a3529a5 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 11 Feb 2023 12:26:07 -0400 Subject: [PATCH 225/296] Add BoardEEPROM support for the ESP8266 --- .../{eeprom/eeprom.rb => board_eeprom.rb} | 0 src/lib/Dino.cpp | 8 +++++- src/lib/Dino.h | 5 ++++ src/lib/DinoEEPROM.cpp | 25 +++++++++++++++---- 4 files changed, 32 insertions(+), 6 deletions(-) rename examples/{eeprom/eeprom.rb => board_eeprom.rb} (100%) diff --git a/examples/eeprom/eeprom.rb b/examples/board_eeprom.rb similarity index 100% rename from examples/eeprom/eeprom.rb rename to examples/board_eeprom.rb diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 72db040c..512697fb 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -221,7 +221,13 @@ void Dino::handshake() { stream->print("ACK:"); stream->print(AUX_SIZE); stream->print(','); - stream->print(EEPROM.length()); + // Send the defined length for ESP8266 EEPROM and initialize later. + // Read the value and send that for other boards. + #ifdef ESP8266 + stream->print(ESP8266_EEPROM_LENGTH); + #else + stream->print(EEPROM.length()); + #endif stream->print(','); stream->print(A0); #if defined(__SAM3X8E__) diff --git a/src/lib/Dino.h b/src/lib/Dino.h index 4039592d..ab4d18d0 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -16,6 +16,11 @@ # define PIN_COUNT 22 #endif +// Use the maximum length for the ESP8266 EEPROM. +#ifdef ESP8266 +# define ESP8266_EEPROM_LENGTH 4096 +#endif + class Dino { public: Dino(); diff --git a/src/lib/DinoEEPROM.cpp b/src/lib/DinoEEPROM.cpp index 63308c6b..0693b345 100644 --- a/src/lib/DinoEEPROM.cpp +++ b/src/lib/DinoEEPROM.cpp @@ -12,7 +12,11 @@ // void Dino::eepromRead(){ if (val > 0) { - uint16_t startAddress = (uint16_t)auxMsg[1] << 8 | auxMsg[0]; + #ifdef ESP8266 + EEPROM.begin(ESP8266_EEPROM_LENGTH); + #endif + + uint16_t startAddress = ((uint16_t)auxMsg[1] << 8) | auxMsg[0]; // Stream read bytes as if coming from a pin named 'EE'. stream->print("EE"); @@ -24,6 +28,10 @@ void Dino::eepromRead(){ stream->print(EEPROM.read(startAddress + i)); stream->print((i+1 == val) ? '\n' : ','); } + + #ifdef ESP8266 + EEPROM.end(); + #endif } } @@ -37,12 +45,19 @@ void Dino::eepromRead(){ // void Dino::eepromWrite(){ if (val > 0) { - uint16_t startAddress = (uint16_t)auxMsg[1] << 8 | auxMsg[0]; + #ifdef ESP8266 + EEPROM.begin(ESP8266_EEPROM_LENGTH); + #endif + + uint16_t startAddress = ((uint16_t)auxMsg[1] << 8) | auxMsg[0]; for (byte i = 0; (i < val); i++) { - if(EEPROM.read(startAddress + i) != auxMsg[2+i]) { - EEPROM.write(startAddress + i, auxMsg[2+i]); - } + EEPROM.write(startAddress + i, auxMsg[2+i]); } + + #ifdef ESP8266 + EEPROM.end(); + EEPROM.commit(); + #endif } } From 11c8e568be0d3ea69b7a9cd08bed9fbff93417fd Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 11 Feb 2023 13:13:15 -0400 Subject: [PATCH 226/296] Change the default config for the mega168 sketch --- lib/dino_cli/targets.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dino_cli/targets.rb b/lib/dino_cli/targets.rb index 1e9d4ea8..5d6cc1bd 100644 --- a/lib/dino_cli/targets.rb +++ b/lib/dino_cli/targets.rb @@ -11,7 +11,7 @@ class DinoCLI::Generator core: [:core], # Specific features for the old mega168 chips. - mega168: [:core, :servo, :one_wire, :ir_out, :tone, :spi, :i2c], + mega168: [:core, :servo, :shift,:tone, :spi, :i2c], # ARM includes everytyhing except specific incompatibilities. arm: STANDARD_PACKAGES - [:serial, :tone, :ir_out], From 63f0b414d6363c2e68894c4f4177b7c2e3f496fd Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 11 Feb 2023 16:46:14 -0400 Subject: [PATCH 227/296] Remove #callbacks= and test callback modification happening in mutex --- lib/dino/components/mixins/callbacks.rb | 9 ++++++--- test/components/mixins/callbacks_test.rb | 13 +++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/dino/components/mixins/callbacks.rb b/lib/dino/components/mixins/callbacks.rb index 13b84f27..24098080 100644 --- a/lib/dino/components/mixins/callbacks.rb +++ b/lib/dino/components/mixins/callbacks.rb @@ -3,13 +3,16 @@ module Components module Mixins module Callbacks attr_reader :callback_mutex, :callbacks - attr_writer :callbacks def after_initialize(options={}) super(options) @callbacks = {} @callback_mutex = Mutex.new end + + def callbacks + callback_mutex.synchronize { @callbacks } + end def add_callback(key=:persistent, &block) callback_mutex.synchronize do @@ -31,13 +34,13 @@ def update(data) data = pre_callback_filter(data) callback_mutex.synchronize do - callbacks.each_value do |array| + @callbacks.each_value do |array| array.each do |callback| callback.call(data) end end # Remove special :read callback before unlocking. - callbacks.delete(:read) + @callbacks.delete(:read) end update_self(data) diff --git a/test/components/mixins/callbacks_test.rb b/test/components/mixins/callbacks_test.rb index 0d304eca..61dca871 100644 --- a/test/components/mixins/callbacks_test.rb +++ b/test/components/mixins/callbacks_test.rb @@ -18,6 +18,19 @@ def part @part ||= CallbackComponent.new(board: board, pin: 1) end + def test_callback_mutex + callback = Proc.new{} + mock = MiniTest::Mock.new + 3.times {mock.expect(:call, nil)} + + part.callback_mutex.stub(:synchronize, mock) do + part.callbacks + part.add_callback(:key, &callback) + part.remove_callbacks(:key) + end + mock.verify + end + def test_add_callback callback = Proc.new{} part.add_callback(&callback) From 7a86b227580a2dd05a5c9c8b78a2842ae09cd3e2 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 11 Feb 2023 17:57:24 -0400 Subject: [PATCH 228/296] Exlude EEPROM and use MSBFIRST/LSBFIRST on the Due so it compiles --- src/lib/Dino.cpp | 10 ++++++++-- src/lib/Dino.h | 1 - src/lib/DinoDefines.h | 6 ++++++ src/lib/DinoEEPROM.cpp | 2 ++ src/lib/DinoSPI.cpp | 8 +++++++- 5 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 512697fb..3ebb3251 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -108,9 +108,11 @@ void Dino::process() { case 4: aRead (pin); break; case 5: setListener (pin, val, auxMsg[0], auxMsg[1], false); break; + #ifdef EEPROM_PRESENT // Implemented in DinoEEPROM.cpp case 6: eepromRead (); break; case 7: eepromWrite (); break; + #endif // Implemented in DinoServo.cpp #ifdef DINO_SERVO @@ -221,13 +223,17 @@ void Dino::handshake() { stream->print("ACK:"); stream->print(AUX_SIZE); stream->print(','); + // Send the defined length for ESP8266 EEPROM and initialize later. - // Read the value and send that for other boards. + // Read the value and send that for other boards, except the Due. #ifdef ESP8266 stream->print(ESP8266_EEPROM_LENGTH); + #elif defined(EEPROM_PRESENT) + stream->print(EEPROM.length()); #else - stream->print(EEPROM.length()); + stream->print('0'); #endif + stream->print(','); stream->print(A0); #if defined(__SAM3X8E__) diff --git a/src/lib/Dino.h b/src/lib/Dino.h index ab4d18d0..badfe5b6 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -4,7 +4,6 @@ #ifndef Dino_h #define Dino_h #include -#include #include "DinoDefines.h" // Figure out how many pins our hardware has. diff --git a/src/lib/DinoDefines.h b/src/lib/DinoDefines.h index daca06b6..8d66ac8c 100644 --- a/src/lib/DinoDefines.h +++ b/src/lib/DinoDefines.h @@ -14,3 +14,9 @@ // #define DINO_SHIFT // #define DINO_SPI // #define DINO_I2C + +// No EEPROM on the Due. +#ifndef __SAM3X8E__ + #define EEPROM_PRESENT + #include +#endif \ No newline at end of file diff --git a/src/lib/DinoEEPROM.cpp b/src/lib/DinoEEPROM.cpp index 0693b345..dd898c9f 100644 --- a/src/lib/DinoEEPROM.cpp +++ b/src/lib/DinoEEPROM.cpp @@ -3,6 +3,7 @@ // #include "Dino.h" +#ifdef EEPROM_PRESENT // CMD = 6 // Read from the microcontroller's EEPROM. // @@ -61,3 +62,4 @@ void Dino::eepromWrite(){ #endif } } +#endif \ No newline at end of file diff --git a/src/lib/DinoSPI.cpp b/src/lib/DinoSPI.cpp index 092983de..280c0b90 100644 --- a/src/lib/DinoSPI.cpp +++ b/src/lib/DinoSPI.cpp @@ -25,7 +25,13 @@ void Dino::spiBegin(byte settings, uint32_t clockRate){ byte mode = settings & 0B00001111; // Bit 7 of settings toggles MSBFIRST. - SPI.beginTransaction(SPISettings(clockRate, bitRead(settings, 7), mode)); + // Don't try to refactor t his and just pass bit 7. + // On the Due, MSBFIRST and LSBFIRST are ByteOrder class objects. + if (bitRead(settings, 7)) { + SPI.beginTransaction(SPISettings(clockRate, MSBFIRST, mode)); + } else { + SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, mode)); + } } // Convenience wrapper for SPI.end From 589590e84225386c9147ef4891a311a3330d181f Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 11 Feb 2023 18:03:23 -0400 Subject: [PATCH 229/296] Update change log with PR message --- CHANGELOG.md | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd324ac0..383cab14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,124 @@ # Changelog +## 0.12.0 + +### New Boards +- The `dino sketch` shell command now accepts a `--target` argument. It includes/excludes features to tailor the sketch for different boards/chips. Run `dino targets` for more info. + +- ATmega Based Boards (default) (`--target mega`): + - This is the default sketch if `--target` isn't specified, and works for many of the common Arduino products, like the Uno, Nano, Leonardo and Mega. + +- ESP8266 (`--target esp8266`): + - Works with `Dino::Board.new`, but calling `Dino::Board::ESP8266.new` instead allows pins to be referred to as any of `'GPIO4'`, `4`, or `'D2'`, as printed on dev boards like the NodeMCU or WeMos, with the printed names mapping to the correct GPIO. + - Works with either built in WiFi or Serial. + - WiFi version supports OTA (over-the-air) update in the Arduino IDE. Initial flash must still be done via serial. + - **Note**: SoftwareSerial is incompatible with the ESP8266. LiquidCrystal (LCD) compiles for the ESP8266, but does not work. Both of these are excluded. + +- Arduino Due (`--target due`) : + - Up to 12-bit analog in/out. Pass a `bits:` option to `Board#new` to set resolution for both. + - DAC support. Refer to DAC pins as `'DAC0'`, `'DAC1'`, just as labeled on the board. Call `#analog_write` or just `#write` on an `AnalogOutput` component that uses the pin. + - Uses the native ARM serial port by default. Configurable in sketch to use programming port. + - **Note**: SoftwareSerial, Infrared, and Tone are currently incompatible with the Arduino Due, and excluded from the sketch. + +- ATmega168 (`--target mega168`): + - By excluding a lot of features, we can still fit the memory constraints of the ATmega168 chips found in older Arduinos. + - SoftwareSerial, LCD, OneWire and IROut are compatible, but left out to keep memory usage down. + - Included libraries can be toggled in `DinoDefines.h` to suit your needs. + - **Note:** Aux message is limited to 264 bytes on the mega168, or less depending on included libraries. The only feature currently affected by this is sending long infrared signals, like for an air conditioner. + +#### New Components + +- Hitachi HD44780 LCD support. _Uses Arduino `LiquidCrystal` library._ + +- Seven Segment Display support. _Ruby implementation as multiple LEDs._ + +- Infrared Emitter support. _Uses [Arduino-IRremote](https://github.com/z3t0/Arduino-IRremote), and the [ESP8266 fork](https://github.com/markszabo/IRremoteESP8266) where applicable._ + +- Tone (piezo) support. _Uses Arduino `tone`,`noTone` functions._ + +- SoftwareSerial. _Uses Arduino `SoftSerial` library._ (**write only / experimental**) + +- Rotary encoder support. _Uses polling method @ 1ms interval._ **WARNING**: Not suitable for high speeds or precise position needs. It will definitely miss steps. Sufficient for using rotary knobs for user input. + +- DHT11 / DHT 21 (AM2301) / DHT22 temperature and relative humidity sensor support. _Custom implementation where input pulses are measured on the board, then decoded in Ruby._ + +- DS3231 RTC (real time clock) over I2C _(Ruby implementation)_ + +- DS18B20 temperature sensor. _Uses custom implementation of Dallas/Maxim 1-Wire bus below._ + +- Dallas/Maxim 1-Wire bus support. _Low level timing functions run on the board. High level logic in Ruby._ + - Most bus features are implemented: reset/presence detect, parasite power handling, bus search and slave device identification, CRC. No overdrive support. + - Based on [Kevin Darrah's video](https://www.youtube.com/watch?v=ZKNQhzPwH0s) explaining the DS18B20 datasheet. + +- I2C bus support. _Uses Arduino`Wire` library._ + +- Shift Register support. _Uses Arduino `ShiftOut` and `ShiftIn` functions._ + +- SPI bus support (_uses Arduino `SPI` library_) : + - Read/Write Transfers + - Read-Only Listeners (like digital/analog listeners, but reads n bytes from MISO) + +- Generic input and output register classes for the above 2: `Register::ShiftIn`, `Register::ShiftOut`, `Register::SPIIn`, `Register::SPIOut`. + +- Board EEPROM support. _Uses Arduino `EEPROM` library._ + +- WiFi shield support (**untested**) + +### Changed Components +- Servos can now be connected to arbitrary pins as long as they are supported by the board. + +- Digital and analog listeners now have dividers on a per-pin basis. + - Timing is based on a 1000 microsecond tick being counted by the board. + - Call `#listen` with a value as the first argument. Eg. `sensor.listen(64)` will tell the board to send us that specific sensor's state every 64 ticks (~64ms) or around 16 times per second, without affecting other components' rates. + - Valid dividers are: `1, 2, 4, 8, 16, 32, 64, 128`. + - Defaults are same as before: `4` for digital, `16` for analog. + +### Hardware Abstraction + +- `MultiPin` abstraction for components using more than one pin: + - Components connecting to more than 1 pin, like an RGB LED or rotary encoder, are now modeled as `MultiPin` and contain multiple `SinglePin` proxy components. An `RGBLed` is built from 3 `AnalogOutput`s, for example, one for each color, connected to a separate pin. + - `MultiPin` implements a shortcut class method `proxy_pins`. Proxying a pin allows subcomponent pin numbers to be given as a hash when initializing an instance of a `MultiPin` component. Eg: `{red: 9, green: 10, blue: 11}` given as the `pins:` option for `RGBLed#new`. + - When initialized, subcomponents corresponding to the proxied pins are automatically created. They're stored in `#proxies` and `attr_reader` methods are created for each, corresponding to their key in the `pins:` hash. Eg: `RGBLed#green` and `RGBLed#proxies[:green]` both give the `AnalogOutput` component that represents the green LED inside the RGB LED, connected to pin 10. + +- `BoardProxy` abstraction for shift/SPI registers: + - The `Register` classes implement enough of the `Board` interface to satisfy components based on `DigitalInput` and `DigitalOutput`, such as `Led` or `Button`. + - This lets you call methods on components directly, rather than manipulating the register data to control components indirectly. + - Initialize the appropriate `Register` object for the type of register. To initialize a component connected to the register, use the register as the "board", and for the pin, give the parallel I/O pin of the register that the component is connected to. Pin 0 maps to the lowest bit. + - This also works for `MultiPin` components built out of only `DigitalInput` and `DigitalOutput`, eg. `SSD` - seven segment display or `RGBLed`. See `examples/register` for more. + +### Input Components, Callbacks and State +- `@value` has been renamed to `@state`. + - By default, all components define `#state` and `#state=`, which access `@state` through `@state_mutex`. This way we don't try to read with `#state` while a callback is updating it with `#state=`. + - `@state` can be any Ruby object representing the state of the component. + +- Callback functionality for components has been extracted into a mixin module, `Mixins::Callbacks`. + - Like before, callbacks for all components on a board run sequentially, in a single "update" thread, separate from the main thread. This is the same thread reading from TxRx. + - `#add_callback` and `#remove_callback` methods are available, and take an optional `key` as argument. + - Blocks given to `#add_callback` are stored in `@callbacks[key]`, to be called later, when the "update" thread receives data for the component. The default key is `:persistent`. + - Each key represents an array of callbacks, so multiple callbacks can share the same key. + - Calling `#remove_callbacks` with a key empties that array. Calling with no key removes **all** callbacks for the component. + - `#pre_callback_filter` is defined in the `Callbacks` module. The return value of this method is what is given to the component's callbacks and to update its `@state`. By default, it returns whatever was given from the board. + - Override `#pre_callback_filter` to process data before giving it to callbacks and `@state`. Eg: given raw bytes from a DHT sensor, process them into a hash containing `:celsius`, `: fahrenheit` and `:humidity` values. That hash is given to to callbacks and `#update_state` instead of the original string of values. + - `#update_state` is defined in the `Callbacks` module. It is called after all callbacks are run and given the return value of `#pre_callback_filter`. By default, it sets `#state=` to that value. + - Override it if updating `@state` is more complex than this, but be sure to either use `#state=` only once, or wrap the operation in `@state_mutex.synchronize`. + +- Input components no longer automatically start listening when created, since there are more options for reading inputs. + - `DigitalInput` and its subclasses are the exception to this. They automatically listen, since there is little advantage to other modes. + +- Input components can have any combination of `#read`, `#poll` and `#listen` methods now, coming from `Reader`, `Poller`, and `Listener` respectively, inside `Mixins`. + - `#read` sends a single read command by calling `#_read`, and blocks the main thread, until `data` is received from `#pre_callback_filter`. When received, any block given to `#read` will run in the "update" thread once and be removed immediately. `#read` then stops blocking the main thread and returns `data`. + - `#poll` requires an interval (in seconds) as its first argument. It starts a new thread, and keeps calling `#_read` in it, at the given interval. `#poll` does not block the main thread, and does not return a value. A block given will be added as a callback inside the `:poll` key. + - `#listen` adds its block as a callback inside the `:listen` key, calls `#_listen` and returns immediately. + - `#stop` stops polling **and** listening. It also **removes all callbacks** in the **`:poll` and `:listen` keys** (callbacks added as blocks when polling or listening). + +### Minor Changes +- Serial communication now uses the [`rubyserial`](https://github.com/hybridgroup/rubyserial) gem instead of [`serialport`](https://github.com/hparra/ruby-serialport). +- Added more useful information and errors during the connect & handshake process. +- Extended message syntax so the Arduino can receive arbitrary length messages, including binary. +- Created `Dino::Message` class to handle message construction. +- Moved CLI into it's own class, `Dino::CLI`. +- Added simple flow control to avoid overrunning the 64 byte input buffer in the Arduino `Serial` library. No flow control for Ruby receiving data. + ## 0.11.3 * Backport bug fixes from 0.12: * Listeners weren't working properly on the Arduino MEGA. From ee96dae96d7b6f221c4ec86dbd5711c1d7e5971d Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 11 Feb 2023 18:16:56 -0400 Subject: [PATCH 230/296] Change #update_self to #update_state for more clarity --- CHANGELOG.md | 3 ++- lib/dino/components/basic/board_eeprom.rb | 6 ++++-- lib/dino/components/mixins/callbacks.rb | 8 ++++---- lib/dino/components/rotary_encoder.rb | 2 +- test/components/mixins/callbacks_test.rb | 2 +- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 383cab14..5a239b45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ - Included libraries can be toggled in `DinoDefines.h` to suit your needs. - **Note:** Aux message is limited to 264 bytes on the mega168, or less depending on included libraries. The only feature currently affected by this is sending long infrared signals, like for an air conditioner. -#### New Components +### New Components - Hitachi HD44780 LCD support. _Uses Arduino `LiquidCrystal` library._ @@ -113,6 +113,7 @@ ### Minor Changes - Serial communication now uses the [`rubyserial`](https://github.com/hybridgroup/rubyserial) gem instead of [`serialport`](https://github.com/hparra/ruby-serialport). +- Switched from `respec` to `minitest` for testing. - Added more useful information and errors during the connect & handshake process. - Extended message syntax so the Arduino can receive arbitrary length messages, including binary. - Created `Dino::Message` class to handle message construction. diff --git a/lib/dino/components/basic/board_eeprom.rb b/lib/dino/components/basic/board_eeprom.rb index c6c3dbc4..c3e49a42 100644 --- a/lib/dino/components/basic/board_eeprom.rb +++ b/lib/dino/components/basic/board_eeprom.rb @@ -56,8 +56,10 @@ def pre_callback_filter(message) {address: address, data: bytes} end - def update_self(hash) - self.state[hash[:address], hash[:data].length] = hash[:data] + def update_state(hash) + @state_mutex.synchronize do + @state[hash[:address], hash[:data].length] = hash[:data] + end end def pin diff --git a/lib/dino/components/mixins/callbacks.rb b/lib/dino/components/mixins/callbacks.rb index 24098080..c63e2607 100644 --- a/lib/dino/components/mixins/callbacks.rb +++ b/lib/dino/components/mixins/callbacks.rb @@ -31,19 +31,19 @@ def remove_callback(key=nil) alias :remove_callbacks :remove_callback def update(data) - data = pre_callback_filter(data) + filtered_data = pre_callback_filter(data) callback_mutex.synchronize do @callbacks.each_value do |array| array.each do |callback| - callback.call(data) + callback.call(filtered_data) end end # Remove special :read callback before unlocking. @callbacks.delete(:read) end - update_self(data) + update_state(filtered_data) end # Override this to process data before passing to callbacks. @@ -52,7 +52,7 @@ def pre_callback_filter(data) end # Override if behavior other than @state = filtered data is needed. - def update_self(filtered_data) + def update_state(filtered_data) self.state = filtered_data end end diff --git a/lib/dino/components/rotary_encoder.rb b/lib/dino/components/rotary_encoder.rb index 477d369d..e9d04f7d 100644 --- a/lib/dino/components/rotary_encoder.rb +++ b/lib/dino/components/rotary_encoder.rb @@ -77,7 +77,7 @@ def pre_callback_filter(step) # # After callbacks, set state to the hash from before, except change. # - def update_self(new_state) + def update_state(new_state) self.state = new_state.except(:change) end end diff --git a/test/components/mixins/callbacks_test.rb b/test/components/mixins/callbacks_test.rb index 61dca871..00eb28aa 100644 --- a/test/components/mixins/callbacks_test.rb +++ b/test/components/mixins/callbacks_test.rb @@ -81,7 +81,7 @@ def test_pre_callback_filter_modifies_data cb.verify end - def test_update_self + def test_update_state part.update("test") assert_equal "dino: test", part.state end From b0c98a66e3e32f5b103f39819371c99717285d52 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 11 Feb 2023 20:03:00 -0400 Subject: [PATCH 231/296] Tests --- lib/dino/board/base.rb | 2 +- lib/dino/components.rb | 2 +- lib/dino/components/mixins/listener.rb | 3 +++ lib/dino/components/register/select.rb | 15 +++++------ lib/dino/components/register/shift_in.rb | 18 +++++-------- lib/dino/components/register/shift_out.rb | 4 --- lib/dino/components/setup/input.rb | 2 +- lib/dino/components/setup/single_pin.rb | 2 +- .../{softserial.rb => software_serial.rb} | 0 test/board_test.rb | 3 ++- test/components/mixins/listener_test.rb | 5 ++++ test/components/mixins/reader_test.rb | 16 +++++++++--- test/components/register/select_test.rb | 22 ++++------------ test/components/register/shift_in_test.rb | 4 --- test/components/register/shift_out_test.rb | 4 --- test/components/setup/base_test.rb | 26 ++++++++++++++++++- test/components/setup/input_test.rb | 9 +++++++ test/components/setup/output_test.rb | 6 +---- test/components/setup/single_pin_test.rb | 10 ++++++- 19 files changed, 88 insertions(+), 65 deletions(-) rename lib/dino/components/{softserial.rb => software_serial.rb} (100%) diff --git a/lib/dino/board/base.rb b/lib/dino/board/base.rb index 84656af3..02b2c0a5 100644 --- a/lib/dino/board/base.rb +++ b/lib/dino/board/base.rb @@ -62,7 +62,7 @@ def add_component(component) end def remove_component(component) - stop_listener(component.pin) + component.stop if component.methods.include? :stop @components.delete(component) end end diff --git a/lib/dino/components.rb b/lib/dino/components.rb index a29c5a45..30931282 100644 --- a/lib/dino/components.rb +++ b/lib/dino/components.rb @@ -15,7 +15,7 @@ module Components autoload :Stepper, 'dino/components/stepper' autoload :LCD, 'dino/components/lcd' autoload :Relay, 'dino/components/relay' - autoload :SoftwareSerial, 'dino/components/softserial' + autoload :SoftwareSerial, 'dino/components/software_serial' autoload :DHT, 'dino/components/dht' autoload :IREmitter, 'dino/components/ir_emitter' autoload :Piezo, 'dino/components/piezo' diff --git a/lib/dino/components/mixins/listener.rb b/lib/dino/components/mixins/listener.rb index da450e03..fd89b8e9 100644 --- a/lib/dino/components/mixins/listener.rb +++ b/lib/dino/components/mixins/listener.rb @@ -3,8 +3,11 @@ module Components module Mixins module Listener include Callbacks + + attr_reader :divider def listen(divider=nil, &block) + @divider = divider stop add_callback(:listen, &block) if block_given? _listen(divider) diff --git a/lib/dino/components/register/select.rb b/lib/dino/components/register/select.rb index 6abe23ee..012c0642 100644 --- a/lib/dino/components/register/select.rb +++ b/lib/dino/components/register/select.rb @@ -3,21 +3,18 @@ module Components module Register class Select # - # Register select is an active-low output pin, used to choose which - # register on a bus we're accessing. For input registers, the board - # sends readings prefixed with the register select pin number, since - # each register uses a unique select pin, while clock and data are shared. + # Register select is an active-low output pin, used to select a device + # on a bus for access. For input registers, the board sends bytes + # as if they were coming from this pin. Each register uses a unique + # select pin, so we can identify which Register object the incoming bytes + # belong to. Devices on the same bus share the clock and data pins. # # There is no need to write this pin directly, but it must be in output - # mode, and must follow the callback pattern to receive updates. + # mode, and must include Callbacks to receieve updates. # include Setup::SinglePin include Setup::Output include Mixins::Callbacks - - def initialize_pins(options={}) - super(options) if defined?(super) - end end end end diff --git a/lib/dino/components/register/shift_in.rb b/lib/dino/components/register/shift_in.rb index 84420bb7..a7745dfe 100644 --- a/lib/dino/components/register/shift_in.rb +++ b/lib/dino/components/register/shift_in.rb @@ -20,10 +20,6 @@ def after_initialize(options={}) self.rising_clock = options[:rising_clock] bubble_callbacks end - - def pin - latch.pin - end # # Some registers use rising edges for clock signals. Unless we pull clock @@ -36,13 +32,6 @@ def rising_clock=(value) @rising_clock = [0, nil, false].include?(value) ? false : true end - # Reads come through the latch pin. Bubble them up to ourselves. - def bubble_callbacks - proxies[:latch].add_callback do |byte| - self.update(byte) - end - end - def read(num_bytes=@bytes) board.shift_read latch.pin, data.pin, clock.pin, num_bytes, preclock_high: rising_clock @@ -56,6 +45,13 @@ def listen(num_bytes=@bytes) def stop board.shift_stop(latch.pin) end + + # Reads come through the latch pin. Bubble them up to ourselves. + def bubble_callbacks + proxies[:latch].add_callback do |byte| + self.update(byte) + end + end end end end diff --git a/lib/dino/components/register/shift_out.rb b/lib/dino/components/register/shift_out.rb index fd545c89..b3de0fc4 100644 --- a/lib/dino/components/register/shift_out.rb +++ b/lib/dino/components/register/shift_out.rb @@ -18,10 +18,6 @@ class ShiftOut def write(*bytes) board.shift_write(latch.pin, data.pin, clock.pin, bytes) end - - def pin - latch.pin - end end end end diff --git a/lib/dino/components/setup/input.rb b/lib/dino/components/setup/input.rb index 8362b12a..52df4f8d 100644 --- a/lib/dino/components/setup/input.rb +++ b/lib/dino/components/setup/input.rb @@ -3,7 +3,7 @@ module Components module Setup module Input include SinglePin - attr_reader :pullup, :divider + attr_reader :pullup def pullup=(pullup) @pullup = pullup diff --git a/lib/dino/components/setup/single_pin.rb b/lib/dino/components/setup/single_pin.rb index 4ae5286f..3dac83c5 100644 --- a/lib/dino/components/setup/single_pin.rb +++ b/lib/dino/components/setup/single_pin.rb @@ -18,8 +18,8 @@ def initialize_pins(options={}) end def mode=(mode) - @mode = mode board.set_pin_mode(pin, mode) + @mode = mode end end end diff --git a/lib/dino/components/softserial.rb b/lib/dino/components/software_serial.rb similarity index 100% rename from lib/dino/components/softserial.rb rename to lib/dino/components/software_serial.rb diff --git a/test/board_test.rb b/test/board_test.rb index d5c72110..275e3ebf 100644 --- a/test/board_test.rb +++ b/test/board_test.rb @@ -73,7 +73,8 @@ def test_eeprom def test_add_remove_component mock = MiniTest::Mock.new - mock.expect(:pin, 1) + mock.expect(:methods, [:stop]) + mock.expect(:stop, true) board.add_component(mock) assert_equal [mock], board.components diff --git a/test/components/mixins/listener_test.rb b/test/components/mixins/listener_test.rb index 801bda2f..e57fb3bb 100644 --- a/test/components/mixins/listener_test.rb +++ b/test/components/mixins/listener_test.rb @@ -21,6 +21,11 @@ def test_include_callbacks assert_includes ListenerComponent.ancestors, Dino::Components::Mixins::Callbacks end + + def test_divider_save_and_read + part.listen(4) + assert_equal part.divider, 4 + end def test_call_stop_before_listening mock = MiniTest::Mock.new.expect :call, nil diff --git a/test/components/mixins/reader_test.rb b/test/components/mixins/reader_test.rb index 19dac6e5..32d3acf4 100644 --- a/test/components/mixins/reader_test.rb +++ b/test/components/mixins/reader_test.rb @@ -39,10 +39,18 @@ def test_read_once part.stub(:_read, mock) { part.read } mock.verify end - - # test returns read value - # test blocks main thread - # test read_using -> {} + + def test_return_value + inject(42) + assert_equal part.read, 42 + end + + def test_read_using + inject(1) + reader = MiniTest::Mock.new.expect :call, nil + part.read_using -> { reader.call } + reader.verify + end def test_add_run_remove_callback cb = MiniTest::Mock.new.expect :call, nil diff --git a/test/components/register/select_test.rb b/test/components/register/select_test.rb index 2ea99ea6..6351d8d4 100644 --- a/test/components/register/select_test.rb +++ b/test/components/register/select_test.rb @@ -1,23 +1,11 @@ require 'test_helper' class RegisterSelectTest < Minitest::Test - def board - @board ||= BoardMock.new - end - - def part - @part ||= Dino::Components::Register::Select.new(board: board, pin: 14) - end - - def test_default_to_output - mock = MiniTest::Mock.new.expect :call, nil, [14, :out] - board.stub(:set_pin_mode, mock) do - part - end - mock.verify - end - - def test_callbacks + def test_includes + assert_includes Dino::Components::Register::Select.ancestors, + Dino::Components::Setup::SinglePin + assert_includes Dino::Components::Register::Select.ancestors, + Dino::Components::Setup::Output assert_includes Dino::Components::Register::Select.ancestors, Dino::Components::Mixins::Callbacks end diff --git a/test/components/register/shift_in_test.rb b/test/components/register/shift_in_test.rb index 8a83fa3e..68618214 100644 --- a/test/components/register/shift_in_test.rb +++ b/test/components/register/shift_in_test.rb @@ -18,10 +18,6 @@ def test_proxies assert_equal Dino::Components::Basic::DigitalInput, part.data.class assert_equal Dino::Components::Register::Select, part.latch.class end - - def test_identifies_with_latch_pin - assert_equal 8, part.pin - end def test_byte_length new_part = Dino::Components::Register::ShiftIn.new(options.merge(bytes: 2)) diff --git a/test/components/register/shift_out_test.rb b/test/components/register/shift_out_test.rb index 35969007..5ad79f5c 100644 --- a/test/components/register/shift_out_test.rb +++ b/test/components/register/shift_out_test.rb @@ -18,10 +18,6 @@ def test_proxies assert_equal Dino::Components::Basic::DigitalOutput, part.data.class assert_equal Dino::Components::Register::Select, part.latch.class end - - def test_identifies_with_latch_pin - assert_equal 8, part.pin - end def test_write # mock = MiniTest::Mock.new.expect :call, nil, ["21.8.1.#{[11,12,0,255,127].pack('C*')}\n"] diff --git a/test/components/setup/base_test.rb b/test/components/setup/base_test.rb index ff18b02e..934e0f2b 100644 --- a/test/components/setup/base_test.rb +++ b/test/components/setup/base_test.rb @@ -8,17 +8,41 @@ class BaseSetupTest < Minitest::Test def board @board ||= BoardMock.new end + + def part + @part ||= BaseComponent.new(board: board) + end def test_requires_board assert_raises(ArgumentError) { BaseComponent.new } end def test_registers_with_board - part = BaseComponent.new(board: board) assert_equal board.components, [part] end + def test_unregisters_with_board + part.send(:unregister) + assert_equal board.components, [] + end + def test_start_with_nil_state assert_nil BaseComponent.new(board: board).state end + + def test_sets_and_gets_state + part.send(:state=, 10) + assert_equal part.state, 10 + end + + def test_state_through_mutex + mock = MiniTest::Mock.new + 2.times {mock.expect(:call, nil)} + + part.instance_variable_get(:@state_mutex).stub(:synchronize, mock) do + part.state + part.send(:state=, nil) + end + mock.verify + end end diff --git a/test/components/setup/input_test.rb b/test/components/setup/input_test.rb index d960c2b6..164fb286 100644 --- a/test/components/setup/input_test.rb +++ b/test/components/setup/input_test.rb @@ -30,6 +30,15 @@ def test_pullup end mock.verify end + + def test_stop_listener + mock = Minitest::Mock.new + mock.expect :call, nil, [1] + board.stub(:stop_listener, mock) do + part._stop_listener + end + mock.verify + end def test_pullup_in_options mock = Minitest::Mock.new.expect :call, nil, [2, true] diff --git a/test/components/setup/output_test.rb b/test/components/setup/output_test.rb index 8ac78e92..be44580a 100644 --- a/test/components/setup/output_test.rb +++ b/test/components/setup/output_test.rb @@ -14,10 +14,6 @@ def part end def test_pin_mode - mock = Minitest::Mock.new.expect :call, nil, [1, :out] - board.stub(:set_pin_mode, mock) do - part - end - mock.verify + assert_equal part.mode, :out end end diff --git a/test/components/setup/single_pin_test.rb b/test/components/setup/single_pin_test.rb index a46ecaa1..7d67cc7b 100644 --- a/test/components/setup/single_pin_test.rb +++ b/test/components/setup/single_pin_test.rb @@ -1,7 +1,7 @@ require 'test_helper' class SinglePinComponent - include Dino::Components::Setup::Input + include Dino::Components::Setup::SinglePin end class SinglePinSetupTest < Minitest::Test @@ -16,4 +16,12 @@ def part def test_requires_pin assert_raises(ArgumentError) { SinglePinComponent.new(board: board) } end + + def test_mode= + mock = Minitest::Mock.new + mock.expect :call, nil, [1, :out] + board.stub(:set_pin_mode, mock) do + part.send(:mode=, :out) + end + end end From 4f7ee25def14ec9a465718973c347709da2d07f8 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 11 Feb 2023 20:09:38 -0400 Subject: [PATCH 232/296] Fix spelling of Fahrenheit in variable names --- examples/dht/dht.rb | 2 +- examples/one_wire/ds18b20.rb | 2 +- lib/dino/components/dht.rb | 4 ++-- lib/dino/components/one_wire/ds18b20.rb | 4 ++-- test/components/dht_test.rb | 2 +- test/components/one_wire/ds18b20_test.rb | 8 ++++---- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/dht/dht.rb b/examples/dht/dht.rb index 63d127b7..f2f07ed5 100644 --- a/examples/dht/dht.rb +++ b/examples/dht/dht.rb @@ -14,7 +14,7 @@ if reading[:error] puts "Error: #{reading[:error]}" else - print "#{reading[:celsius]} \xC2\xB0C | #{reading[:farenheit]} \xC2\xB0F | " + print "#{reading[:celsius]} \xC2\xB0C | #{reading[:fahrenheit]} \xC2\xB0F | " puts "#{reading[:humidity]}% relative humidity" end end diff --git a/examples/one_wire/ds18b20.rb b/examples/one_wire/ds18b20.rb index afe24c07..49ce8f43 100644 --- a/examples/one_wire/ds18b20.rb +++ b/examples/one_wire/ds18b20.rb @@ -44,7 +44,7 @@ def print_reading(reading, sensor) if reading[:crc_error] puts "CRC check failed for this reading!" else - print "#{reading[:celsius]} \xC2\xB0C | #{reading[:farenheit]} \xC2\xB0F | " + print "#{reading[:celsius]} \xC2\xB0C | #{reading[:fahrenheit]} \xC2\xB0F | " puts "Raw: #{reading[:raw].inspect}" end end diff --git a/lib/dino/components/dht.rb b/lib/dino/components/dht.rb index 46438eae..0c91635c 100644 --- a/lib/dino/components/dht.rb +++ b/lib/dino/components/dht.rb @@ -31,9 +31,9 @@ def decode(data) celsius = ((bytes[2] << 8) | bytes[3]).to_f / 10 humidity = ((bytes[0] << 8) | bytes[1]).to_f / 10 - farenheit = (celsius * 1.8 + 32).round(1) + fahrenheit = (celsius * 1.8 + 32).round(1) - { celsius: celsius, farenheit: farenheit, humidity: humidity } + { celsius: celsius, fahrenheit: fahrenheit, humidity: humidity } end def crc(bytes) diff --git a/lib/dino/components/one_wire/ds18b20.rb b/lib/dino/components/one_wire/ds18b20.rb index 8aaf0245..853b441c 100644 --- a/lib/dino/components/one_wire/ds18b20.rb +++ b/lib/dino/components/one_wire/ds18b20.rb @@ -60,9 +60,9 @@ def pre_callback_filter(bytes) # def decode_temperature(bytes) celsius = bytes[0..1].pack('C*').unpack('s<')[0] * (2.0 ** -4) - farenheit = (celsius * 1.8 + 32).round(4) + fahrenheit = (celsius * 1.8 + 32).round(4) - {celsius: celsius, farenheit: farenheit} + {celsius: celsius, fahrenheit: fahrenheit} end def decode_resolution(bytes) diff --git a/test/components/dht_test.rb b/test/components/dht_test.rb index 911638d3..bf67c339 100644 --- a/test/components/dht_test.rb +++ b/test/components/dht_test.rb @@ -55,7 +55,7 @@ def test_decode # It should calculate output correctly. result = part.decode(GOOD_ARRAY) - assert_equal result, {celsius: 29.5, farenheit: 85.1, humidity: 66.9} + assert_equal result, {celsius: 29.5, fahrenheit: 85.1, humidity: 66.9} end def test_crc diff --git a/test/components/one_wire/ds18b20_test.rb b/test/components/one_wire/ds18b20_test.rb index 8a6c1499..301a7a78 100644 --- a/test/components/one_wire/ds18b20_test.rb +++ b/test/components/one_wire/ds18b20_test.rb @@ -10,13 +10,13 @@ def part end def test_decode_temperatures - assert_equal({ celsius: 125, farenheit: 257 }, + assert_equal({ celsius: 125, fahrenheit: 257 }, part.decode_temperature([0b1101_0000,0b0000_0111])) - assert_equal({ celsius: 0, farenheit: 32 }, + assert_equal({ celsius: 0, fahrenheit: 32 }, part.decode_temperature([0b0000_0000,0b0000_0000])) - assert_equal({ celsius: -10.125, farenheit: 13.775 }, + assert_equal({ celsius: -10.125, fahrenheit: 13.775 }, part.decode_temperature([0b0101_1110,0b1111_1111])) - assert_equal({ celsius: -55, farenheit: -67 }, + assert_equal({ celsius: -55, fahrenheit: -67 }, part.decode_temperature([0b1001_0000,0b1111_1100])) end From fde8ee77a820952e328d09392b994ac1c86a917d Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 12 Feb 2023 13:08:12 -0400 Subject: [PATCH 233/296] Tests for ShiftIO register interface --- lib/dino/api/shift_io.rb | 10 +++--- test/api/servo_test.rb | 2 -- test/api/shift_io_test.rb | 71 +++++++++++++++++++++++++++++++++++++++ test/api/spi_test.rb | 1 + 4 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 test/api/shift_io_test.rb diff --git a/lib/dino/api/shift_io.rb b/lib/dino/api/shift_io.rb index f1f2b3de..f6a4ddb8 100644 --- a/lib/dino/api/shift_io.rb +++ b/lib/dino/api/shift_io.rb @@ -12,12 +12,12 @@ def shift_write(latch, data, clock, byte_array, options={}) settings = shift_settings(data, clock) limit = aux_limit - settings.length bytes = pack :uint8, - byte_array, - max: (limit < 256) ? limit : 256 + [byte_array].flatten, + max: limit write Message.encode command: 21, pin: convert_pin(latch), - value: bytes.length, # Should be length-1 so 0 = 1 byte, 255 = 256 bytes + value: bytes.length, aux_message: settings + bytes end @@ -25,7 +25,7 @@ def shift_read(latch, data, clock, num_bytes, options={}) settings = shift_settings(data, clock, options[:preclock_high]) write Message.encode command: 22, pin: convert_pin(latch), - value: num_bytes, # Should be num-1 so 0 = 1 byte, 255 = 256 bytes + value: num_bytes, aux_message: settings end @@ -33,7 +33,7 @@ def shift_listen(latch, data, clock, num_bytes, options={}) settings = shift_settings(data, clock, options[:preclock_high]) write Message.encode command: 23, pin: convert_pin(latch), - value: num_bytes, # Should be num-1 so 0 = 1 byte, 255 = 256 bytes + value: num_bytes, aux_message: settings end diff --git a/test/api/servo_test.rb b/test/api/servo_test.rb index 76f1b6e7..9eecd34f 100644 --- a/test/api/servo_test.rb +++ b/test/api/servo_test.rb @@ -1,5 +1,3 @@ -# For convenience when validating longer data types. - require 'test_helper' class APIServoTest < Minitest::Test diff --git a/test/api/shift_io_test.rb b/test/api/shift_io_test.rb new file mode 100644 index 00000000..45c71174 --- /dev/null +++ b/test/api/shift_io_test.rb @@ -0,0 +1,71 @@ +# encoding: ascii-8bit +# For convenience when validating longer data types. + +require 'test_helper' + +class APIShiftIOTest < Minitest::Test + include Dino::API::Helper + + def txrx + @txrx ||= TxRxMock.new + end + + def board + @board ||= Dino::Board.new(txrx) + end + + def test_shift_settings + settings1 = pack :uint8, [4, 5, 1] + settings2 = pack :uint8, [6, 7, 0] + assert_equal board.shift_settings(4, 5, true), settings1 + assert_equal board.shift_settings(6, 7), settings2 + end + + def test_shift_write + settings = board.shift_settings(4, 5) + bytes1 = pack :uint8, [1,2,3] + bytes2 = pack :uint8, [25] + mock = MiniTest::Mock.new + mock.expect :call, nil, [Dino::Message.encode(command: 21, pin: 3, value: 3, aux_message: settings + bytes1)] + mock.expect :call, nil, [Dino::Message.encode(command: 21, pin: 3, value: 1, aux_message: settings + bytes2)] + + board.stub(:write, mock) do + board.shift_write(3, 4, 5, [1,2,3]) + board.shift_write(3, 4, 5, 25) + end + end + + def test_shift_read + settings1 = board.shift_settings(4, 5) + settings2 = board.shift_settings(4, 5, true) + mock = MiniTest::Mock.new + mock.expect :call, nil, [Dino::Message.encode(command: 22, pin: 3, value: 8, aux_message: settings1)] + mock.expect :call, nil, [Dino::Message.encode(command: 22, pin: 3, value: 4, aux_message: settings2)] + + board.stub(:write, mock) do + board.shift_read(3, 4, 5, 8) + board.shift_read(3, 4, 5, 4, preclock_high: true) + end + end + + def test_shift_listen + settings1 = board.shift_settings(4, 5) + settings2 = board.shift_settings(4, 5, true) + mock = MiniTest::Mock.new + mock.expect :call, nil, [Dino::Message.encode(command: 23, pin: 3, value: 8, aux_message: settings1)] + mock.expect :call, nil, [Dino::Message.encode(command: 23, pin: 3, value: 4, aux_message: settings2)] + + board.stub(:write, mock) do + board.shift_listen(3, 4, 5, 8) + board.shift_listen(3, 4, 5, 4, preclock_high: true) + end + end + + def test_shift_stop + mock = MiniTest::Mock.new + mock.expect :call, nil, [Dino::Message.encode(command: 24, pin: 3)] + board.stub(:write, mock) do + board.shift_stop(3) + end + end +end diff --git a/test/api/spi_test.rb b/test/api/spi_test.rb index af824f7e..136d33db 100644 --- a/test/api/spi_test.rb +++ b/test/api/spi_test.rb @@ -1,3 +1,4 @@ +# encoding: ascii-8bit # For convenience when validating longer data types. require 'test_helper' From 1e442d5e4163aabe9ca02b15704c1af4bf2226f8 Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 12 Feb 2023 18:29:32 -0400 Subject: [PATCH 234/296] Make tone work with freq/duration above 4 digits, or no duration at all --- lib/dino/api/shift_io.rb | 4 ++-- lib/dino/api/tone.rb | 17 +++++++++++------ lib/dino/components/piezo.rb | 2 +- src/lib/DinoTone.cpp | 11 +++++++++-- test/api/tone_test.rb | 11 ++++++++++- test/components/piezo_test.rb | 33 +++++++++++++++++++++++++++++++++ 6 files changed, 66 insertions(+), 12 deletions(-) create mode 100644 test/components/piezo_test.rb diff --git a/lib/dino/api/shift_io.rb b/lib/dino/api/shift_io.rb index f6a4ddb8..dcc79baf 100644 --- a/lib/dino/api/shift_io.rb +++ b/lib/dino/api/shift_io.rb @@ -8,11 +8,11 @@ def shift_settings(data, clock, preclock_high=false) pack :uint8, [convert_pin(data), convert_pin(clock), preclock_high] end - def shift_write(latch, data, clock, byte_array, options={}) + def shift_write(latch, data, clock, bytes, options={}) settings = shift_settings(data, clock) limit = aux_limit - settings.length bytes = pack :uint8, - [byte_array].flatten, + bytes, max: limit write Message.encode command: 21, diff --git a/lib/dino/api/tone.rb b/lib/dino/api/tone.rb index 9f4a7170..8faf8fd4 100644 --- a/lib/dino/api/tone.rb +++ b/lib/dino/api/tone.rb @@ -2,15 +2,20 @@ module Dino module API module Tone include Helper + + def tone(pin, frequency, duration=nil) + if frequency < 31 + raise ArgumentError, "Tone cannot generate frequencies lower than 31Hz" + end + + # Pack the frequency and optiona duration as binary. + aux = pack(:uint16, frequency) + aux << pack(:uint32, duration) if duration - # value = tone frequency in Hz (should be sent as binary to allow higher than 9999, and limit to above 30Hz) - # duration = tone duration in ms - # duration currently sent as string. Should be binary and max limited to uint32 - def tone(pin, value, duration) write Dino::Message.encode command: 17, pin: convert_pin(pin), - value: value, - aux_message: duration + value: duration ? 1 : 0, + aux_message: aux end def no_tone(pin) diff --git a/lib/dino/components/piezo.rb b/lib/dino/components/piezo.rb index c1f558e2..2e840fc8 100644 --- a/lib/dino/components/piezo.rb +++ b/lib/dino/components/piezo.rb @@ -11,7 +11,7 @@ def after_initialize(options={}) end # Duration is in mills - def tone(value, duration = 500) + def tone(value, duration=nil) board.tone(pin, value, duration) end diff --git a/src/lib/DinoTone.cpp b/src/lib/DinoTone.cpp index e15caf3f..db4bb233 100644 --- a/src/lib/DinoTone.cpp +++ b/src/lib/DinoTone.cpp @@ -6,8 +6,15 @@ // CMD = 20 void Dino::tone() { - unsigned int duration = atoi((char*)auxMsg); - ::tone(pin, val, duration); + uint16_t frequency = auxMsg[0]; + uint32_t duration = auxMsg[2]; + + // val is 1 if a duration was given, 0 if not. + if (val !=0) { + ::tone(pin, frequency, duration); + } else { + ::tone(pin, frequency); + } } // CMD = 21 diff --git a/test/api/tone_test.rb b/test/api/tone_test.rb index e5d160fb..c4457fcd 100644 --- a/test/api/tone_test.rb +++ b/test/api/tone_test.rb @@ -13,13 +13,22 @@ def board def test_tone mock = MiniTest::Mock.new - mock.expect :call, nil, [Dino::Message.encode(command: 17, pin: 10, value: 150, aux_message: 2000)] + aux1 = pack(:uint16, 150) + pack(:uint32, 2000) + aux2 = pack(:uint16, 300) + + mock.expect :call, nil, [Dino::Message.encode(command: 17, pin: 10, value: 1, aux_message: aux1)] + mock.expect :call, nil, [Dino::Message.encode(command: 17, pin: 10, value: 0, aux_message: aux2)] board.stub(:write, mock) do board.tone(10, 150, 2000) + board.tone(10, 300) end mock.verify end + + def test_tone_prevents_low_frequencies + assert_raises(ArgumentError, /freq/i) { board.tone(4, 30, 3000) } + end def test_no_tone mock = MiniTest::Mock.new diff --git a/test/components/piezo_test.rb b/test/components/piezo_test.rb new file mode 100644 index 00000000..6d9e81b6 --- /dev/null +++ b/test/components/piezo_test.rb @@ -0,0 +1,33 @@ +require 'test_helper' + +class PiezoTest < MiniTest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= Dino::Components::Piezo.new(board: board, pin:8) + end + + def test_low_on_initialize + assert_equal part.state, board.low + end + + def test_tone + mock = MiniTest::Mock.new + mock.expect :call, nil, [part.pin, 60, nil] + mock.expect :call, nil, [part.pin, 120, 2000] + board.stub(:tone, mock) do + part.tone(60) + part.tone(120, 2000) + end + end + + def test_no_tone + mock = MiniTest::Mock.new + mock.expect :call, nil, [part.pin] + board.stub(:tone, mock) do + part.no_tone + end + end +end From 809454714f622c7aac27a99da74cbf1674ed0eb5 Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 12 Feb 2023 23:46:22 -0400 Subject: [PATCH 235/296] Let DigitalOutput take strings and nil. Let components on ESP8266 have nil pin --- lib/dino/board/esp8266.rb | 3 ++- lib/dino/components/basic/digital_output.rb | 1 + test/components/basic/digital_output_test.rb | 9 ++++++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/dino/board/esp8266.rb b/lib/dino/board/esp8266.rb index cbd28e5c..eba38e4b 100644 --- a/lib/dino/board/esp8266.rb +++ b/lib/dino/board/esp8266.rb @@ -29,13 +29,14 @@ class ESP8266 < Base } def convert_pin(pin) + return nil if pin == nil pin = pin.to_s return pin.to_i if pin.match(RAW_REGEX) return gpio_pin_to_i(pin) if pin.match(GPIO_REGEX) return analog_pin_to_i(pin) if pin.match(ANALOG_REGEX) return digital_pin_to_i(pin) if pin.match(DIGITAL_REGEX) return "EE" if pin == "EE" - raise ArgumentError, "incorrect pin format" + raise ArgumentError, "incorrect pin format: #{pin.inspect}" end # Only one analog in on the ESP8266, GPIO 17. diff --git a/lib/dino/components/basic/digital_output.rb b/lib/dino/components/basic/digital_output.rb index 280694c4..48415fe8 100644 --- a/lib/dino/components/basic/digital_output.rb +++ b/lib/dino/components/basic/digital_output.rb @@ -18,6 +18,7 @@ def pre_callback_filter(board_state) end def digital_write(value) + value = value.to_i value = board.high unless (value == board.low) board.digital_write(pin, @state = value) end diff --git a/test/components/basic/digital_output_test.rb b/test/components/basic/digital_output_test.rb index 3b5d30b9..6ea4de46 100644 --- a/test/components/basic/digital_output_test.rb +++ b/test/components/basic/digital_output_test.rb @@ -19,9 +19,16 @@ def read_state_on_initialize def test_digital_write part - mock = MiniTest::Mock.new.expect :call, nil, [14, board.high] + mock = MiniTest::Mock.new + mock.expect :call, nil, [14, board.high] + mock.expect :call, nil, [14, board.low] + mock.expect :call, nil, [14, board.low] + mock.expect :call, nil, [14, board.high] board.stub(:digital_write, mock) do part.digital_write(board.high) + part.digital_write(nil) + part.digital_write("0") + part.digital_write("1") end mock.verify assert_equal board.high, part.state From 8693e6abaa9f0966bd6d5896a9f3b8bd57452cdc Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 13 Feb 2023 16:00:56 -0400 Subject: [PATCH 236/296] Tests for BoardEEPROM and RotaryEncoder --- lib/dino/components/rotary_encoder.rb | 2 +- test/components/basic/board_eeprom_test.rb | 61 ++++++++++++++++ test/components/rotary_encoder_test.rb | 83 ++++++++++++++++++++++ 3 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 test/components/basic/board_eeprom_test.rb create mode 100644 test/components/rotary_encoder_test.rb diff --git a/lib/dino/components/rotary_encoder.rb b/lib/dino/components/rotary_encoder.rb index e9d04f7d..1edd4b86 100644 --- a/lib/dino/components/rotary_encoder.rb +++ b/lib/dino/components/rotary_encoder.rb @@ -15,7 +15,7 @@ def after_initialize(options={}) if options[:steps_per_revolution] self.degrees_per_step = (360 / options[:steps_per_revolution]) else - self.degrees_per_step = 30 + self.degrees_per_step = 12 end reset diff --git a/test/components/basic/board_eeprom_test.rb b/test/components/basic/board_eeprom_test.rb new file mode 100644 index 00000000..ae17768e --- /dev/null +++ b/test/components/basic/board_eeprom_test.rb @@ -0,0 +1,61 @@ +require 'test_helper' + +class BoardMock < Dino::Board::Default + attr_reader :eeprom_stub + + def eeprom_read(start_address, length) + # Initialize a fake EEPROM + @eeprom_stub ||= Array.new(eeprom_length){255} + + # Pack it up like a string coming from the board. + string = @eeprom_stub[start_address, length].map{ |x| x.to_s }.join(",") + + # Update ourselves with it. + self.update("EE:#{start_address}-#{string}\n") + end + + def eeprom_write(start_address, bytes) + @eeprom_stub[start_address, bytes.length] = bytes + end +end + +class BoardEEPROMTest < Minitest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= board.eeprom + end + + def test_pin_ee + assert_equal part.pin, "EE" + end + + def test_loads_on_initialize_and_updates_correctly + assert_equal part.state, Array.new(board.eeprom_length){255} + end + + def test_delegates_to_state_array + mock = MiniTest::Mock.new + mock.expect(:[], 255, [0]) + mock.expect(:[]=, 128, [1, 128]) + mock.expect(:each, nil) + mock.expect(:each_with_index, nil) + + part.stub(:state, mock) do + part[0] + part[1] = 128 + part.each { |el| el } + part.each_with_index { |el| el } + end + end + + def test_saves_to_the_board + part[0] = 128 + part[part.length] = 127 + part.save + assert_equal board.eeprom_stub[0], 128 + assert_equal board.eeprom_stub[board.eeprom_length], 127 + end +end diff --git a/test/components/rotary_encoder_test.rb b/test/components/rotary_encoder_test.rb new file mode 100644 index 00000000..f1a1fc10 --- /dev/null +++ b/test/components/rotary_encoder_test.rb @@ -0,0 +1,83 @@ +require 'test_helper' + +class RotaryEncoderTest < MiniTest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= Dino::Components::RotaryEncoder.new board: board, pins: {data:3, clock: 4} + end + + def test_sets_steps_per_revolution + assert_equal part.degrees_per_step, 12 + part2 = Dino::Components::RotaryEncoder.new board: board, pins: {data:3, clock: 4}, steps_per_revolution: 40 + assert_equal part2.degrees_per_step, 9 + end + + def test_resets_on_initialize + assert_equal part.state, {steps: 0, angle: 0} + end + + def test_calls_listen_on_both_pins_with_given_divider + clock_mock = MiniTest::Mock.new.expect(:call, nil, [1]) + clock_mock.expect(:call, nil, [2]) + data_mock = MiniTest::Mock.new.expect(:call, nil, [1]) + data_mock.expect(:call, nil, [2]) + + part.clock.stub(:listen, clock_mock) do + part.data.stub(:listen, data_mock) do + part.send(:after_initialize) + part.send(:after_initialize, divider: 2) + end + end + end + + def test_observes_on_initialize + mock = MiniTest::Mock.new.expect(:call, nil) + part.stub(:observe_pins, mock) do + part.send(:after_initialize) + end + end + + def test_observes_the_right_pin + refute_empty part.clock.callbacks + assert_empty part.data.callbacks + + part2 = Dino::Components::RotaryEncoder.new board: board, pins: {data:4, clock: 3} + + refute_empty part2.data.callbacks + assert_empty part2.clock.callbacks + end + + def test_goes_the_right_direction + part.data.send(:update, 1) + part.clock.send(:update, 1) + assert_equal part.state, { steps: 1, angle: 12.0} + + part.reset + + part.data.send(:update, 1) + part.clock.send(:update, 0) + assert_equal part.state, { steps: -1, angle: 348.0} + end + + def test_callback_prefilter + part.data.send(:update, 1) + part.clock.send(:update, 1) + callback_value = nil + part.add_callback do |value| + callback_value = value + end + part.data.send(:update, 1) + part.clock.send(:update, 1) + + assert_equal callback_value, {change: 1, steps: 2, angle: 24.0} + end + + def test_update_state_removes_change + part.data.send(:update, 1) + part.clock.send(:update, 1) + assert_nil part.state[:change] + end +end From 53b9a7d3e02cdc3934a41debaff45618b899cd0b Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 13 Feb 2023 17:20:46 -0400 Subject: [PATCH 237/296] Let Shift registers set bit order. Default to MSBFIRST for in, LSBFIRST for out --- lib/dino/api/shift_io.rb | 14 +++++---- lib/dino/components/register/shift_in.rb | 14 ++++++--- lib/dino/components/register/shift_out.rb | 7 +++++ lib/dino/components/register/spi_in.rb | 4 +-- lib/dino/components/register/spi_out.rb | 2 +- src/lib/Dino.cpp | 4 +-- src/lib/Dino.h | 8 ++--- src/lib/DinoShift.cpp | 28 ++++++++++++----- test/api/shift_io_test.rb | 38 +++++++++++++---------- test/components/register/shift_in_test.rb | 18 ++++++++--- 10 files changed, 89 insertions(+), 48 deletions(-) diff --git a/lib/dino/api/shift_io.rb b/lib/dino/api/shift_io.rb index dcc79baf..55f8a672 100644 --- a/lib/dino/api/shift_io.rb +++ b/lib/dino/api/shift_io.rb @@ -3,13 +3,15 @@ module API module ShiftIO include Helper - def shift_settings(data, clock, preclock_high=false) - preclock_high = preclock_high ? 1 : 0 - pack :uint8, [convert_pin(data), convert_pin(clock), preclock_high] + def shift_settings(data, clock, bit_order=:lsbfirst, preclock_high=false) + settings = 0b00 + settings |= 0b01 if bit_order == :msbfirst + settings |= 0b10 if preclock_high + pack :uint8, [convert_pin(data), convert_pin(clock), settings] end def shift_write(latch, data, clock, bytes, options={}) - settings = shift_settings(data, clock) + settings = shift_settings(data, clock, options[:bit_order]) limit = aux_limit - settings.length bytes = pack :uint8, bytes, @@ -22,7 +24,7 @@ def shift_write(latch, data, clock, bytes, options={}) end def shift_read(latch, data, clock, num_bytes, options={}) - settings = shift_settings(data, clock, options[:preclock_high]) + settings = shift_settings(data, clock, options[:bit_order], options[:preclock_high]) write Message.encode command: 22, pin: convert_pin(latch), value: num_bytes, @@ -30,7 +32,7 @@ def shift_read(latch, data, clock, num_bytes, options={}) end def shift_listen(latch, data, clock, num_bytes, options={}) - settings = shift_settings(data, clock, options[:preclock_high]) + settings = shift_settings(data, clock, options[:bit_order], options[:preclock_high]) write Message.encode command: 23, pin: convert_pin(latch), value: num_bytes, diff --git a/lib/dino/components/register/shift_in.rb b/lib/dino/components/register/shift_in.rb index a7745dfe..17074d65 100644 --- a/lib/dino/components/register/shift_in.rb +++ b/lib/dino/components/register/shift_in.rb @@ -15,9 +15,10 @@ class ShiftIn data: Basic::DigitalInput, latch: Register::Select - def after_initialize(options={}) + def after_initialize(options) super(options) - self.rising_clock = options[:rising_clock] + self.rising_clock = options[:rising_clock] || false + self.bit_order = options[:bit_order] || :msbfirst bubble_callbacks end @@ -27,19 +28,22 @@ def after_initialize(options={}) # Set this once and future calls to #read and #listen will do it. # attr_reader :rising_clock + attr_accessor :bit_order def rising_clock=(value) @rising_clock = [0, nil, false].include?(value) ? false : true end def read(num_bytes=@bytes) - board.shift_read latch.pin, data.pin, clock.pin, num_bytes, - preclock_high: rising_clock + board.shift_read latch.pin, data.pin, clock.pin, num_bytes, + preclock_high: rising_clock, + bit_order: bit_order end def listen(num_bytes=@bytes) board.shift_listen latch.pin, data.pin, clock.pin, num_bytes, - preclock_high: rising_clock + preclock_high: rising_clock, + bit_order: bit_order end def stop diff --git a/lib/dino/components/register/shift_out.rb b/lib/dino/components/register/shift_out.rb index b3de0fc4..67a3ad3f 100644 --- a/lib/dino/components/register/shift_out.rb +++ b/lib/dino/components/register/shift_out.rb @@ -14,6 +14,13 @@ class ShiftOut proxy_pins clock: Basic::DigitalOutput, data: Basic::DigitalOutput, latch: Register::Select + + def after_initialize(options) + super(options) + self.bit_order = options[:bit_order] || :lsbfirst + end + + attr_accessor :bit_order def write(*bytes) board.shift_write(latch.pin, data.pin, clock.pin, bytes) diff --git a/lib/dino/components/register/spi_in.rb b/lib/dino/components/register/spi_in.rb index d3c1d966..109349f8 100644 --- a/lib/dino/components/register/spi_in.rb +++ b/lib/dino/components/register/spi_in.rb @@ -12,12 +12,12 @@ class SPIIn < Select attr_reader :spi_mode, :frequency, :bit_order - def after_initialize(options={}) + def after_initialize(options) super(options) if defined?(super) @spi_mode = options[:spi_mode] || 0 @frequency = options[:frequency] || 3000000 - @bit_order = options[:bit_order] || :lsbfirst + @bit_order = options[:bit_order] || :msbfirst end def read diff --git a/lib/dino/components/register/spi_out.rb b/lib/dino/components/register/spi_out.rb index 8d5922a2..e47fb8eb 100644 --- a/lib/dino/components/register/spi_out.rb +++ b/lib/dino/components/register/spi_out.rb @@ -12,7 +12,7 @@ class SPIOut < Select attr_reader :spi_mode, :frequency, :bit_order - def after_initialize(options={}) + def after_initialize(options) super(options) if defined?(super) @spi_mode = options[:spi_mode] || 0 diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 3ebb3251..1515f690 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -146,8 +146,8 @@ void Dino::process() { // Implemented in DinoShift.cpp #ifdef DINO_SHIFT - case 21: shiftWrite (pin, val, auxMsg[0], auxMsg[1], &auxMsg[3]); break; - case 22: shiftRead (pin, val, auxMsg[0], auxMsg[1], auxMsg[2]); break; + case 21: shiftWrite (pin, val, auxMsg[0], auxMsg[1], auxMsg[2], &auxMsg[3]); break; + case 22: shiftRead (pin, val, auxMsg[0], auxMsg[1], auxMsg[2]); break; case 23: addShiftListener (); break; case 24: removeShiftListener (); break; #endif diff --git a/src/lib/Dino.h b/src/lib/Dino.h index badfe5b6..f4e5a761 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -100,10 +100,10 @@ class Dino { void noTone (); //cmd = 18 // Shift Registers - void shiftWrite (int latchPin, int len, byte dataPin, byte clockPin, byte *data); //cmd = 21 - void shiftRead (int latchPin, int len, byte dataPin, byte clockPin, byte clockHighFirst); //cmd = 22 - void addShiftListener (); //cmd = 23 - void removeShiftListener (); //cmd = 24 + void shiftWrite (int latchPin, int len, byte dataPin, byte clockPin, byte settings, byte *data); //cmd = 21 + void shiftRead (int latchPin, int len, byte dataPin, byte clockPin, byte settings); //cmd = 22 + void addShiftListener (); //cmd = 23 + void removeShiftListener (); //cmd = 24 void updateShiftListeners (); void clearShiftListeners (); diff --git a/src/lib/DinoShift.cpp b/src/lib/DinoShift.cpp index 1f7d33da..bbe630e7 100644 --- a/src/lib/DinoShift.cpp +++ b/src/lib/DinoShift.cpp @@ -23,18 +23,24 @@ shiftListener shiftListeners[SHIFT_LISTENER_COUNT]; // val = length (int) // auxMsg[0] = data pin (byte) // auxMsg[1] = clock pin (byte) -// auxMsg[2] = send clock high before reading (byte) (0/1) (read func only) +// auxMsg[2] = settings +// bit 0 = transmission bit order (1 = MSBFIRST, 0 = LSBFIRST) +// bit 1 = send clock high before reading (byte) (0/1) (only matters for reading) // auxMsg[3]+ = data (bytes) (write func only) // // CMD = 21 // Write to a shift register. -void Dino::shiftWrite(int latchPin, int len, byte dataPin, byte clockPin, byte *data) { +void Dino::shiftWrite(int latchPin, int len, byte dataPin, byte clockPin, byte settings, byte *data) { // Set latch pin low to begin serial write. digitalWrite(latchPin, LOW); // Write one byte at a time. for (uint8_t i = 0; i < len; i++) { - shiftOut(dataPin, clockPin, LSBFIRST, data[i]); + if (bitRead(settings, 0)) { + shiftOut(dataPin, clockPin, MSBFIRST, data[i]); + } else { + shiftOut(dataPin, clockPin, LSBFIRST, data[i]); + } } // Set latch pin high so register writes to parallel output. @@ -44,11 +50,11 @@ void Dino::shiftWrite(int latchPin, int len, byte dataPin, byte clockPin, byte * // CMD = 22 // Read from a shift register. -void Dino::shiftRead(int latchPin, int len, byte dataPin, byte clockPin, byte clockHighFirst) { +void Dino::shiftRead(int latchPin, int len, byte dataPin, byte clockPin, byte settings) { // Send clock pin high if using a register that clocks on rising edges. // If not, the MSB will not be read on those registers (always 1), // and all other bits will be shifted by 1 towards the LSB. - if (clockHighFirst > 0) digitalWrite(clockPin, HIGH); + if (bitRead(settings, 1)) digitalWrite(clockPin, HIGH); // Latch high to read parallel state, then low again to stop. digitalWrite(latchPin, HIGH); @@ -58,11 +64,17 @@ void Dino::shiftRead(int latchPin, int len, byte dataPin, byte clockPin, byte cl // Start with just pin number and : for now. stream->print(latchPin); stream->print(':'); + byte reading = 0; for (int i = 1; i <= len; i++) { - // Read a single byte from the register. - byte reading = shiftIn(dataPin, clockPin, LSBFIRST); - + + // Read a single byte from the register. + if (bitRead(settings, 0)) { + reading = shiftIn(dataPin, clockPin, MSBFIRST); + } else { + reading = shiftIn(dataPin, clockPin, LSBFIRST); + } + // Print it, then a comma or \n if it's the last byte. stream->print(reading); stream->print((i==len) ? '\n' : ','); diff --git a/test/api/shift_io_test.rb b/test/api/shift_io_test.rb index 45c71174..da3e3e0c 100644 --- a/test/api/shift_io_test.rb +++ b/test/api/shift_io_test.rb @@ -15,49 +15,55 @@ def board end def test_shift_settings - settings1 = pack :uint8, [4, 5, 1] - settings2 = pack :uint8, [6, 7, 0] - assert_equal board.shift_settings(4, 5, true), settings1 - assert_equal board.shift_settings(6, 7), settings2 + settings1 = pack :uint8, [6, 7, 0b00] + settings2 = pack :uint8, [4, 5, 0b01] + settings3 = pack :uint8, [4, 5, 0b10] + settings4 = pack :uint8, [4, 5, 0b11] + + assert_equal board.shift_settings(6, 7), settings1 + assert_equal board.shift_settings(4, 5, :msbfirst), settings2 + assert_equal board.shift_settings(4, 5, nil, true), settings3 + assert_equal board.shift_settings(4, 5, :msbfirst, true), settings4 end def test_shift_write - settings = board.shift_settings(4, 5) + settings1 = board.shift_settings(4, 5) + settings2 = board.shift_settings(4, 5, :msbfirst) bytes1 = pack :uint8, [1,2,3] bytes2 = pack :uint8, [25] mock = MiniTest::Mock.new - mock.expect :call, nil, [Dino::Message.encode(command: 21, pin: 3, value: 3, aux_message: settings + bytes1)] - mock.expect :call, nil, [Dino::Message.encode(command: 21, pin: 3, value: 1, aux_message: settings + bytes2)] + mock.expect :call, nil, [Dino::Message.encode(command: 21, pin: 3, value: 3, aux_message: settings1 + bytes1)] + mock.expect :call, nil, [Dino::Message.encode(command: 21, pin: 3, value: 1, aux_message: settings2 + bytes2)] board.stub(:write, mock) do board.shift_write(3, 4, 5, [1,2,3]) - board.shift_write(3, 4, 5, 25) + board.shift_write(3, 4, 5, 25, bit_order: :msbfirst) end end def test_shift_read settings1 = board.shift_settings(4, 5) - settings2 = board.shift_settings(4, 5, true) + settings2 = board.shift_settings(4, 5, nil, true) + settings3 = board.shift_settings(4, 5, :msbfirst, true) mock = MiniTest::Mock.new mock.expect :call, nil, [Dino::Message.encode(command: 22, pin: 3, value: 8, aux_message: settings1)] mock.expect :call, nil, [Dino::Message.encode(command: 22, pin: 3, value: 4, aux_message: settings2)] + mock.expect :call, nil, [Dino::Message.encode(command: 22, pin: 3, value: 4, aux_message: settings3)] board.stub(:write, mock) do board.shift_read(3, 4, 5, 8) board.shift_read(3, 4, 5, 4, preclock_high: true) + board.shift_read(3, 4, 5, 4, bit_order: :msbfirst, preclock_high: true) end end def test_shift_listen - settings1 = board.shift_settings(4, 5) - settings2 = board.shift_settings(4, 5, true) + settings = board.shift_settings(4, 5, :msbfirst, true) mock = MiniTest::Mock.new - mock.expect :call, nil, [Dino::Message.encode(command: 23, pin: 3, value: 8, aux_message: settings1)] - mock.expect :call, nil, [Dino::Message.encode(command: 23, pin: 3, value: 4, aux_message: settings2)] - + mock.expect :call, nil, [Dino::Message.encode(command: 23, pin: 3, value: 4, aux_message: settings)] + board.stub(:write, mock) do - board.shift_listen(3, 4, 5, 8) - board.shift_listen(3, 4, 5, 4, preclock_high: true) + board.shift_listen(3, 4, 5, 4, preclock_high: true, bit_order: :msbfirst) end end diff --git a/test/components/register/shift_in_test.rb b/test/components/register/shift_in_test.rb index 68618214..8bf9176c 100644 --- a/test/components/register/shift_in_test.rb +++ b/test/components/register/shift_in_test.rb @@ -31,18 +31,28 @@ def test_rising_clock end def test_read - mock = MiniTest::Mock.new.expect :call, nil, [8, 11, 12, 2], preclock_high: true + mock = MiniTest::Mock.new + mock.expect :call, nil, [8, 11, 12, 2], preclock_high: false, bit_order: :msbfirst + mock.expect :call, nil, [8, 11, 12, 2], preclock_high: true, bit_order: :lsbfirst board.stub(:shift_read, mock) do - new_part = Dino::Components::Register::ShiftIn.new(options.merge(bytes: 2, rising_clock: true)) + new_part = Dino::Components::Register::ShiftIn.new options.merge(bytes: 2) + new_part.read + + new_part = Dino::Components::Register::ShiftIn.new options.merge(bytes: 2, rising_clock: true, bit_order: :lsbfirst) new_part.read end mock.verify end def test_listen - mock = MiniTest::Mock.new.expect :call, nil, [8, 11, 12, 2], preclock_high: true + mock = MiniTest::Mock.new + mock.expect :call, nil, [8, 11, 12, 2], preclock_high: false, bit_order: :msbfirst + mock.expect :call, nil, [8, 11, 12, 2], preclock_high: true, bit_order: :lsbfirst board.stub(:shift_listen, mock) do - new_part = Dino::Components::Register::ShiftIn.new(options.merge(bytes: 2, rising_clock: true)) + new_part = Dino::Components::Register::ShiftIn.new options.merge(bytes: 2) + new_part.listen + + new_part = Dino::Components::Register::ShiftIn.new options.merge(bytes: 2, rising_clock: true, bit_order: :lsbfirst) new_part.listen end mock.verify From 5ede312eb930f2d4d508fb26bc93b927773b7716 Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 13 Feb 2023 17:49:10 -0400 Subject: [PATCH 238/296] Change target for Due to 'sam3x' and update changelog --- CHANGELOG.md | 24 ++++++++++++------------ lib/dino_cli/targets.rb | 2 +- lib/dino_cli/targets.txt | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a239b45..37ceb733 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,15 +6,15 @@ - The `dino sketch` shell command now accepts a `--target` argument. It includes/excludes features to tailor the sketch for different boards/chips. Run `dino targets` for more info. - ATmega Based Boards (default) (`--target mega`): - - This is the default sketch if `--target` isn't specified, and works for many of the common Arduino products, like the Uno, Nano, Leonardo and Mega. + - This is the default sketch if `--target` isn't specified, and works for Arduino (and other) products based on the ATmega AVR chips, like the Uno, Nano, Leonardo and Mega. - ESP8266 (`--target esp8266`): - - Works with `Dino::Board.new`, but calling `Dino::Board::ESP8266.new` instead allows pins to be referred to as any of `'GPIO4'`, `4`, or `'D2'`, as printed on dev boards like the NodeMCU or WeMos, with the printed names mapping to the correct GPIO. + - Works with `Dino::Board.new`, but calling `Dino::Board::ESP8266.new` instead allows pins to be referred to as any of `'GPIO4'`, `4`, or `'D2'`, as printed on the NodeMCU dev board. When in doubt, look up your board's GPIO mapping and use those numbers instead. - Works with either built in WiFi or Serial. - WiFi version supports OTA (over-the-air) update in the Arduino IDE. Initial flash must still be done via serial. - **Note**: SoftwareSerial is incompatible with the ESP8266. LiquidCrystal (LCD) compiles for the ESP8266, but does not work. Both of these are excluded. -- Arduino Due (`--target due`) : +- Arduino Due (`--target sam3x`) : - Up to 12-bit analog in/out. Pass a `bits:` option to `Board#new` to set resolution for both. - DAC support. Refer to DAC pins as `'DAC0'`, `'DAC1'`, just as labeled on the board. Call `#analog_write` or just `#write` on an `AnalogOutput` component that uses the pin. - Uses the native ARM serial port by default. Configurable in sketch to use programming port. @@ -28,6 +28,8 @@ ### New Components +- `TxRx::TCP` allows communication with the board over an IP network, instead of serial connection. Tested on Arduino Uno Ethernet Shield (Wiznet W5100), and ESP8266 native WiFi. Should work on Uno WiFi shield, but is **untested**. WiFi must be configured before flashing. Instad of `dino sketch serial`, use `dino sketch wifi`. + - Hitachi HD44780 LCD support. _Uses Arduino `LiquidCrystal` library._ - Seven Segment Display support. _Ruby implementation as multiple LEDs._ @@ -38,7 +40,7 @@ - SoftwareSerial. _Uses Arduino `SoftSerial` library._ (**write only / experimental**) -- Rotary encoder support. _Uses polling method @ 1ms interval._ **WARNING**: Not suitable for high speeds or precise position needs. It will definitely miss steps. Sufficient for using rotary knobs for user input. +- Rotary encoder support. _Uses polling method @ 1ms interval._ **WARNING**: Not suitable for high speed or precise position needs. It will definitely miss steps. Sufficient for rotary knobs as user input. - DHT11 / DHT 21 (AM2301) / DHT22 temperature and relative humidity sensor support. _Custom implementation where input pulses are measured on the board, then decoded in Ruby._ @@ -62,21 +64,19 @@ - Board EEPROM support. _Uses Arduino `EEPROM` library._ -- WiFi shield support (**untested**) - ### Changed Components - Servos can now be connected to arbitrary pins as long as they are supported by the board. - Digital and analog listeners now have dividers on a per-pin basis. - Timing is based on a 1000 microsecond tick being counted by the board. - - Call `#listen` with a value as the first argument. Eg. `sensor.listen(64)` will tell the board to send us that specific sensor's state every 64 ticks (~64ms) or around 16 times per second, without affecting other components' rates. + - Call `#listen` with a value as the first argument. Eg. `analog_sensor.listen(64)` will tell the board to send us that specific sensor's state every 64 ticks (~64ms) or around 16 times per second, without affecting other components' rates. - Valid dividers are: `1, 2, 4, 8, 16, 32, 64, 128`. - Defaults are same as before: `4` for digital, `16` for analog. ### Hardware Abstraction - `MultiPin` abstraction for components using more than one pin: - - Components connecting to more than 1 pin, like an RGB LED or rotary encoder, are now modeled as `MultiPin` and contain multiple `SinglePin` proxy components. An `RGBLed` is built from 3 `AnalogOutput`s, for example, one for each color, connected to a separate pin. + - Components connecting to more than 1 pin, like an RGB LED or rotary encoder, are now modeled as `MultiPin` and contain multiple `SinglePin` `proxies`. An `RGBLed` is built from 3 `AnalogOutput`s, for example, one for each color, connected to a separate pin. - `MultiPin` implements a shortcut class method `proxy_pins`. Proxying a pin allows subcomponent pin numbers to be given as a hash when initializing an instance of a `MultiPin` component. Eg: `{red: 9, green: 10, blue: 11}` given as the `pins:` option for `RGBLed#new`. - When initialized, subcomponents corresponding to the proxied pins are automatically created. They're stored in `#proxies` and `attr_reader` methods are created for each, corresponding to their key in the `pins:` hash. Eg: `RGBLed#green` and `RGBLed#proxies[:green]` both give the `AnalogOutput` component that represents the green LED inside the RGB LED, connected to pin 10. @@ -98,22 +98,22 @@ - Each key represents an array of callbacks, so multiple callbacks can share the same key. - Calling `#remove_callbacks` with a key empties that array. Calling with no key removes **all** callbacks for the component. - `#pre_callback_filter` is defined in the `Callbacks` module. The return value of this method is what is given to the component's callbacks and to update its `@state`. By default, it returns whatever was given from the board. - - Override `#pre_callback_filter` to process data before giving it to callbacks and `@state`. Eg: given raw bytes from a DHT sensor, process them into a hash containing `:celsius`, `: fahrenheit` and `:humidity` values. That hash is given to to callbacks and `#update_state` instead of the original string of values. - - `#update_state` is defined in the `Callbacks` module. It is called after all callbacks are run and given the return value of `#pre_callback_filter`. By default, it sets `#state=` to that value. + - Override `#pre_callback_filter` to process data before giving it to callbacks and `@state`. Eg: given raw bytes from a DHT sensor, process them into a hash containing `:celsius`, `: fahrenheit` and `:humidity` values. That hash is given to to callbacks and `#update_state` instead of the original string of raw bytes. + - `#update_state` is defined in the `Callbacks` module. It is called after all callbacks are run and given the return value of `#pre_callback_filter`. By default, it sets `@state=` to the value given. - Override it if updating `@state` is more complex than this, but be sure to either use `#state=` only once, or wrap the operation in `@state_mutex.synchronize`. - Input components no longer automatically start listening when created, since there are more options for reading inputs. - `DigitalInput` and its subclasses are the exception to this. They automatically listen, since there is little advantage to other modes. - Input components can have any combination of `#read`, `#poll` and `#listen` methods now, coming from `Reader`, `Poller`, and `Listener` respectively, inside `Mixins`. - - `#read` sends a single read command by calling `#_read`, and blocks the main thread, until `data` is received from `#pre_callback_filter`. When received, any block given to `#read` will run in the "update" thread once and be removed immediately. `#read` then stops blocking the main thread and returns `data`. + - `#read` sends a single read command by calling `#_read`, and blocks the main thread, until `data` is received from `#pre_callback_filter`. When received, any block that was given to `#read` will run once as a callback and be removed immediately. `#read` then stops blocking the main thread and returns `data`. - `#poll` requires an interval (in seconds) as its first argument. It starts a new thread, and keeps calling `#_read` in it, at the given interval. `#poll` does not block the main thread, and does not return a value. A block given will be added as a callback inside the `:poll` key. - `#listen` adds its block as a callback inside the `:listen` key, calls `#_listen` and returns immediately. - `#stop` stops polling **and** listening. It also **removes all callbacks** in the **`:poll` and `:listen` keys** (callbacks added as blocks when polling or listening). ### Minor Changes - Serial communication now uses the [`rubyserial`](https://github.com/hybridgroup/rubyserial) gem instead of [`serialport`](https://github.com/hparra/ruby-serialport). -- Switched from `respec` to `minitest` for testing. +- Switched from `rspec` to `minitest` for testing. - Added more useful information and errors during the connect & handshake process. - Extended message syntax so the Arduino can receive arbitrary length messages, including binary. - Created `Dino::Message` class to handle message construction. diff --git a/lib/dino_cli/targets.rb b/lib/dino_cli/targets.rb index 5d6cc1bd..2d18ddfe 100644 --- a/lib/dino_cli/targets.rb +++ b/lib/dino_cli/targets.rb @@ -14,7 +14,7 @@ class DinoCLI::Generator mega168: [:core, :servo, :shift,:tone, :spi, :i2c], # ARM includes everytyhing except specific incompatibilities. - arm: STANDARD_PACKAGES - [:serial, :tone, :ir_out], + sam3x: STANDARD_PACKAGES - [:serial, :tone, :ir_out], # A surprising amount "just works" on the ESP, notably not LCD. esp8266: STANDARD_PACKAGES - [:lcd, :serial, :ir_out] + [:ir_out_esp8266], diff --git a/lib/dino_cli/targets.txt b/lib/dino_cli/targets.txt index 394d12e1..1fcf52e3 100644 --- a/lib/dino_cli/targets.txt +++ b/lib/dino_cli/targets.txt @@ -58,7 +58,7 @@ These limits apply as long as an Atmega168 is selected in the Arduino IDE, regardless of the options passed to this sketch generator. - arm + sam3x This is the same as mega, but omits libraries specifically known to be incompatible with the Atmel SAMD (ARM Cortex M0) chips. Use this when From ceb33996d2c5879317a0dd0c18a3dbdd834b6ef2 Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 13 Feb 2023 22:50:36 -0400 Subject: [PATCH 239/296] Tests for SPI input and output registers --- lib/dino/components/i2c/slave.rb | 2 +- lib/dino/components/register/input.rb | 9 ++-- lib/dino/components/register/output.rb | 17 ++++--- lib/dino/components/register/shift_in.rb | 6 ++- lib/dino/components/register/shift_out.rb | 2 +- lib/dino/components/register/spi_in.rb | 11 ++--- lib/dino/components/register/spi_out.rb | 7 ++- lib/dino/components/setup/base.rb | 2 + test/components/register/shift_out_test.rb | 1 - test/components/register/spi_in_test.rb | 54 ++++++++++++++++++++++ test/components/register/spi_out_test.rb | 38 +++++++++++++++ 11 files changed, 126 insertions(+), 23 deletions(-) create mode 100644 test/components/register/spi_in_test.rb create mode 100644 test/components/register/spi_out_test.rb diff --git a/lib/dino/components/i2c/slave.rb b/lib/dino/components/i2c/slave.rb index e0b50419..790f06a5 100644 --- a/lib/dino/components/i2c/slave.rb +++ b/lib/dino/components/i2c/slave.rb @@ -5,7 +5,7 @@ class Slave include Mixins::BusSlave include Mixins::Reader - def initialize(options) + def before_initialize(options) super(options) @repeated_start = options[:repeated_start] || false end diff --git a/lib/dino/components/register/input.rb b/lib/dino/components/register/input.rb index 3a7f670d..76d85ec1 100644 --- a/lib/dino/components/register/input.rb +++ b/lib/dino/components/register/input.rb @@ -6,9 +6,9 @@ module Input include Mixins::Callbacks attr_reader :bytes - - def after_initialize(options={}) - super(options) if defined?(super) + + def before_initialize(options={}) + super(options) # # To use the register as a board proxy, we need to know how many # bytes there are and map each bit to a virtual pin. @@ -28,7 +28,10 @@ def after_initialize(options={}) # @reading_pins = Array.new(@bytes*8) {|i| false} @listening_pins = Array.new(@bytes*8) {|i| false} + end + def after_initialize(options={}) + super(options) enable_proxy end diff --git a/lib/dino/components/register/output.rb b/lib/dino/components/register/output.rb index 64ca7adf..695a514b 100644 --- a/lib/dino/components/register/output.rb +++ b/lib/dino/components/register/output.rb @@ -3,9 +3,11 @@ module Components module Register module Output include Setup::Base - - def after_initialize(options={}) - super(options) if defined?(super) + + attr_reader :bytes + + def before_initialize(options={}) + super(options) # # To use the register as a board proxy, we need to know how many # bytes there are and map each bit to a virtual pin. @@ -17,9 +19,8 @@ def after_initialize(options={}) # When used as a board proxy, store the state of each register # pin as a 0 or 1 in an array that is (@bytes * 8) long. Zero out to start. # - @state = Array.new(@bytes*8) { 0 } - write_state - + self.state = Array.new(@bytes*8) { 0 } + # # When used as a board proxy, only write sate if @write_delay seconds # have passed since this object last got input. Better for things like SSDs @@ -27,6 +28,10 @@ def after_initialize(options={}) # @write_delay = options[:write_delay] || 0.005 end + + def after_initialize(options={}) + write_state + end def write raise 'define #write in child class based on communication method' diff --git a/lib/dino/components/register/shift_in.rb b/lib/dino/components/register/shift_in.rb index 17074d65..2a217875 100644 --- a/lib/dino/components/register/shift_in.rb +++ b/lib/dino/components/register/shift_in.rb @@ -15,10 +15,14 @@ class ShiftIn data: Basic::DigitalInput, latch: Register::Select - def after_initialize(options) + def before_initialize(options={}) super(options) self.rising_clock = options[:rising_clock] || false self.bit_order = options[:bit_order] || :msbfirst + end + + def after_initialize(options={}) + super(options) bubble_callbacks end diff --git a/lib/dino/components/register/shift_out.rb b/lib/dino/components/register/shift_out.rb index 67a3ad3f..ef1ab19d 100644 --- a/lib/dino/components/register/shift_out.rb +++ b/lib/dino/components/register/shift_out.rb @@ -15,7 +15,7 @@ class ShiftOut data: Basic::DigitalOutput, latch: Register::Select - def after_initialize(options) + def before_initialize(options={}) super(options) self.bit_order = options[:bit_order] || :lsbfirst end diff --git a/lib/dino/components/register/spi_in.rb b/lib/dino/components/register/spi_in.rb index 109349f8..586b7ec0 100644 --- a/lib/dino/components/register/spi_in.rb +++ b/lib/dino/components/register/spi_in.rb @@ -12,20 +12,19 @@ class SPIIn < Select attr_reader :spi_mode, :frequency, :bit_order - def after_initialize(options) - super(options) if defined?(super) - + def initialize(options) + super(options) @spi_mode = options[:spi_mode] || 0 - @frequency = options[:frequency] || 3000000 + @frequency = options[:frequency] || 1000000 @bit_order = options[:bit_order] || :msbfirst end def read - board.spi_transfer(pin, mode: spi_mode, frequency: frequency, read: @bytes) + board.spi_transfer(pin, mode: @spi_mode, frequency: frequency, read: @bytes, bit_order: @bit_order) end def listen - board.spi_listen(pin, mode: spi_mode, frequency: frequency, read: @bytes) + board.spi_listen(pin, mode: @spi_mode, frequency: frequency, read: @bytes, bit_order: @bit_order) end def stop diff --git a/lib/dino/components/register/spi_out.rb b/lib/dino/components/register/spi_out.rb index e47fb8eb..3740429c 100644 --- a/lib/dino/components/register/spi_out.rb +++ b/lib/dino/components/register/spi_out.rb @@ -12,11 +12,10 @@ class SPIOut < Select attr_reader :spi_mode, :frequency, :bit_order - def after_initialize(options) - super(options) if defined?(super) - + def before_initialize(options) + super(options) @spi_mode = options[:spi_mode] || 0 - @frequency = options[:frequency] || 3000000 + @frequency = options[:frequency] || 1000000 @bit_order = options[:bit_order] || :lsbfirst end diff --git a/lib/dino/components/setup/base.rb b/lib/dino/components/setup/base.rb index 06d7b161..3fad5fd4 100644 --- a/lib/dino/components/setup/base.rb +++ b/lib/dino/components/setup/base.rb @@ -7,6 +7,7 @@ module Base def initialize(options={}) @state = nil @state_mutex = Mutex.new + before_initialize(options) initialize_board(options) initialize_pins(options) register @@ -42,6 +43,7 @@ def unregister # Setup::Base only requires a board. # Include modules from Setup or override this to use pins. # + def before_initialize(options={}) ; end def initialize_pins(options={}) ; end alias :initialize_pin :initialize_pins diff --git a/test/components/register/shift_out_test.rb b/test/components/register/shift_out_test.rb index 5ad79f5c..ff6b5ffd 100644 --- a/test/components/register/shift_out_test.rb +++ b/test/components/register/shift_out_test.rb @@ -20,7 +20,6 @@ def test_proxies end def test_write - # mock = MiniTest::Mock.new.expect :call, nil, ["21.8.1.#{[11,12,0,255,127].pack('C*')}\n"] new_part = Dino::Components::Register::ShiftOut.new(options.merge(bytes: 2)) mock = MiniTest::Mock.new.expect :call, nil, [8, 11, 12, [255,127]] board.stub(:shift_write, mock) do diff --git a/test/components/register/spi_in_test.rb b/test/components/register/spi_in_test.rb new file mode 100644 index 00000000..199b95f2 --- /dev/null +++ b/test/components/register/spi_in_test.rb @@ -0,0 +1,54 @@ +require 'test_helper' + +class RegisterSPIInTest < Minitest::Test + def board + @board ||= BoardMock.new + end + + def options + { board: board, pin: 9, frequency: 800000, spi_mode: 2, bit_order: :lsbfirst, bytes: 2 } + end + + def part + @part ||= Dino::Components::Register::SPIIn.new(options) + end + + def test_defaults + part = Dino::Components::Register::SPIIn.new board: board, pin: 9 + assert_equal part.frequency, 1000000 + assert_equal part.spi_mode, 0 + assert_equal part.bit_order, :msbfirst + assert_equal part.bytes, 1 + end + + def test_options + assert_equal part.frequency, 800000 + assert_equal part.spi_mode, 2 + assert_equal part.bit_order, :lsbfirst + assert_equal part.bytes, 2 + end + + def test_read + mock = MiniTest::Mock.new.expect :call, nil, [9], mode: 2, frequency: 800000, read: 2, bit_order: :lsbfirst + board.stub(:spi_transfer, mock) do + part.read + end + mock.verify + end + + def test_listen + mock = MiniTest::Mock.new.expect :call, nil, [9], mode: 2, frequency: 800000, read: 2, bit_order: :lsbfirst + board.stub(:spi_listen, mock) do + part.listen + end + mock.verify + end + + def test_stop + mock = MiniTest::Mock.new.expect :call, nil, [9] + board.stub(:spi_stop, mock) do + part.stop + end + mock.verify + end +end diff --git a/test/components/register/spi_out_test.rb b/test/components/register/spi_out_test.rb new file mode 100644 index 00000000..6a2655f1 --- /dev/null +++ b/test/components/register/spi_out_test.rb @@ -0,0 +1,38 @@ +require 'test_helper' + +class RegisterSPIOutTest < Minitest::Test + def board + @board ||= BoardMock.new + end + + def options + { board: board, pin: 9, frequency: 800000, spi_mode: 2, bit_order: :msbfirst } + end + + def part + @part ||= Dino::Components::Register::SPIOut.new(options) + end + + def test_defaults + part = Dino::Components::Register::SPIOut.new board: board, pin: 9 + assert_equal part.frequency, 1000000 + assert_equal part.spi_mode, 0 + assert_equal part.bit_order, :lsbfirst + end + + def test_options + assert_equal part.frequency, 800000 + assert_equal part.spi_mode, 2 + assert_equal part.bit_order, :msbfirst + end + + def test_write + mock = MiniTest::Mock.new + mock.expect :call, nil, [9], mode: 2, frequency: 800000, write: [0], bit_order: :msbfirst + mock.expect :call, nil, [9], mode: 2, frequency: 800000, write: [255,127], bit_order: :msbfirst + board.stub(:spi_transfer, mock) do + part.write(255,127) + end + mock.verify + end +end From cccb96c4b5a05ac6431b2a730069c93ad408f322 Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 13 Feb 2023 22:59:20 -0400 Subject: [PATCH 240/296] Handshake checks ACK, so it doesn't raise from old data in serial buffer --- lib/dino/tx_rx/base.rb | 3 +-- lib/dino/tx_rx/flow_control.rb | 7 +------ lib/dino/tx_rx/handshake.rb | 17 +++++++++++++---- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index 58252930..5faf665c 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -48,8 +48,7 @@ def start_read end def stop_read - return nil if @thread.nil? - Thread.kill(@thread) + Thread.kill(@thread) if @thread @thread = nil end diff --git a/lib/dino/tx_rx/flow_control.rb b/lib/dino/tx_rx/flow_control.rb index 1010dcb1..f21cf1d0 100644 --- a/lib/dino/tx_rx/flow_control.rb +++ b/lib/dino/tx_rx/flow_control.rb @@ -64,12 +64,7 @@ def reset_flow_control def read_and_parse line = read - if line && line.match(/\AACK:/) - # A HandshakeAttempt is observing. Also pass self so it can stop. - changed && notify_observers(self, line.split(":", 2)[1]) - # ACK: means the board reset its counter. Reset ours. - @transit_mutex.synchronize { @transit_bytes = 0 } - elsif line && line.match(/\ARCV:/) + if line && line.match(/\ARCV:/) remove_transit_bytes(line.split(/:/)[1].to_i) elsif line parse(line) diff --git a/lib/dino/tx_rx/handshake.rb b/lib/dino/tx_rx/handshake.rb index e0a77764..7b060407 100644 --- a/lib/dino/tx_rx/handshake.rb +++ b/lib/dino/tx_rx/handshake.rb @@ -7,10 +7,11 @@ def initialize @result = nil end - def update(sender, result) - sender.delete_observer(self) - @result = result - @acknowledged = true + def update(line) + if line.match(/\AACK:/) + @result = line.split(":", 2)[1] + @acknowledged = true + end end end @@ -30,6 +31,14 @@ def handshake loop do if attempt.acknowledged puts "Acknowledged. Hardware ready...\n\n" + + # Stop the handshake attempt from observing. + self.delete_observer(attempt) + + # We reset the board, so reset the transit mutex. + @transit_mutex.synchronize { @transit_bytes = 0 } + + # Return the data part of the ACK line. return attempt.result end end From 2cb62434f369e550a6a9e3b849789531664a729b Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 13 Feb 2023 23:51:57 -0400 Subject: [PATCH 241/296] Add CLI instructions for vendor libraries --- lib/dino_cli/generator.rb | 14 ++++++++++++++ lib/dino_cli/helper.rb | 6 ++++++ lib/dino_cli/missing_files.txt | 20 ++++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 lib/dino_cli/missing_files.txt diff --git a/lib/dino_cli/generator.rb b/lib/dino_cli/generator.rb index a386aa34..119bfdb6 100644 --- a/lib/dino_cli/generator.rb +++ b/lib/dino_cli/generator.rb @@ -3,6 +3,10 @@ class DinoCLI::Generator require "dino_cli/packages" require "dino_cli/targets" require "dino/version" + + require "dino_cli/helper" + include DinoCLI::Helper + attr_accessor :options def initialize(options={}) @@ -29,6 +33,7 @@ def self.run!(options={}) def read @packages = PACKAGES + files_missing = false # Replace each filepath with a hash containing the filepath and contents. @packages.each_key do |k| @@ -42,8 +47,16 @@ def read contents = "#include \"DinoDefines.h\"\n#ifdef #{directive}\n" << contents << "\n#endif\n" end { path: f, contents: contents } + rescue + files_missing = true + puts "File missing: #{f}" end end + + # Show the text file when vendor files are missing + if files_missing + missing_files + end # Read in the sketch itself. @sketch = File.read File.join(options[:src_dir], "#{options[:src_sketch_name]}.ino") @@ -117,6 +130,7 @@ def write # Then write the file contents to the destination path. if (targeted && !excluded) package[:files].each do |file| + next unless file dest_path = File.join(output_dir, file[:path].split('/')[-1]) File.open(dest_path, 'w') { |f| f.write file[:contents] } end diff --git a/lib/dino_cli/helper.rb b/lib/dino_cli/helper.rb index 3ea1b46f..683b4a88 100644 --- a/lib/dino_cli/helper.rb +++ b/lib/dino_cli/helper.rb @@ -20,4 +20,10 @@ def targets $stderr.puts exit(2) end + + def missing_files + text = File.read(File.join(@options[:cli_dir], "missing_files.txt")) + $stderr.print text + $stderr.puts + end end diff --git a/lib/dino_cli/missing_files.txt b/lib/dino_cli/missing_files.txt new file mode 100644 index 00000000..ab6e61c8 --- /dev/null +++ b/lib/dino_cli/missing_files.txt @@ -0,0 +1,20 @@ + + If all the missing files are from the vendor directory, you have 2 options: + + 1) If you cloned the dino repo with git, please run: + + git submodule update --init --recursive + + Then try generating the sketch again. + This command clones the needed repos into dino's src/vendor folder. + + 2) If you installed the gem via gem install, you can ignore missing vendor + files and install the libraries in your Arduino app instead. + + The libraries used are: + + IRremote v2.3.2 + IRremoteESP8266 v2.3.2 + + In the Arduino app's library manager, search for them by name. Please + install the versions listed here. Newer or older versions may be incompatible. From 1f5fbeda418dbeb3f626847cd6e862b9a65a9cb7 Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 14 Feb 2023 11:53:54 -0400 Subject: [PATCH 242/296] Test base classes for I2C bus devices --- lib/dino/components/i2c/ds3231.rb | 2 +- lib/dino/components/i2c/slave.rb | 2 +- lib/dino/components/mixins/bus_slave.rb | 11 ++-- test/components/i2c/bus_test.rb | 77 +++++++++++++++++++++++++ test/components/i2c/slave_test.rb | 53 +++++++++++++++++ 5 files changed, 136 insertions(+), 9 deletions(-) create mode 100644 test/components/i2c/bus_test.rb create mode 100644 test/components/i2c/slave_test.rb diff --git a/lib/dino/components/i2c/ds3231.rb b/lib/dino/components/i2c/ds3231.rb index ef2a36e3..bc8a77ed 100644 --- a/lib/dino/components/i2c/ds3231.rb +++ b/lib/dino/components/i2c/ds3231.rb @@ -23,7 +23,7 @@ def time=(time) # Time data starts at register 0 and is 7 bytes long. def _read - read_bytes(0, 7) + super(0, 7) end # Convert raw bytes from the I2C bus into a Ruby Time object. diff --git a/lib/dino/components/i2c/slave.rb b/lib/dino/components/i2c/slave.rb index 790f06a5..60b86487 100644 --- a/lib/dino/components/i2c/slave.rb +++ b/lib/dino/components/i2c/slave.rb @@ -16,7 +16,7 @@ def write(bytes=[]) bus.write(address, bytes, repeated_start: repeated_start) end - def read_bytes(register, num_bytes=1) + def _read(register, num_bytes=1) bus.read(address, register, num_bytes, repeated_start: repeated_start) end end diff --git a/lib/dino/components/mixins/bus_slave.rb b/lib/dino/components/mixins/bus_slave.rb index 2cc5fc7c..ae285edb 100644 --- a/lib/dino/components/mixins/bus_slave.rb +++ b/lib/dino/components/mixins/bus_slave.rb @@ -7,19 +7,16 @@ module BusSlave attr_reader :address alias :bus :board - def initialize(options={}) + def before_initialize(options={}) options[:board] ||= options[:bus] - super(options) - end - - def after_initialize(options={}) - super(options) - + unless options[:address] raise ArgumentError, 'missing Slave device address; try Bus#search first' end @address = options[:address] + + super(options) end def atomically(&block) diff --git a/test/components/i2c/bus_test.rb b/test/components/i2c/bus_test.rb new file mode 100644 index 00000000..4ad2e07f --- /dev/null +++ b/test/components/i2c/bus_test.rb @@ -0,0 +1,77 @@ +require 'test_helper' + +class I2CBusTest < MiniTest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= Dino::Components::I2C::Bus.new(board: board, pin:5) + end + + def slave + @slave ||= Dino::Components::I2C::Slave.new(bus: part, address: 0x30) + end + + def inject_read(lines, wait_for_callbacks = true) + Thread.new do + if wait_for_callbacks + sleep while board.components.empty? + sleep while board.components.first.callbacks.empty? + end + + [lines].flatten.each do |line| + board.update(line) + end + end + end + + def test_initialize + inject_read("5:") + assert_equal part.found_devices, [] + refute_nil part.callbacks[:bus_master] + end + + def test_search + inject_read("5:48:50") + + mock = MiniTest::Mock.new.expect :call, nil + board.stub(:i2c_search, mock) do + part + end + + mock.verify + assert_equal part.found_devices, [0x30, 0x32] + end + + def test_write + inject_read("5:48:50") + + mock = MiniTest::Mock.new.expect :call, nil, [0x30, [0x01, 0x02], some_option: true] + board.stub(:i2c_write, mock) do + part.write 0x30, [0x01, 0x02], some_option: true + end + mock.verify + end + + def test__read + inject_read("5:48:50") + + mock = MiniTest::Mock.new.expect :call, nil, [0x32, 0x03, 6, some_option: true] + board.stub(:i2c_read, mock) do + part._read 0x32, 0x03, 6, some_option: true + end + mock.verify + end + + def test_updates_slaves + inject_read("5:48,50") + mock = MiniTest::Mock.new.expect :call, nil, [[255, 127]] + + slave.stub(:update, mock) do + part.send(:update, "48-255,127") + part.send(:update, "50-128,0") + end + mock.verify + end +end diff --git a/test/components/i2c/slave_test.rb b/test/components/i2c/slave_test.rb new file mode 100644 index 00000000..194fea06 --- /dev/null +++ b/test/components/i2c/slave_test.rb @@ -0,0 +1,53 @@ +require 'test_helper' + +class I2SlaveTest < MiniTest::Test + def board + @board ||= BoardMock.new + end + + def bus + unless @bus + inject_read("5:48") + @bus ||= Dino::Components::I2C::Bus.new(board: board, pin:5) + else + @bus + end + end + + def part + @part ||= Dino::Components::I2C::Slave.new(bus: bus, address: 0x30) + end + + def inject_read(lines, wait_for_callbacks = true) + Thread.new do + if wait_for_callbacks + sleep while board.components.empty? + sleep while board.components.first.callbacks.empty? + end + + [lines].flatten.each do |line| + board.update(line) + end + end + end + + def test_write_and_repeated_start + part.repeated_start = true + + mock = MiniTest::Mock.new.expect :call, nil, [0x30, [1,2]], repeated_start: true + bus.stub(:write, mock) do + part.write [1,2] + end + end + + def test__read_and_repeated_start + part.repeated_start = true + + inject_read("5:48-127,127,127,127,127,127") + + mock = MiniTest::Mock.new.expect :call, nil, [0x30, 0x03, 6], repeated_start: true + bus.stub(:write, mock) do + part._read(0, 6) + end + end +end From 212f6a56122f79696b6274934411f3ff0b37219e Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 14 Feb 2023 14:40:24 -0400 Subject: [PATCH 243/296] Tests for DS3231 real time clock over ITC --- lib/dino/components/i2c/ds3231.rb | 31 ++++++++------ test/components/i2c/ds3231_test.rb | 68 ++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 12 deletions(-) create mode 100644 test/components/i2c/ds3231_test.rb diff --git a/lib/dino/components/i2c/ds3231.rb b/lib/dino/components/i2c/ds3231.rb index bc8a77ed..8bdcd233 100644 --- a/lib/dino/components/i2c/ds3231.rb +++ b/lib/dino/components/i2c/ds3231.rb @@ -4,21 +4,13 @@ module I2C class DS3231 < Slave require 'bcd' - # Set the time. + # Write start register 0x00, then bytes to set time. def time=(time) - bytes = [ 0, - BCD.decode(time.sec), - BCD.decode(time.min), - BCD.decode(time.hour), - BCD.decode(time.strftime('%u').to_i), - BCD.decode(time.day), - BCD.decode(time.month), - BCD.decode(time.year - 1970) ] - write(bytes) + write [0, time_to_bcd(time)] time end - # Read the time. + # Do a blocking read when #time is called. alias :time :read # Time data starts at register 0 and is 7 bytes long. @@ -26,8 +18,23 @@ def _read super(0, 7) end - # Convert raw bytes from the I2C bus into a Ruby Time object. def pre_callback_filter(bytes) + bcd_to_time(bytes) + end + + # Convert Time object to 7 byte BCD sequence. + def time_to_bcd(time) + [ BCD.decode(time.sec), + BCD.decode(time.min), + BCD.decode(time.hour), + BCD.decode(time.strftime('%u').to_i), + BCD.decode(time.day), + BCD.decode(time.month), + BCD.decode(time.year - 1970) ] + end + + # Convert 7 byte BCD sequence to Time object. + def bcd_to_time(bytes) t = bytes.map { |b| BCD.encode(b) } Time.new t[6] + 1970, t[5], t[4], t[2], t[1], t[0] end diff --git a/test/components/i2c/ds3231_test.rb b/test/components/i2c/ds3231_test.rb new file mode 100644 index 00000000..88285333 --- /dev/null +++ b/test/components/i2c/ds3231_test.rb @@ -0,0 +1,68 @@ +require 'test_helper' + +class DS3231Test < MiniTest::Test + def board + @board ||= BoardMock.new + end + + def bus + unless @bus + inject_read("5:104") + @bus ||= Dino::Components::I2C::Bus.new(board: board, pin:5) + else + @bus + end + end + + def part + @part ||= Dino::Components::I2C::DS3231.new(bus: bus, address: 0x68) + end + + def inject_read(lines, wait_for_callbacks = true) + Thread.new do + if wait_for_callbacks + sleep while board.components.empty? + sleep while board.components.first.callbacks.empty? + end + + [lines].flatten.each do |line| + board.update(line) + end + end + end + + def test_time_to_bcd + time = Time.new(2000, 1, 1, 0, 0, 0.0) + bytes = part.time_to_bcd(time) + assert_equal bytes, [0, 0, 0, 6, 1, 1, 48] + end + + def test_bcd_to_time + bytes = [0, 0, 0, 6, 1, 1, 48] + time = part.bcd_to_time(bytes) + assert_equal time, Time.new(2000, 1, 1, 0, 0, 0.0) + end + + def test_time= + mock = MiniTest::Mock.new.expect :call, nil, [[0, [0, 0, 0, 6, 1, 1, 48]]] + part.stub(:write, mock) do + part.time = Time.new(2000, 1, 1, 0, 0, 0.0) + end + end + + def test_read + bus; inject_read("5:104-0,0,0,6,1,1,48") + + mock = MiniTest::Mock.new.expect :call, nil, [part.address, 0x00, 7, repeated_start: false] + bus.stub(:_read, mock) do + part.time + end + end + + def test_pre_callback_filter + mock = MiniTest::Mock.new.expect :call, nil, [Time.new(2000, 1, 1, 0, 0, 0.0)] + part.stub(:update_state, mock) do + bus.send(:update, "104-0,0,0,6,1,1,48") + end + end +end From 277baa6d4c80c397dba9ea9662c81fd581822956 Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 14 Feb 2023 16:09:18 -0400 Subject: [PATCH 244/296] Update led example and start numbering examples --- examples/{led => 01-led}/led.png | Bin examples/01-led/led.rb | 57 +++++++++++++++++++++++++++++++ examples/led/led.rb | 23 ------------- 3 files changed, 57 insertions(+), 23 deletions(-) rename examples/{led => 01-led}/led.png (100%) create mode 100644 examples/01-led/led.rb delete mode 100644 examples/led/led.rb diff --git a/examples/led/led.png b/examples/01-led/led.png similarity index 100% rename from examples/led/led.png rename to examples/01-led/led.png diff --git a/examples/01-led/led.rb b/examples/01-led/led.rb new file mode 100644 index 00000000..66f66825 --- /dev/null +++ b/examples/01-led/led.rb @@ -0,0 +1,57 @@ +# +# Simple example that controls an LED. +# +require 'bundler/setup' +require 'dino' + +# If the board is connected with a USB cable, it's most likely serial over USB. +connection = Dino::TxRx::Serial.new + +# Initialize an object to represent the board, giving the connection. +board = Dino::Board.new(connection) + +# Initialize an object representing the LED, giving the board and pin. +# The on-board LED (marked "L") is connectd to pin 13 on most Arduinos. +led = Dino::Components::Led.new(pin: 13, board: board) + +# +# Now we can control the LED. +# Led is a DigitalOutput, so it can only have one of two states: +# 1 / high / on +# 0 / low / off +# +# We can write the state directly, or use named convenience methods. +# The 3 lines below all do the same thing: turn it on, wait half a second, turn it off. +# +led.write(1); sleep 0.5; led.write(0) +led.high; sleep 0.5; led.low +led.on; sleep 0.5; led.off + +# +# Calling #toggle will set it to the opposite of its current state. +# Keep it blinking for 3 more seconds using #toggle. +# +6.times do + led.toggle + sleep 0.5 +end + +# +# What if we want to blink in the background? +# +# led.blink runs in a separate thread, managed by the led object, +# and doesn't block the main thread. Give it the blink interval in seconds. +# +led.blink 0.5 + +# Wait while it blinks for 5 seconds. +sleep 5 + +# Call a method that sets the state (#write, #high, #low, #on, #off) to +# automatically stop the blink thread. +led.off +sleep 2 + +# Blink faster indefinitely. +led.blink 0.25 +sleep diff --git a/examples/led/led.rb b/examples/led/led.rb deleted file mode 100644 index f7b8b7a7..00000000 --- a/examples/led/led.rb +++ /dev/null @@ -1,23 +0,0 @@ -# -# This is a simple example to blink an led -# every half a second -# -require 'bundler/setup' -require 'dino' - -board = Dino::Board.new(Dino::TxRx::Serial.new) -led = Dino::Components::Led.new(pin: 13, board: board) - -# Start blinking every half second. -led.blink 0.5 - -# Wait for 5 seconds. #blink does not block. -sleep 5 - -# Calling #on implicitly stops #blink. -led.on -sleep 5 - -# Blink faster. -led.blink 0.25 -sleep From 56532b5212d4ee4263a42b91d36f269731060fb2 Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 14 Feb 2023 16:18:04 -0400 Subject: [PATCH 245/296] Arrange the first 5 examples into a mini tutorial --- examples/{button => 02-button}/button.png | Bin examples/{button => 02-button}/button.rb | 0 .../potentiometer.rb | 0 examples/04-pwm_led/pwm_led.rb | 0 examples/{rgb_led => 05-rgb_led}/rgb_led.rb | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename examples/{button => 02-button}/button.png (100%) rename examples/{button => 02-button}/button.rb (100%) rename examples/{potentiometer => 03-potentiometer}/potentiometer.rb (100%) create mode 100644 examples/04-pwm_led/pwm_led.rb rename examples/{rgb_led => 05-rgb_led}/rgb_led.rb (100%) diff --git a/examples/button/button.png b/examples/02-button/button.png similarity index 100% rename from examples/button/button.png rename to examples/02-button/button.png diff --git a/examples/button/button.rb b/examples/02-button/button.rb similarity index 100% rename from examples/button/button.rb rename to examples/02-button/button.rb diff --git a/examples/potentiometer/potentiometer.rb b/examples/03-potentiometer/potentiometer.rb similarity index 100% rename from examples/potentiometer/potentiometer.rb rename to examples/03-potentiometer/potentiometer.rb diff --git a/examples/04-pwm_led/pwm_led.rb b/examples/04-pwm_led/pwm_led.rb new file mode 100644 index 00000000..e69de29b diff --git a/examples/rgb_led/rgb_led.rb b/examples/05-rgb_led/rgb_led.rb similarity index 100% rename from examples/rgb_led/rgb_led.rb rename to examples/05-rgb_led/rgb_led.rb From 62d4e4a65e4e76bfef6203ea4d13ac5d70cc765b Mon Sep 17 00:00:00 2001 From: vickash Date: Wed, 15 Feb 2023 01:36:57 -0400 Subject: [PATCH 246/296] Improve flow control: no timing functions. Notify every half buffer size. --- lib/dino/tx_rx/base.rb | 2 +- lib/dino/tx_rx/flow_control.rb | 4 ++-- src/lib/Dino.cpp | 25 +++++++++---------------- src/lib/Dino.h | 10 ++++------ 4 files changed, 16 insertions(+), 25 deletions(-) diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index 5faf665c..6493aca5 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -35,7 +35,7 @@ def flush_read end def start_read - @thread ||= Thread.new do + @thread ||= Thread.new do trap("INT") do io.write("\n91\n") raise Interrupt diff --git a/lib/dino/tx_rx/flow_control.rb b/lib/dino/tx_rx/flow_control.rb index f21cf1d0..d72a1c38 100644 --- a/lib/dino/tx_rx/flow_control.rb +++ b/lib/dino/tx_rx/flow_control.rb @@ -64,8 +64,8 @@ def reset_flow_control def read_and_parse line = read - if line && line.match(/\ARCV:/) - remove_transit_bytes(line.split(/:/)[1].to_i) + if line && line.match(/\ARx/) + remove_transit_bytes(line.split(/x/)[1].to_i) elsif line parse(line) else diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 1515f690..fc667dc9 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -12,28 +12,21 @@ Dino::Dino(){ } -void Dino::acknowledge() { - stream->print("RCV:"); - stream->print(rcvBytes); +void Dino::rxNotify() { + stream->print("Rx"); + stream->print(rxBytes); stream->print("\n"); - rcvBytes = 0; + rxBytes = 0; } void Dino::run(){ - boolean gotByte = false; - while(stream->available() > 0) { - gotByte = true; - rcvBytes ++; + rxBytes ++; parse(stream->read()); - // Acknowledge when we've received as many bytes as the serial input buffer - if (rcvBytes == rcvThreshold) acknowledge(); + // Acknowledge when we've received half as many bytes as the serial buffer. + if (rxBytes >= rxNotifyLimit) rxNotify(); } - if (gotByte) lastRcv = millis(); - - // Also acknowledge when the last byte received goes outside the receive window. - if ((rcvBytes > 0) && ((millis() - lastRcv) > rcvWindow)) acknowledge(); // Run dino's listeners. updateListeners(); @@ -217,8 +210,8 @@ void Dino::updateListeners() { void Dino::handshake() { resetState(); - // Reset this so we never send RCV: along with ACK: - rcvBytes = 0; + // Reset this so we never send Rx along with ACK: + rxBytes = 0; stream->print("ACK:"); stream->print(AUX_SIZE); diff --git a/src/lib/Dino.h b/src/lib/Dino.h index f4e5a761..c63f2bdf 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -145,7 +145,6 @@ class Dino { // // Main loop input functions. // - void acknowledge(); void parse(byte c); void process(); @@ -173,10 +172,9 @@ class Dino { #endif byte auxMsg[AUX_SIZE]; - // Keep count of bytes as we receive them and send a dino message with how many. - uint8_t rcvBytes = 0; - uint8_t rcvThreshold = 64; - unsigned long lastRcv = millis(); - long long rcvWindow = 1000; + // Flow control stuff. Notify when we've received half a serial buffer worth of bytes. + void rxNotify(); + uint8_t rxBytes = 0; + uint8_t rxNotifyLimit = 32; }; #endif From be19bb3a3734c27c4bd047eaa77393f030fd23f7 Mon Sep 17 00:00:00 2001 From: vickash Date: Wed, 15 Feb 2023 01:54:08 -0400 Subject: [PATCH 247/296] Update button example --- examples/01-led/led.rb | 39 +++++++++++++---------- examples/02-button/button.rb | 62 +++++++++++++++++++++++++++++------- 2 files changed, 72 insertions(+), 29 deletions(-) diff --git a/examples/01-led/led.rb b/examples/01-led/led.rb index 66f66825..f372a4ee 100644 --- a/examples/01-led/led.rb +++ b/examples/01-led/led.rb @@ -1,34 +1,39 @@ # -# Simple example that controls an LED. +# Example 1: Controlling an LED # require 'bundler/setup' require 'dino' -# If the board is connected with a USB cable, it's most likely serial over USB. -connection = Dino::TxRx::Serial.new +# If the board is plugged into a USB port, we can talk to it with serial over USB. +io = Dino::TxRx::Serial.new -# Initialize an object to represent the board, giving the connection. -board = Dino::Board.new(connection) +# Create an object to represent the board, giving the I/O object. +board = Dino::Board.new(io) -# Initialize an object representing the LED, giving the board and pin. -# The on-board LED (marked "L") is connectd to pin 13 on most Arduinos. -led = Dino::Components::Led.new(pin: 13, board: board) +# +# Create an object for the LED, giving the board, and the pin that the positive +# leg of your LED is connected to. The longer leg is usually positive. +# See led.png in this folder for a hook-up diagram. +# +# The on-board LED (marked "L") is internally connected to pin 13 on most Arduinos, +# and can be used without connecting anything. +# +led = Dino::Components::Led.new(board: board, pin: 13) # -# Now we can control the LED. -# Led is a DigitalOutput, so it can only have one of two states: +# Now we can use it. A digital output can only have one of two states: # 1 / high / on # 0 / low / off # -# We can write the state directly, or use named convenience methods. -# The 3 lines below all do the same thing: turn it on, wait half a second, turn it off. +# We can write state directly, or use named convenience methods that led has. +# The 3 lines below all do the same thing: turn on, wait half a second, turn off. # led.write(1); sleep 0.5; led.write(0) led.high; sleep 0.5; led.low led.on; sleep 0.5; led.off # -# Calling #toggle will set it to the opposite of its current state. +# led.toggle will set it to the opposite of its current state. # Keep it blinking for 3 more seconds using #toggle. # 6.times do @@ -40,18 +45,18 @@ # What if we want to blink in the background? # # led.blink runs in a separate thread, managed by the led object, -# and doesn't block the main thread. Give it the blink interval in seconds. +# and doesn't block the main thread. Give the blink interval in seconds. # led.blink 0.5 # Wait while it blinks for 5 seconds. sleep 5 -# Call a method that sets the state (#write, #high, #low, #on, #off) to -# automatically stop the blink thread. +# Calling a method that sets the state (#write, #high, #low, #on, #off) +# automatically stops the blink thread. led.off sleep 2 # Blink faster indefinitely. -led.blink 0.25 +led.blink 0.05 sleep diff --git a/examples/02-button/button.rb b/examples/02-button/button.rb index eadecf7f..6716786c 100644 --- a/examples/02-button/button.rb +++ b/examples/02-button/button.rb @@ -1,24 +1,62 @@ # -# This is an example of how to use the button class -# You must register helpers and have the main thread -# sleep or in someway keep running or your program -# will exit before any callbacks can be called +# Example 2: Using a Button # require 'bundler/setup' require 'dino' +# Set up the board, connecting with serial over USB board = Dino::Board.new(Dino::TxRx::Serial.new) -button = Dino::Components::Button.new(pin: 13, board: board) -button.down do - puts "button down" -end +# +# Create an object for a momentary button, giving the board, and the pin that +# the ungrounded side of the button is connected to. +# +# `pullup: true` tells the board to keep the input pin high (logical 1) when the +# button is not pressed. Without this (or an external pullup resistor), the pin +# might float between 0 and 1, giving incorrect readings. +# +# See button.png in this folder for a hook-up diagram. +# +button = Dino::Components::Button.new(board: board, pin: 7, pullup: true) -button.up do - puts "button up" +# +# As soon as a Button (or any DigitalInput) is created, the board starts +# listening for changes to the physical button. When that happens, our button +# object is notified. To catch these notifications, we have to use a callback. +# +# button.add_callback saves a block of code, and runs it when the button state changes. +# +# The callback below checks if the state is 0 (the button went from up to down), +# then counts and prints the number of times pressed. +# +presses = 0 +button.add_callback(:count_presses) do |state| + if state == 0 + presses = presses + 1 + puts "Button press ##{presses}" + end end -# Force callbacks to run at least once for the initial state. -button.read +# Wait for the button to be pressed 3 times. +sleep 0.005 while presses < 3 + +# Callbacks are stored in a Hash. We can be specific and use keys when adding and removing. +button.remove_callbacks(:count_presses) + +# Or remove them all. Either way, the block above won't run anymore. +button.remove_callbacks + +# +# button.down and button.up add callbacks that automatically check state for you. +# +# #down runs only when state goes from high (1) to low (0). +# #up runs only when state goes from low (0) to high (1). +# +# We can use them to control the internal LED from example 1. +# +led = Dino::Components::Led.new(board: board, pin: 13) + +button.up { led.off } +button.down { led.on } sleep From 5c5b4e65adcbe90a83ee83e56a8efa0aff471198 Mon Sep 17 00:00:00 2001 From: vickash Date: Wed, 15 Feb 2023 14:31:58 -0400 Subject: [PATCH 248/296] Use a separate thread and buffer to write to the board. The board buffer updates in the same thread that callbacks run. If we try to write in that thread, and there wasn't enough buffer, the write blocks indefinitely, and the buffer space never updates. --- lib/dino/tx_rx/base.rb | 42 +++++++++++------ lib/dino/tx_rx/flow_control.rb | 80 ++++++++++++++++++++------------ lib/dino/tx_rx/handshake.rb | 1 + lib/dino/tx_rx/serial.rb | 4 +- lib/dino/tx_rx/tcp.rb | 4 +- test/txrx/serial_test.rb | 84 ++++++++++++++++++++++------------ 6 files changed, 136 insertions(+), 79 deletions(-) diff --git a/lib/dino/tx_rx/base.rb b/lib/dino/tx_rx/base.rb index 6493aca5..817aff9f 100644 --- a/lib/dino/tx_rx/base.rb +++ b/lib/dino/tx_rx/base.rb @@ -17,6 +17,10 @@ def self.inherited(subclass) end private + + def parse(line) + changed && notify_observers(line) if line + end def io @io ||= connect @@ -26,34 +30,42 @@ def io_reset flush_read stop_read start_read + stop_write + start_write end def flush_read - Timeout.timeout(5) { read until read == nil } + Timeout.timeout(5) { _read until _read == nil } rescue Timeout::Error raise RxFlushTimeout, "Cannot read from device, or device not running dino" end def start_read - @thread ||= Thread.new do - trap("INT") do - io.write("\n91\n") - raise Interrupt - end - - loop do - read_and_parse - end + @read_thread ||= Thread.new do + loop { parse(read) } end end def stop_read - Thread.kill(@thread) if @thread - @thread = nil + Thread.kill(@read_thread) if @read_thread + @read_thread = nil end - - def parse(line) - changed && notify_observers(line) + + def start_write + @write_thread ||= Thread.new do + # Tell the board to reset if our script is interrupted. + trap("INT") do + _write "\n91\n" + raise Interrupt + end + + loop { write_from_buffer } + end + end + + def stop_write + Thread.kill(@write_thread) if @write_thread + @write_thread = nil end end end diff --git a/lib/dino/tx_rx/flow_control.rb b/lib/dino/tx_rx/flow_control.rb index d72a1c38..5c4fd95e 100644 --- a/lib/dino/tx_rx/flow_control.rb +++ b/lib/dino/tx_rx/flow_control.rb @@ -13,34 +13,15 @@ def initialize(*args) def write(message) add_write_call - @write_mutex.synchronize do - while message && !message.empty? - bytes = reserve_bytes(message.length) - if bytes > 0 - fragment = message[0..(bytes-1)] - message = message[bytes..-1] - super(fragment) - else - tx_wait - end - end - end - end - - # Keep the transit mutex lock for as little time as possible this way. - def reserve_bytes(length) - @transit_mutex.synchronize do - available = BOARD_BUFFER - @transit_bytes - reserved = (length > available) ? available : length - @transit_bytes += reserved - reserved + @write_buffer_mutex.synchronize do + @write_buffer << message end end private def reset_flow_control - @write_mutex ||= Mutex.new + @io_mutex ||= Mutex.new @transit_mutex ||= Mutex.new @tx_sleep_mutex ||= Mutex.new @rx_sleep_mutex ||= Mutex.new @@ -57,23 +38,62 @@ def reset_flow_control @read_lines = 0 @rx_sleep_calls = 0 end + + @write_buffer_mutex ||= Mutex.new + @write_buffer_mutex.synchronize do + @write_buffer = "" + end @last_interval_update = Time.now end + + def write_from_buffer + fragment = nil + + # Check space on the remote read buffer. If available, take a fragment + # of that many bytes off the local write buffer. + @write_buffer_mutex.synchronize do + break if @write_buffer.empty? + bytes = reserve_bytes(@write_buffer.length) + if bytes > 0 + fragment = @write_buffer[0..(bytes-1)] + @write_buffer = @write_buffer[bytes..-1] + end + end + + # Write if we can. Wait otherwise. + if fragment + @io_mutex.synchronize { _write fragment } + else + tx_wait + end + end - def read_and_parse - line = read + # Use transit mutex for as short as possible by reserving bytes and writing later. + def reserve_bytes(length) + @transit_mutex.synchronize do + available = BOARD_BUFFER - @transit_bytes + reserved = (length > available) ? available : length + @transit_bytes += reserved + reserved + end + end - if line && line.match(/\ARx/) - remove_transit_bytes(line.split(/x/)[1].to_i) - elsif line - parse(line) + def read + line = @io_mutex.synchronize { _read } + + if line + add_read_line + if line.match(/\ARx/) + remove_transit_bytes(line.split(/x/)[1].to_i) + line = nil + end else rx_wait end - - add_read_line if line update_sleep_intervals + + return line end def update_sleep_intervals diff --git a/lib/dino/tx_rx/handshake.rb b/lib/dino/tx_rx/handshake.rb index 7b060407..60d525a1 100644 --- a/lib/dino/tx_rx/handshake.rb +++ b/lib/dino/tx_rx/handshake.rb @@ -29,6 +29,7 @@ def handshake Timeout.timeout(HANDSHAKE_TIMEOUT) do loop do + sleep 0.001 if attempt.acknowledged puts "Acknowledged. Hardware ready...\n\n" diff --git a/lib/dino/tx_rx/serial.rb b/lib/dino/tx_rx/serial.rb index 5a99be8b..d9001de8 100644 --- a/lib/dino/tx_rx/serial.rb +++ b/lib/dino/tx_rx/serial.rb @@ -14,11 +14,11 @@ def to_s "#{@device} @ #{@baud} baud" end - def write(message) + def _write(message) io.write(message) end - def read + def _read buff, escaped = "", false loop do char = io.read(1) diff --git a/lib/dino/tx_rx/tcp.rb b/lib/dino/tx_rx/tcp.rb index df8eb31f..d0fbca35 100644 --- a/lib/dino/tx_rx/tcp.rb +++ b/lib/dino/tx_rx/tcp.rb @@ -25,7 +25,7 @@ def connect raise TCPConnectError, error.message end - def write(message) + def _write(message) loop do if IO.select(nil, [io], nil, 0) io.syswrite(message) @@ -34,7 +34,7 @@ def write(message) end end - def read + def _read IO.select([io], nil, nil, 0) && io.gets.gsub(/\n\z/, "") end end diff --git a/test/txrx/serial_test.rb b/test/txrx/serial_test.rb index 16034a2d..93217f10 100644 --- a/test/txrx/serial_test.rb +++ b/test/txrx/serial_test.rb @@ -75,54 +75,78 @@ def test_connect_on_windows def test_io_reset flush_mock = MiniTest::Mock.new.expect :call, true - stop_mock = MiniTest::Mock.new.expect :call, true - start_mock = MiniTest::Mock.new.expect :call, true + r_stop_mock = MiniTest::Mock.new.expect :call, true + r_start_mock = MiniTest::Mock.new.expect :call, true + w_stop_mock = MiniTest::Mock.new.expect :call, true + w_start_mock = MiniTest::Mock.new.expect :call, true + txrx.stub(:flush_read, flush_mock) do - txrx.stub(:stop_read, stop_mock) do - txrx.stub(:start_read, start_mock) do - txrx.send(:io_reset) + txrx.stub(:stop_read, r_stop_mock) do + txrx.stub(:start_read, r_start_mock) do + txrx.stub(:stop_write, w_stop_mock) do + txrx.stub(:start_write, w_start_mock) do + txrx.send(:io_reset) + end + end end end end + flush_mock.verify - stop_mock.verify - start_mock.verify + r_stop_mock.verify + r_start_mock.verify + w_start_mock.verify + w_start_mock.verify end - def test_read_and_parse - txrx.stub(:read, "02:00:00") do - txrx.stub(:changed, true) do - mock = MiniTest::Mock.new.expect :call, nil, ['02:00:00'] - txrx.stub(:notify_observers, mock) do - txrx.send(:read_and_parse) - end - mock.verify + def test_read + txrx.stub(:_read, "02:00:00") do + line = txrx.send(:read) + assert_equal line, "02:00:00" + end + end + + def test_parse + mock = MiniTest::Mock.new.expect :call, nil, ['02:00:00'] + txrx.stub(:changed, true) do + txrx.stub(:notify_observers, mock) do + txrx.send(:parse, '02:00:00') end + mock.verify end end - # Test start read? - def test_stop_read thread = Thread.new { sleep } - mock = MiniTest::Mock.new.expect :call, nil, [thread] - txrx.instance_variable_set(:@thread, thread) - - Thread.stub(:kill, mock) do - txrx.send(:stop_read) - end - mock.verify + txrx.instance_variable_set(:@read_thread, thread) + txrx.send(:stop_write) + assert_nil txrx.instance_variable_get(:@write_thread) + end + + def test_stop_write + thread = Thread.new { sleep } + txrx.instance_variable_set(:@write_thread, thread) + txrx.send(:stop_write) + assert_nil txrx.instance_variable_get(:@write_thread) end def test_write - mock = MiniTest::Mock.new.expect :write, nil, ['message'] - txrx.stub(:io, mock) do - txrx.write('message') + # Message is appended to the buffer. + txrx.send(:stop_write) + txrx.write('message') + assert_equal txrx.instance_variable_get("@write_buffer"), "message" + + # Message is written from buffer when we start the write thread. + mock = MiniTest::Mock.new.expect :call, nil, ['message'] + txrx.stub(:_write, mock) do + txrx.send(:start_write) + sleep 0.005 end mock.verify + assert_equal txrx.instance_variable_get("@write_buffer"), "" end - def test_read_single_chars_until_newline_and_strips_it + def test_io_read_single_chars_until_newline_and_strips_it mock = MiniTest::Mock.new "line\n".split("").each do |char| mock.expect :read, char, [1] @@ -133,7 +157,7 @@ def test_read_single_chars_until_newline_and_strips_it mock.verify end - def test_read_handles_escaped_newlines_and_backslashes + def test_io_read_handles_escaped_newlines_and_backslashes mock = MiniTest::Mock.new "l1\\\nl2\\\\\n".split("").each do |char| mock.expect :read, char, [1] @@ -144,7 +168,7 @@ def test_read_handles_escaped_newlines_and_backslashes mock.verify end - def test_read_returns_empty_string_if_just_newline + def test_io_read_returns_empty_string_if_just_newline mock = MiniTest::Mock.new mock.expect :read, "\n", [1] txrx.stub(:io, mock) do From b928561ffe8f8c505e3539a2c10ecd5c2560ad4b Mon Sep 17 00:00:00 2001 From: vickash Date: Fri, 17 Feb 2023 17:15:47 -0400 Subject: [PATCH 249/296] Count ticks (and update listeners) more precisely --- src/lib/Dino.cpp | 17 +++++++++++------ src/lib/Dino.h | 12 +++++++++--- src/lib/DinoCoreIO.cpp | 4 ++-- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index fc667dc9..f48063ee 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -188,12 +188,17 @@ void Dino::process() { // Digital listeners only send values on change. // void Dino::updateListeners() { - unsigned long now = micros(); - if ((now - lastTick) > 1000) { - lastTick = now; - tickCount++; + currentTime = micros(); + timeDiff = currentTime - lastTime; + + if (timeDiff > 999) { + // Add a tick for every 1000us passed + tickCount = tickCount + (timeDiff / 1000); + + // lastTime for next run is currentTime offset by remainder. + lastTime = currentTime - (timeDiff % 1000); - updateCoreListeners(tickCount); + updateCoreListeners(); // Register Listeners #ifdef DINO_SHIFT @@ -249,7 +254,7 @@ void Dino::resetState() { fragmentIndex = 0; charIndex = 0; tickCount = 0; - lastTick = micros(); + lastTime = micros(); } diff --git a/src/lib/Dino.h b/src/lib/Dino.h index c63f2bdf..f1170389 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -50,13 +50,19 @@ class Dino { void aWrite (byte p, int v, boolean echo=true); //cmd = 3 int aRead (byte p); //cmd = 4 void setListener (byte p, boolean enabled, byte analog, byte exponent, boolean local=true); //cmd = 5 + + // Read value of micros() every loop. + unsigned long currentTime; + + // Counts 1ms ticks based on currentTime. Rolls over every 256ms. + byte tickCount; private: // // Main loop listen functions. // void updateListeners (); - void updateCoreListeners (byte tickCount); + void updateCoreListeners (); void analogListenerUpdate (byte index); void digitalListenerUpdate (byte index); void clearCoreListeners (); @@ -138,8 +144,8 @@ class Dino { void resetState (); //cmd = 91 void setRegisterDivider (); //cmd = 97 void setAnalogResolution (); //cmd = 96 - unsigned long lastTick; - byte tickCount; + unsigned long lastTime; + unsigned long timeDiff; byte registerDivider; // diff --git a/src/lib/DinoCoreIO.cpp b/src/lib/DinoCoreIO.cpp index 2c6c75ce..eee8d3e5 100644 --- a/src/lib/DinoCoreIO.cpp +++ b/src/lib/DinoCoreIO.cpp @@ -146,12 +146,12 @@ void Dino::setListener(byte p, boolean enabled, byte analog, byte exponent, bool } // Runs once on every loop to update necessary listeners. -void Dino::updateCoreListeners(byte tickCount){ +void Dino::updateCoreListeners() { for (byte i = 0; i <= lastActiveListener; i++){ // Check if active. if (bitRead(listeners[i][0], 7) == 1){ // Check if to update it on this tick. - // Divider exponent is last 3 bits of settings. + // Divider exponent is last 3 bits of settings. byte exponent = listeners[i][0] & 0B00000111; byte divider = dividerMap[exponent]; if(tickCount % divider == 0){ From cce3a135463c1b0aca417fad191beecb239a372b Mon Sep 17 00:00:00 2001 From: vickash Date: Fri, 17 Feb 2023 22:15:52 -0400 Subject: [PATCH 250/296] Set listener defaults after_initialize instead of in args --- lib/dino/components/basic/analog_input.rb | 13 +++++++++++-- lib/dino/components/basic/digital_input.rb | 9 +++++---- lib/dino/components/mixins/listener.rb | 4 ++-- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/lib/dino/components/basic/analog_input.rb b/lib/dino/components/basic/analog_input.rb index 78e32127..6ee735d6 100644 --- a/lib/dino/components/basic/analog_input.rb +++ b/lib/dino/components/basic/analog_input.rb @@ -7,15 +7,24 @@ class AnalogInput include Mixins::Reader include Mixins::Poller include Mixins::Listener + + def after_initialize(options={}) + super(options) + @divider = 16 + end def _read board.analog_read(pin) end - def _listen(divider=16) - @divider = divider + def _listen(divider=nil) + @divider = divider || @divider board.analog_listen(pin, @divider) end + + def pre_callback_filter(value) + value.to_i + end end end end diff --git a/lib/dino/components/basic/digital_input.rb b/lib/dino/components/basic/digital_input.rb index 2e3c4343..40d6e4dd 100644 --- a/lib/dino/components/basic/digital_input.rb +++ b/lib/dino/components/basic/digital_input.rb @@ -10,6 +10,7 @@ class DigitalInput def after_initialize(options={}) super(options) + @divider = 4 _listen end @@ -17,8 +18,8 @@ def _read board.digital_read(pin) end - def _listen(divider=4) - @divider = divider + def _listen(divider=nil) + @divider = divider || @divider board.digital_listen(pin, @divider) end @@ -34,8 +35,8 @@ def on_low(&block) end end - def pre_callback_filter(data) - data.to_i + def pre_callback_filter(value) + value.to_i end def high?; state == board.high end diff --git a/lib/dino/components/mixins/listener.rb b/lib/dino/components/mixins/listener.rb index fd89b8e9..b0fae556 100644 --- a/lib/dino/components/mixins/listener.rb +++ b/lib/dino/components/mixins/listener.rb @@ -7,10 +7,10 @@ module Listener attr_reader :divider def listen(divider=nil, &block) - @divider = divider + @divider = divider || @listener stop add_callback(:listen, &block) if block_given? - _listen(divider) + _listen(@divider) end def stop From 3f9a2f2711ae5706ef760408277613d730bb09af Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 18 Feb 2023 00:16:52 -0400 Subject: [PATCH 251/296] Add Potentiometer, with moving average smoothing, and example --- examples/01-led/led.rb | 25 ++++---- examples/02-button/button.rb | 9 +-- examples/03-potentiometer/potentiometer.rb | 66 +++++++++++++++++----- lib/dino/components.rb | 1 + lib/dino/components/potentiometer.rb | 51 +++++++++++++++++ 5 files changed, 123 insertions(+), 29 deletions(-) create mode 100644 lib/dino/components/potentiometer.rb diff --git a/examples/01-led/led.rb b/examples/01-led/led.rb index f372a4ee..d78b0a77 100644 --- a/examples/01-led/led.rb +++ b/examples/01-led/led.rb @@ -20,20 +20,22 @@ # led = Dino::Components::Led.new(board: board, pin: 13) +# Now we can make it blink. +puts "Blinking every half second..." + # -# Now we can use it. A digital output can only have one of two states: +# A digital output can only have one of two states: # 1 / high / on # 0 / low / off -# -# We can write state directly, or use named convenience methods that led has. -# The 3 lines below all do the same thing: turn on, wait half a second, turn off. +# We can use led.write to set it directly, or named convenience methods. +# These 3 lines all do the same thing: turn on, wait half a second, turn off. # led.write(1); sleep 0.5; led.write(0) led.high; sleep 0.5; led.low led.on; sleep 0.5; led.off # -# led.toggle will set it to the opposite of its current state. +# led.toggle will set it to the opposite state each time it's called. # Keep it blinking for 3 more seconds using #toggle. # 6.times do @@ -44,19 +46,20 @@ # # What if we want to blink in the background? # -# led.blink runs in a separate thread, managed by the led object, -# and doesn't block the main thread. Give the blink interval in seconds. +# led.blink runs in a separate thread, managed by the led, and doesn't block the +# main thread. Give it the blink interval in seconds. # led.blink 0.5 - -# Wait while it blinks for 5 seconds. -sleep 5 +puts "Blinking in the background... Hello from the main thread!" +sleep 3 # Calling a method that sets the state (#write, #high, #low, #on, #off) # automatically stops the blink thread. +puts "Turning off for 2 seconds..." led.off sleep 2 # Blink faster indefinitely. -led.blink 0.05 +puts "Blinking faster forever... (Press Ctrl+C to exit)" +led.blink 0.1 sleep diff --git a/examples/02-button/button.rb b/examples/02-button/button.rb index 6716786c..06b3816f 100644 --- a/examples/02-button/button.rb +++ b/examples/02-button/button.rb @@ -23,8 +23,7 @@ # As soon as a Button (or any DigitalInput) is created, the board starts # listening for changes to the physical button. When that happens, our button # object is notified. To catch these notifications, we have to use a callback. -# -# button.add_callback saves a block of code, and runs it when the button state changes. +# button.add_callback saves a block of code to run each time the button state changes. # # The callback below checks if the state is 0 (the button went from up to down), # then counts and prints the number of times pressed. @@ -40,8 +39,8 @@ # Wait for the button to be pressed 3 times. sleep 0.005 while presses < 3 -# Callbacks are stored in a Hash. We can be specific and use keys when adding and removing. -button.remove_callbacks(:count_presses) +# Button keeps its callbacks in a Hash. We can be specific and use keys to add and remove. +button.remove_callback(:count_presses) # Or remove them all. Either way, the block above won't run anymore. button.remove_callbacks @@ -59,4 +58,6 @@ button.up { led.off } button.down { led.on } +puts "Press the button to turn on the LED... (Ctrl+C to exit)" + sleep diff --git a/examples/03-potentiometer/potentiometer.rb b/examples/03-potentiometer/potentiometer.rb index 03940b62..3db77b09 100644 --- a/examples/03-potentiometer/potentiometer.rb +++ b/examples/03-potentiometer/potentiometer.rb @@ -1,24 +1,62 @@ # -# This is an example of how to use the sensor class -# with a potentiometer to control the flash speed of an -# LED. The set_delay callback reads from the potentiometer -# and changes the sleep delay for the LED on/off cycle. -# +# Example 3: Potentiometer and Analog Inputs +# require 'bundler/setup' require 'dino' +# Set up the board, connecting with serial over USB board = Dino::Board.new(Dino::TxRx::Serial.new) -led = Dino::Components::Led.new(pin: 13, board: board) -potentiometer = Dino::Components::Sensor.new(pin: 'A0', board: board) -delay = 500.0 +# +# Connect the potentiometer's outer 2 pins to Ground and Vcc respectively. +# The center is the wiper, and should be connected to one of the board's analog +# pins. On most boards these pins start with 'A' and can be given as strings. +# +# See potentiometer.png in this folder for a hook-up diagram. +# +potentiometer = Dino::Components::Potentiometer.new(pin: 'A0', board: board) + +# +# Like with Button, the Board starts reading the Potentiometer value immediately, +# but we need to add a callback to do something with it. +# +potentiometer.on_change do |value| + print "Potentiometer value: #{value} \r" +end +puts "Turn the potentiometer to change value. Press Enter to continue..." + +# Stop the callback. +gets; puts +potentiometer.remove_callbacks + +# +# The default resolution for Analog Input is 10-bits, so you should have seen +# values from 0 - 1023. We can use the value to control the blinking +# speed of the LED from the earlier example. +# +led = Dino::Components::Led.new(board: board, pin: 13) + +# Helper method to calculate the blink time. +def map_pot_value(value) + # Map 10 bit value into 0 to 1 range. + fraction = value / 1023.to_f -potentiometer.when_data_received do |data| - puts "DATA: #{delay = data.to_i}" + # Linearization hack for audio taper potentiometers. + # Adjust k for different tapers. This was an A500K. + k = 5 + linearized = (fraction * (k + 1)) / ((k * fraction) + 1) + # Use this for linear potentiometers instead. + # linearized = fraction + + # Map to the 0.1 to 0.5 seconds range in reverse. Clockwise = faster. + 0.5 - (linearized * 0.4) end -[:on, :off].cycle do |switch| - puts "DELAY: #{seconds = (delay / 1000.0)}" - led.send(switch) - sleep seconds +# Callback that calculates the blink interval and tells the LED. +potentiometer.on_change do |value| + interval = map_pot_value(value) + print "LED blink interval: #{interval.round(4)} seconds \r" + led.blink(interval) end +puts "Turn potentiometer to control the LED blink. Press Ctrl+C to exit..." +sleep diff --git a/lib/dino/components.rb b/lib/dino/components.rb index 30931282..9d44ac2f 100644 --- a/lib/dino/components.rb +++ b/lib/dino/components.rb @@ -21,5 +21,6 @@ module Components autoload :Piezo, 'dino/components/piezo' autoload :RotaryEncoder, 'dino/components/rotary_encoder' autoload :OneWire, 'dino/components/one_wire' + autoload :Potentiometer, 'dino/components/potentiometer' end end diff --git a/lib/dino/components/potentiometer.rb b/lib/dino/components/potentiometer.rb new file mode 100644 index 00000000..272a85b8 --- /dev/null +++ b/lib/dino/components/potentiometer.rb @@ -0,0 +1,51 @@ +module Dino + module Components + class Potentiometer < Basic::AnalogInput + + attr_accessor :smoothing + + def after_initialize(options={}) + super(options) + + # Read 2x as often than regular AnalogInput. + @divider = 8 + + # Keep values to smooth with moving average by default. + self.smoothing = true + @moving_set = [] + + # Start listening immediately. + listen + end + + def smoothing_on + self.smoothing = true + end + + def smoothing_off + self.smoothing = false + end + + def on_change(&block) + add_callback(:on_change) do |new_state| + block.call(new_state) if new_state != self.state + end + end + + def pre_callback_filter(value) + smoothing ? smooth_input(value) : value + end + + def smooth_input(value) + # Add new value, but limit to the 8 latest values. + @moving_set << value.to_i + @moving_set.shift if @moving_set.length > 8 + + average = @moving_set.reduce(:+) / @moving_set.length.to_f + + # Round up or down based on previous state to smooth even more. + state && (state > average) ? average.ceil : average.floor + end + end + end +end From fd95ec7715be76e79a80a5f64b0678071886d31e Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 18 Feb 2023 21:10:44 -0400 Subject: [PATCH 252/296] Output sketch folder instead for use with Arduino CLI --- lib/dino_cli.rb | 2 +- lib/dino_cli/generator.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/dino_cli.rb b/lib/dino_cli.rb index 03e4621a..246221ca 100644 --- a/lib/dino_cli.rb +++ b/lib/dino_cli.rb @@ -13,6 +13,6 @@ def self.start(options={}) def self.sketch(options) result = DinoCLI::Generator.run!(options) - $stdout.puts result[:sketch_file] + $stdout.puts result[:sketch_folder] end end diff --git a/lib/dino_cli/generator.rb b/lib/dino_cli/generator.rb index 119bfdb6..9389772b 100644 --- a/lib/dino_cli/generator.rb +++ b/lib/dino_cli/generator.rb @@ -139,6 +139,7 @@ def write # Return the location of the sketch file. options[:sketch_file] = sketch + options[:sketch_folder] = output_dir options end From cd09ce0d81be4fb89998248b919b3ab0b3e3f86a Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 19 Feb 2023 02:24:21 -0400 Subject: [PATCH 253/296] Update readme and changelog --- CHANGELOG.md | 2 + README.md | 149 +++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 117 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37ceb733..c1d3e0b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,8 @@ - SoftwareSerial. _Uses Arduino `SoftSerial` library._ (**write only / experimental**) +- Potentiometer class, based on AnalogInput, but enables moving average smoothing by default and adds #on_change callback method. + - Rotary encoder support. _Uses polling method @ 1ms interval._ **WARNING**: Not suitable for high speed or precise position needs. It will definitely miss steps. Sufficient for rotary knobs as user input. - DHT11 / DHT 21 (AM2301) / DHT22 temperature and relative humidity sensor support. _Custom implementation where input pulses are measured on the board, then decoded in Ruby._ diff --git a/README.md b/README.md index 60b988a0..13d65ba5 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,146 @@ -# Dino 0.12.0 -[![Build Status](https://secure.travis-ci.org/austinbv/dino.png)](http://travis-ci.org/austinbv/dino) +# Dino 0.12.0 [![Build Status](https://secure.travis-ci.org/austinbv/dino.png)](http://travis-ci.org/austinbv/dino) +#### Ruby Meets Microcontrollers +Dino gives you a high-level Ruby interface to low-level hardware, without writing microcontroller code. Use LEDs, buttons, sensors and more, just as easily as any Ruby object: -## Get Started In No Time +````ruby +led.blink 0.5 -Dino lets you start programming your Arduino with Ruby in minutes. +lcd.puts "Hello World!" -#### Install the Gem +reading = sensor.read +button.down do + puts "Button pressed!" +end +```` + +Dino doesn't run Ruby on the microcontroller board either, like [mruby](https://github.com/mruby/mruby). The board always runs a C++ firmware that exposes as much low-level I/O as possible, so we can use it in Ruby. It becomes an I/O peripheral for your computer. + +High-level abstraction in Ruby makes hardware classes easy to implement, with interfaces we expect. They also get thread-safe state, callbacks for inputs, and effectively mulitask a single threaded microcontroller (no "task" priority though). + +Physical components connected to your boards map to Ruby objects you can use directly. You get to think about your hardware and appplication logic, not all the stuff in between. + +## Getting Started +#### 1) Install the Gem ```shell gem install dino ``` -#### Download the Arduino IDE +Before we can use the microcontroller in Ruby, we need to flash it with the dino firmware (or "sketch" in Arduino slang). This is needed **only once** for each board, but future versions of dino may need reflashing to add firmware-level functions. -Dino is all about writing Ruby, but we'll need to upload some C code to the Arduino so we can talk to it. You'll need the [Arduino IDE](http://arduino.cc/en/Main/Software) for that. Even though it's still in beta, version 1.5 is recommended since it lets you upload from the command line. +#### 2) Install the Arduino IDE OR CLI -#### Connect your Arduino +Download the Arduino IDE from [here](http://arduino.cc/en/Main/Software) for a graphical interface, or install the command line interface from [here](https://github.com/arduino/arduino-cli/releases), or Homebrew. -Connect your board with a USB cable. Open the Arduino IDE and pull down the `Tools` menu. Make sure you have the right type of board, and the correct serial port selected. +**CLI Installation with Homebrew on Mac, Linux, or WSL on Windows:** +````shell +brew update +brew install arduino-cli +```` -#### Prepare the Bootstrapper +#### 3) Install Arduino Dependencies +Dino uses Arduino "cores" which add microcontroller support, and a few C++ libraries for now. We need to install them. -Use the included command line tool to create a folder with the Arduino sketch you want to use and optionally configure it. +**IDE:** +* Open the IDE and select Tools > Board > Board Manager from the menus. +* Search for `ESP8266 Boards` and install the latest version. +* Now go to: Tools > Manage Libraries. +* Find and install the following libraries, at the version numbers given: + * `Liquid Crystal by Arduino, Adafruit` at latest version + * `IRremote by shirriff, z3to, ArminJo` at `version 2.2.3` + * `IRremoteESP82666 by David Conran, Sebastien Warin` at `version 2.3.2` +**CLI:** +````shell +arduino-cli core install esp8266:esp8266 +arduino-cli lib install LiquidCrystal +arduino-cli lib install IRremote@2.2.3 +arduino-cli lib install IRremoteESP8266@2.3.2 +```` + +#### 4) Generate the Arduino Sketch +The `dino` command is included with the gem. It will make the Arduino sketch folder for you, and configure it. + +**For ATmega boards, Serial over USB:** (Arduino Uno, Nano, Mega, Leonardo, Micro) ```shell -# If connecting via serial, USB or ser2net, this is all you should need: dino sketch serial +```` -# If usng the ethernet shield, you'll want to specify unique MAC and IP addresses: -dino sketch ethernet -mac XX:XX:XX:XX:XX:XX -ip XXX.XXX.XXX.XXX +**For ESP8266, Serial over USB:** +```shell +dino sketch serial -target esp8266 +```` -# For more options: -dino help -``` +**For ESP8266 over WiFi (2.4Ghz and DHCP Only):** +```shell +dino sketch wifi -target esp8266 -ssid YOUR_SSID -password YOUR_PASSWORD +```` +**Note:** [This example](https://github.com/austinbv/dino/tree/master/examples/tcp.rb) shows how to instantiate a board over the network. After flashing, you'll need to figure out what IP address your board has, and modify the TxRx line in any other example you wish to run. They are written for serial. + +#### 5a) IDE Flashing + +* Connect the board to your computer with a USB cable. +* Open the .ino file inside your sketch folder with the IDE. +* Open the dropdown menu at the top of the IDE window, and select your board. +* Press the Upload ➡️ button. This will compile the sketch, and flash it to the board. + +**Troubleshooting:** +* If your serial port is in the list, but the board is wrong, select the serial port anyway, then go to Tools > Board in the menus and choose your board from the list. +* If your board doesn't show up at all, make sure it is connected properly. Try disconnecting and reconnecting, use a different USB port or cable, or press the reset button after plugging it in. -__Note:__ Current Ethernet shields come with a sticker indicating the MAC address you should use with them. For older shields without a dedicated MAC address, inventing a random one should work, but don't use the same one for multiple boards. Valid IP addresses depend on the configuration of your network. +#### 5b) CLI Flashing -#### Upload The Bootstrapper +* The path output by `dino sketch` earlier is your sketch folder. Keep it handy. +* Connect the board to your computer with a USB cable. +* Check if the CLI recognizes it: -* Connect the Arduino to a USB port on your machine, (even if using the ethernet sketch). -* Open [the normal Arduino IDE](http://arduino.cc/en/Main/Software) -* Open the `.ino` file in the sketch folder you just generated. -* Click the upload button (an arrow). +````shell +arduino-cli board list +```` + +* Using the Port and FQBN (Fully Qualified Board Name) shown, compile and upload the sketch: +````shell +arduino-cli compile -b YOUR_FQBN YOUR_SKETCH_FOLDER +arduino-cli upload -v -p YOUR_PORT -b YOUR_FQBN YOUR_SKETCH_FOLDER +```` -#### Verify Install +**Troubleshooting:** +* Follow the same steps as the IDE method above. List all FQBNs using: +````shell +arduino-cli board listall +```` -* Build the sample circuit [examples/led/led.png](https://raw.github.com/austinbv/dino/master/examples/led/led.png) -* From your terminal, execute `ruby example/led/led.rb` -* Observe your LED blinking continuously +#### 6) Test It! + +Most boards have an on-board LED. It's internally connected to pin 13 on Arduinos, but might be another pin on your board. Run the LED example from [here](https://github.com/austinbv/dino/tree/master/examples/01-led/led.rb), changing the pin number if needed. If the LED starts blinking, you're ready for Ruby! ## Examples and Tutorials -### Circuits and Programs +#### Included Examples + +* The first 5 [examples](https://github.com/austinbv/dino/tree/master/examples) are sort of a mini-tutorial, to familiarize you with the basics. Read the comments and try modifying the code. You will need the following: + * 1 microcontroller (Arduino Uno, Leonardo and Mega are most compatible) + * 1 button or momentary switch + * 1 potentiometer (any value) + * 1 external RGB LED (4 legs common cathode, not a Neopixel, or individually addressable) + * 1 external LED (any color, or use one color of the RGB LED) + * Current limiting resistors for the LEDs + * Breadboard + * Jumper wires + + **Tip:** Arduino kits are a cost-effective way to get started. They will almost certainly include these parts, plus more, getting you well beyond the starter examples. + +* The remaining examples will usually demonstrate the interface for a specific component class, or how to use multiple components together. +* Each example folder should incldue a wiring diagram alongside its code. + +#### More Examples -* Take a look in [the example directory](https://github.com/austinbv/dino/tree/master/examples) for small component examples -* Try [Getting Started with Arduino and Dino](http://tutorials.jumpstartlab.com/projects/arduino/introducing_arduino.html) from [Jumpstart Lab](http://jumpstartlab.com), building a number-guessing game and a simple nightlight -* An example [rails app using Dino and Pusher](https://github.com/austinbv/dino_rails_example) -* For a Sinatra example look at the [site used to shoot the cannon at RubyConf2012](https://github.com/austinbv/dino_cannon) +* Try [Getting Started with Arduino and Dino](http://tutorials.jumpstartlab.com/projects/arduino/introducing_arduino.html) from [Jumpstart Lab](http://jumpstartlab.com) (_ignore old install instructions_). +* An example [rails app](https://github.com/austinbv/dino_rails_example) using Dino and Pusher. +* For a Sinatra example, look at the [site](https://github.com/austinbv/dino_cannon) used to shoot the cannon at RubyConf2012. -### Explanatory Talks +## Explanatory Talks * "Arduino the Ruby Way" at RubyConf 2012 - * [Video by ConFreaks](http://confreaks.com/videos/1294-rubyconf2012-arduino-the-ruby-way) + * [Video by ConFreaks](https://www.youtube.com/watch?v=oUIor6GK-qA) * [Slides on SpeakerDeck](https://speakerdeck.com/austinbv/arduino-the-ruby-way) From c1fdefa1a612b3265d215ff3ac78a21237efe37c Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 19 Feb 2023 02:38:32 -0400 Subject: [PATCH 254/296] Fix readme formatting --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 13d65ba5..a112419b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Dino 0.12.0 [![Build Status](https://secure.travis-ci.org/austinbv/dino.png)](http://travis-ci.org/austinbv/dino) -#### Ruby Meets Microcontrollers +### Ruby Meets Microcontrollers Dino gives you a high-level Ruby interface to low-level hardware, without writing microcontroller code. Use LEDs, buttons, sensors and more, just as easily as any Ruby object: ````ruby @@ -14,9 +14,9 @@ button.down do end ```` -Dino doesn't run Ruby on the microcontroller board either, like [mruby](https://github.com/mruby/mruby). The board always runs a C++ firmware that exposes as much low-level I/O as possible, so we can use it in Ruby. It becomes an I/O peripheral for your computer. +Dino doesn't run Ruby on the microcontroller board either, like [mruby](https://github.com/mruby/mruby). The board always runs a C++ firmware that exposes as much low-level I/O as possible, for us to use in Ruby. It becomes a peripheral for your computer. -High-level abstraction in Ruby makes hardware classes easy to implement, with interfaces we expect. They also get thread-safe state, callbacks for inputs, and effectively mulitask a single threaded microcontroller (no "task" priority though). +High-level abstraction in Ruby makes hardware classes easy to implement, with interfaces we expect. They get thread-safe state, callbacks for inputs, and mulitask a single core microcontroller, but no "task" priority. Physical components connected to your boards map to Ruby objects you can use directly. You get to think about your hardware and appplication logic, not all the stuff in between. @@ -26,11 +26,11 @@ Physical components connected to your boards map to Ruby objects you can use dir gem install dino ``` -Before we can use the microcontroller in Ruby, we need to flash it with the dino firmware (or "sketch" in Arduino slang). This is needed **only once** for each board, but future versions of dino may need reflashing to add firmware-level functions. +Before using the microcontroller in Ruby, we need to flash it with the dino firmware (or "sketch" in Arduino slang). This is needed **only once** for each board, but future dino versions may need reflashing for firmware functions. #### 2) Install the Arduino IDE OR CLI -Download the Arduino IDE from [here](http://arduino.cc/en/Main/Software) for a graphical interface, or install the command line interface from [here](https://github.com/arduino/arduino-cli/releases), or Homebrew. +Get the Arduino IDE [here](http://arduino.cc/en/Main/Software) for a graphical interface, or use the command line interface from [here](https://github.com/arduino/arduino-cli/releases), or Homebrew. **CLI Installation with Homebrew on Mac, Linux, or WSL on Windows:** ````shell @@ -39,7 +39,7 @@ brew install arduino-cli ```` #### 3) Install Arduino Dependencies -Dino uses Arduino "cores" which add microcontroller support, and a few C++ libraries for now. We need to install them. +Dino uses Arduino "cores" which add microcontroller support, and a few C++ libraries for now. Let's install them. **IDE:** * Open the IDE and select Tools > Board > Board Manager from the menus. @@ -75,7 +75,7 @@ dino sketch serial -target esp8266 ```shell dino sketch wifi -target esp8266 -ssid YOUR_SSID -password YOUR_PASSWORD ```` -**Note:** [This example](https://github.com/austinbv/dino/tree/master/examples/tcp.rb) shows how to instantiate a board over the network. After flashing, you'll need to figure out what IP address your board has, and modify the TxRx line in any other example you wish to run. They are written for serial. +**Note:** [This example](https://github.com/austinbv/dino/tree/master/examples/tcp.rb) shows how to instantiate a board over the network. You'll need to figure out what IP address your board got, and modify the TxRx line in other examples you wish to run. They are written for serial. #### 5a) IDE Flashing @@ -112,7 +112,7 @@ arduino-cli board listall #### 6) Test It! -Most boards have an on-board LED. It's internally connected to pin 13 on Arduinos, but might be another pin on your board. Run the LED example from [here](https://github.com/austinbv/dino/tree/master/examples/01-led/led.rb), changing the pin number if needed. If the LED starts blinking, you're ready for Ruby! +Most boards have an on-board LED. It's internally connected to pin 13 on Arduinos, but might be different for you. Run the LED example from [here](https://github.com/austinbv/dino/tree/master/examples/01-led/led.rb), changing the pin number if needed. If the LED starts blinking, you're ready for Ruby! ## Examples and Tutorials From 169830b8385196771fded85093abcdbae9816b4f Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 19 Feb 2023 14:02:18 -0400 Subject: [PATCH 255/296] Add board#finish_write for scripts that don't loop --- CHANGELOG.md | 14 +++++++------- README.md | 10 +++++----- examples/soft_serial.rb | 15 +++++++++++++++ examples/stepper/stepper.rb | 5 ++++- lib/dino/board/base.rb | 6 ++++++ lib/dino/tx_rx/flow_control.rb | 4 ++++ 6 files changed, 41 insertions(+), 13 deletions(-) create mode 100644 examples/soft_serial.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index c1d3e0b7..7024f047 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,11 +38,11 @@ - Tone (piezo) support. _Uses Arduino `tone`,`noTone` functions._ -- SoftwareSerial. _Uses Arduino `SoftSerial` library._ (**write only / experimental**) +- SoftwareSerial **(write only)**. _Uses Arduino `SoftSerial` library. Only tested on ATmega chips._ -- Potentiometer class, based on AnalogInput, but enables moving average smoothing by default and adds #on_change callback method. +- Potentiometer class, based on AnalogInput, but enables moving average smoothing by default, and adds #on_change callback method. -- Rotary encoder support. _Uses polling method @ 1ms interval._ **WARNING**: Not suitable for high speed or precise position needs. It will definitely miss steps. Sufficient for rotary knobs as user input. +- Rotary encoder support. _Polls @ 1ms interval._ **WARNING**: Not suitable for high speed or precise position needs. It will definitely miss steps. Sufficient for rotary knobs as user input. - DHT11 / DHT 21 (AM2301) / DHT22 temperature and relative humidity sensor support. _Custom implementation where input pulses are measured on the board, then decoded in Ruby._ @@ -54,7 +54,7 @@ - Most bus features are implemented: reset/presence detect, parasite power handling, bus search and slave device identification, CRC. No overdrive support. - Based on [Kevin Darrah's video](https://www.youtube.com/watch?v=ZKNQhzPwH0s) explaining the DS18B20 datasheet. -- I2C bus support. _Uses Arduino`Wire` library._ +- I2C bus support. _Uses Arduino `Wire` library._ - Shift Register support. _Uses Arduino `ShiftOut` and `ShiftIn` functions._ @@ -71,7 +71,7 @@ - Digital and analog listeners now have dividers on a per-pin basis. - Timing is based on a 1000 microsecond tick being counted by the board. - - Call `#listen` with a value as the first argument. Eg. `analog_sensor.listen(64)` will tell the board to send us that specific sensor's state every 64 ticks (~64ms) or around 16 times per second, without affecting other components' rates. + - Call `#listen` with a value as the first argument. Eg. `analog_sensor.listen(64)` will tell the board to send us that specific sensor's state every 64 ticks (~64ms) or around 16 times per second, without affecting other components' rates. - Valid dividers are: `1, 2, 4, 8, 16, 32, 64, 128`. - Defaults are same as before: `4` for digital, `16` for analog. @@ -85,8 +85,8 @@ - `BoardProxy` abstraction for shift/SPI registers: - The `Register` classes implement enough of the `Board` interface to satisfy components based on `DigitalInput` and `DigitalOutput`, such as `Led` or `Button`. - This lets you call methods on components directly, rather than manipulating the register data to control components indirectly. - - Initialize the appropriate `Register` object for the type of register. To initialize a component connected to the register, use the register as the "board", and for the pin, give the parallel I/O pin of the register that the component is connected to. Pin 0 maps to the lowest bit. - - This also works for `MultiPin` components built out of only `DigitalInput` and `DigitalOutput`, eg. `SSD` - seven segment display or `RGBLed`. See `examples/register` for more. + - Initialize the appropriate `Register` object for the type of register. To initialize a component connected to the register, use the register as the `board:`, and give the parallel I/O pin on the register that the component is connected to. Pin 0 maps to the lowest bit. + - This also works for `MultiPin` components built out of only `DigitalInput` or `DigitalOutput`, eg. `SSD` - seven segment display or `RGBLed`. See `examples/register` for more. ### Input Components, Callbacks and State - `@value` has been renamed to `@state`. diff --git a/README.md b/README.md index a112419b..a881a5be 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,11 @@ button.down do end ```` -Dino doesn't run Ruby on the microcontroller board either, like [mruby](https://github.com/mruby/mruby). The board always runs a C++ firmware that exposes as much low-level I/O as possible, for us to use in Ruby. It becomes a peripheral for your computer. +Dino doesn't run Ruby on the microcontroller board either, like [mruby](https://github.com/mruby/mruby). The board runs a C++ firmware that exposes as much low-level I/O as possible, so we can use it in Ruby. It becomes a peripheral for your computer. -High-level abstraction in Ruby makes hardware classes easy to implement, with interfaces we expect. They get thread-safe state, callbacks for inputs, and mulitask a single core microcontroller, but no "task" priority. +High-level abstraction in Ruby makes hardware classes easy to implement, with interfaces we expect. They "multitask" a single core microcontroller, with thread-safe state and callbacks for inputs, but no "task" priority. If you need more, integration is seamless. Simply connect another board and instantiate. -Physical components connected to your boards map to Ruby objects you can use directly. You get to think about your hardware and appplication logic, not all the stuff in between. +Each physical component you connect to your board(s) maps to a Ruby object you can use directly. You get to think about your hardware and appplication logic, not all the stuff in between. ## Getting Started #### 1) Install the Gem @@ -112,7 +112,7 @@ arduino-cli board listall #### 6) Test It! -Most boards have an on-board LED. It's internally connected to pin 13 on Arduinos, but might be different for you. Run the LED example from [here](https://github.com/austinbv/dino/tree/master/examples/01-led/led.rb), changing the pin number if needed. If the LED starts blinking, you're ready for Ruby! +Most boards have an on-board LED. It's internally connected to pin 13 on Arduinos, but might be different for you. Run the LED example [here](https://github.com/austinbv/dino/tree/master/examples/01-led/led.rb). Change the pin number if needed. If the LED starts blinking, you're ready for Ruby! ## Examples and Tutorials @@ -122,7 +122,7 @@ Most boards have an on-board LED. It's internally connected to pin 13 on Arduino * 1 microcontroller (Arduino Uno, Leonardo and Mega are most compatible) * 1 button or momentary switch * 1 potentiometer (any value) - * 1 external RGB LED (4 legs common cathode, not a Neopixel, or individually addressable) + * 1 external RGB LED (4 legs common cathode, not a Neopixel or individually addressable) * 1 external LED (any color, or use one color of the RGB LED) * Current limiting resistors for the LEDs * Breadboard diff --git a/examples/soft_serial.rb b/examples/soft_serial.rb new file mode 100644 index 00000000..fa04c8a7 --- /dev/null +++ b/examples/soft_serial.rb @@ -0,0 +1,15 @@ +# +# Example of writing to software serial. +# +require 'bundler/setup' +require 'dino' + +board = Dino::Board.new(Dino::TxRx::Serial.new(device: "/dev/cu.usbmodem14B01")) + +# Even though an Rx pin is given here (it's needed by the lbirary), only transmission works for now. +soft_serial = Dino::Components::SoftwareSerial.new board: board, pins: { rx:10, tx:11 }, baud: 9600 + +soft_serial.puts "Hello World!" + +# The board writes asynchronously. Make sure it has written everything before we exit. +board.finish_write diff --git a/examples/stepper/stepper.rb b/examples/stepper/stepper.rb index c79c8b2c..a71daaa6 100644 --- a/examples/stepper/stepper.rb +++ b/examples/stepper/stepper.rb @@ -1,5 +1,5 @@ # -# This is a simple example to move a stepper motor using the sparkfun easydriver shield: https://www.sparkfun.com/products/10267? +# Example driving a stepper motor with the EasyDriver board: https://www.sparkfun.com/products/10267? # require 'bundler/setup' require 'dino' @@ -16,3 +16,6 @@ stepper.step_cw sleep 0.001 end + +# The board writes asynchronously. Make sure it has written everything before we exit. +board.finish_write diff --git a/lib/dino/board/base.rb b/lib/dino/board/base.rb index 02b2c0a5..6b9bf34a 100644 --- a/lib/dino/board/base.rb +++ b/lib/dino/board/base.rb @@ -19,6 +19,12 @@ def initialize(io, options={}) io.add_observer(self) self.analog_resolution = options[:bits] || 8 end + + def finish_write + sleep 0.001 while @io.writing? + write "\n91\n" + sleep 0.001 while @io.writing? + end def analog_resolution @bits ||= 8 diff --git a/lib/dino/tx_rx/flow_control.rb b/lib/dino/tx_rx/flow_control.rb index 5c4fd95e..4848b675 100644 --- a/lib/dino/tx_rx/flow_control.rb +++ b/lib/dino/tx_rx/flow_control.rb @@ -17,6 +17,10 @@ def write(message) @write_buffer << message end end + + def writing? + @write_buffer_mutex.synchronize { !@write_buffer.empty? } + end private From 3880ca55ebf60c11c2d59079f15adcd7d61446b4 Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 20 Feb 2023 11:36:08 -0400 Subject: [PATCH 256/296] Improve the stepper example --- README.md | 2 +- examples/stepper/stepper.rb | 31 ++++++++++++++++++----- lib/dino/components/stepper.rb | 45 +++++++++++++++++----------------- lib/dino_cli/targets.txt | 2 +- 4 files changed, 49 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index a881a5be..860ec708 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ dino sketch wifi -target esp8266 -ssid YOUR_SSID -password YOUR_PASSWORD * Connect the board to your computer with a USB cable. * Open the .ino file inside your sketch folder with the IDE. * Open the dropdown menu at the top of the IDE window, and select your board. -* Press the Upload ➡️ button. This will compile the sketch, and flash it to the board. +* Press the Upload :arrow_right: button. This will compile the sketch, and flash it to the board. **Troubleshooting:** * If your serial port is in the list, but the board is wrong, select the serial port anyway, then go to Tools > Board in the menus and choose your board from the list. diff --git a/examples/stepper/stepper.rb b/examples/stepper/stepper.rb index a71daaa6..77655348 100644 --- a/examples/stepper/stepper.rb +++ b/examples/stepper/stepper.rb @@ -5,17 +5,36 @@ require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) -stepper = Dino::Components::Stepper.new(board: board, pins: { step: 10, direction: 8 }) +stepper = Dino::Components::Stepper.new board: board, + pins: { slp: 6, enable: 7, direction: 8, step: 9, ms1: 10, ms2: 11 } + +# Default is 8 microsteps. Set to 2 so we can move faster. +stepper.microsteps = 2 -1600.times do +# 400 steps is now 1 revolution for a 200 step motor. +400.times do stepper.step_cc - sleep 0.001 + sleep 0.002 end -1600.times do +# Sleep the driver chip and wait a while. +stepper.sleep +sleep 1 + +# Wake it up and set to full steps. +stepper.wake +stepper.microsteps = 1 + +# +# Now 200 steps the other way will move us back to the start. +# Note the longer sleep here since the steps are bigger. +# Adjust both sleep vales to suit your motor. +# +200.times do stepper.step_cw - sleep 0.001 + sleep 0.006 end -# The board writes asynchronously. Make sure it has written everything before we exit. +# We write to the board asynchronously. +# Make sure we send all step commands before exit. board.finish_write diff --git a/lib/dino/components/stepper.rb b/lib/dino/components/stepper.rb index 9716e75a..b6b12af4 100644 --- a/lib/dino/components/stepper.rb +++ b/lib/dino/components/stepper.rb @@ -9,46 +9,45 @@ class Stepper proxy_pins ms1: Basic::DigitalOutput, ms2: Basic::DigitalOutput, enable: Basic::DigitalOutput, - sleep: Basic::DigitalOutput, - optional: true - + slp: Basic::DigitalOutput, + optional: true + + attr_reader :microsteps + def after_initialize(options={}) - wake; on; divider = 8 + wake; on; + self.microsteps = 8 end - def sleep_mode - sleep.low if pins[:sleep] + def sleep + slp.low if slp end def wake - sleep.high if pins[:sleep] + slp.high if slp end def off - enable.high if pins[:enable] + enable.high if enable end def on - enable.low if pins[:enable] + enable.low if enable end - def divider=(steps) - return unless (ms1 && ms2) - case steps.to_i - when 1 - ms1.low; ms2.low - when 2 - ms1.high; ms2.low - when 4 - ms1.low; ms2.high - when 8 - ms1.high; ms2.high + def microsteps=(steps) + if (ms1 && ms2) + case steps.to_i + when 1; ms2.low; ms1.low + when 2; ms2.low; ms1.high + when 4; ms2.high; ms1.low + when 8; ms2.high; ms1.high + end else - return + raise ArgumentError, "ms1 and ms2 pins must be connected to GPIO pins to control microstepping." end - @divider = steps + @microsteps = steps end - attr_reader :divider def step_cc direction.high unless direction.high? diff --git a/lib/dino_cli/targets.txt b/lib/dino_cli/targets.txt index 1fcf52e3..9d3afa61 100644 --- a/lib/dino_cli/targets.txt +++ b/lib/dino_cli/targets.txt @@ -42,7 +42,7 @@ This specifically targets the older ATmega168 chip used in early Arduinos. With half the RAM and flash available, we need to cut the sketch down. This option includes core functionality AND: - Servo (6 maxmium), DHT, OneWire, IR Out, Tone, SPI, I2C + Servo (6 maxmium), DHT, Shift In/Out, Tone, SPI, I2C Chips: ATmega168 Boards: Duemilanove, Diecimila, Pro, Pro Mini From 4d61f87d6148d1c69ab6a537f524d8a7dd749160 Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 20 Feb 2023 13:31:47 -0400 Subject: [PATCH 257/296] Add wifi library to readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 860ec708..76942747 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ Dino uses Arduino "cores" which add microcontroller support, and a few C++ libra * Now go to: Tools > Manage Libraries. * Find and install the following libraries, at the version numbers given: * `Liquid Crystal by Arduino, Adafruit` at latest version + * `WiFi by Arduino` at latest version * `IRremote by shirriff, z3to, ArminJo` at `version 2.2.3` * `IRremoteESP82666 by David Conran, Sebastien Warin` at `version 2.3.2` @@ -54,8 +55,9 @@ Dino uses Arduino "cores" which add microcontroller support, and a few C++ libra ````shell arduino-cli core install esp8266:esp8266 arduino-cli lib install LiquidCrystal +arduino-cli lib install WiFi arduino-cli lib install IRremote@2.2.3 -arduino-cli lib install IRremoteESP8266@2.3.2 +arduino-cli lib install IRremoteESP8266@2.3.2 ```` #### 4) Generate the Arduino Sketch From c54edfa97bd514ba16768623f2d27251c93e7913 Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 20 Feb 2023 13:32:11 -0400 Subject: [PATCH 258/296] Sleep stepper driver after example --- examples/stepper/stepper.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/stepper/stepper.rb b/examples/stepper/stepper.rb index 77655348..6e03e21b 100644 --- a/examples/stepper/stepper.rb +++ b/examples/stepper/stepper.rb @@ -35,6 +35,9 @@ sleep 0.006 end +# Sleep the driver once we're done. +stepper.sleep + # We write to the board asynchronously. # Make sure we send all step commands before exit. board.finish_write From 737de0bf5a70aed8bf384e9f484d65b1374032bf Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 20 Feb 2023 15:18:36 -0400 Subject: [PATCH 259/296] Track supported hardware in HARDWARE.md --- HARDWARE.md | 171 ++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 8 +-- 2 files changed, 175 insertions(+), 4 deletions(-) create mode 100644 HARDWARE.md diff --git a/HARDWARE.md b/HARDWARE.md new file mode 100644 index 00000000..122c61b6 --- /dev/null +++ b/HARDWARE.md @@ -0,0 +1,171 @@ +# Supported Microcontrollers + +:green_heart: Full support :yellow_heart: Partial support :heart: Planned. No support yet + +### Microchip/Atmel Chips in Arduino Products (and Compatibles) + +| Chip | Status | Version| Boards | Notes | +| :-------- | :------: | :----- | :--------------- |------ | +| ATmega168 | :yellow_heart: | 0.12.0 | Duemilanove, Diecimila, Pro | Features removed for memory. Configurable. Run `dino targets` for more +| ATmega328 | :green_heart: | 0.11.0 | Uno, Nano, Fio, Pro | +| ATmega32u4 | :green_heart: | 0.11.0 | Leonardo, Micro, Leonardo ETH, Esplora, LilyPad USB | **v0.11.1** for Leonardo ETH +| ATmega1280 | :green_heart: | 0.11.1 | Mega | +| ATmega2560 | :green_heart: | 0.11.1 | Mega2560, Arduino Mega ADK | +| ATSAM3X8E | :yellow_heart: | 0.12.0 | Due | Uses native USB. SoftSerial, Tone, IR Out, and I2C don't work yet +| ATSAMD21 | :heart: | - | Zero, M0, M0 Pro | + +**Note:** Only USB boards listed. Any supported chip should work, once you can flash it and connect to serial. + +### Arduino Accessories + +| Chip | Status | Version| Product | Notes | +| :-------- | :------: | :----- | :--------------- |------ | +| Wiznet W5100/5500 | :green_heart: | 0.11.1 | Ethernet Shield | Wired Ethernet for Uno/Mega pin-compatible boards +| HDG204 + AT32UC3 | :question: | 0.12.0 | WiFi Shield | WiFi for Uno. No hardware to test, but compiles +| ATWINC1500 | :question: | 0.12.0 | WiFi Shield 101 | As above, but heavy on RAM. Compiles for Mega only + +### Espressif Chips with Built-In WiFi + +| Chip | Status | Version| Boards | Notes | +| :-------- | :------: | :----- | :--------------- |------ | +| ESP8266 | :yellow_heart: | 0.12.0 | NodeMCU | SoftwareSerial and LCD don't work yet +| ESP32 | :heart: | - | DOIT ESP32 DevKit V1 | Original ESP-WROOM-32 +| ESP32-S2 | :heart: | - | LOLIN S2 Pico | Single core Xtensa, native USB +| ESP32-S3 | :heart: | - | LOLIN S3 V1.0.0 | Dual core RISC-V, native USB + +**Note:** There are too many boards using these chips to be comprehensive. Most should work. These are the exact ones used for testing, chosen based on popularity. + +# Supported Components + +:green_heart: Full support :yellow_heart: Partial support :heart: Planned. No support yet + +### Basic GPIO Interface + +| Name | Status | Version | Component Class | Notes | +| :--------------- | :------: | :----- | :------ | :---- | +| Digital Out | :green_heart: | 0.11.0 | `DigitalOutput` | - | +| Digital In | :green_heart: | 0.11.0 | `DigitalInput` | 1ms - 128ms (4ms default) listen, poll, or read +| Analog (PWM) Out | :green_heart: | 0.11.0 | `AnalogOutput` | +| Analog (ADC) In | :green_heart: | 0.11.0 | `AnalogInput` | 1ms - 128ms (16ms default) listen, poll, or read +| Analog (DAC) Out | :green_heart: | 0.12.0 | `AnalogOutput` | Only present on Arduino Due and ESP32 +| Tone Out (Square Wave)| :green_heart: | 0.12.0 | - | Not working on Due yet + +**Note:** When listening, the board checks the pin's value every **_2^n_** ms (**_n_** from **_0_** to **_7_**), without further prompting. Polling and reading follow a call and response pattern. + +### Advanced Interfaces + +| Name | Status | SW/HW | Version | Component Class | Notes | +| :--------------- | :------: | :-------- | :----- | :--------------- |------ | +| I2C | :green_heart: | Hardware | 0.12.0 | `I2C::Bus` +| SPI | :green_heart: | Hardware | 0.12.0 | `Register::Select` | Hardware register I/O +| Shift In/Out | :green_heart: | Software | 0.12.0 | `Register::Select` | Bit bang register I/O +| Software Serial | :yellow_heart: | Software | 0.12.0 | `SoftwareSerial` | No read, only write +| Hardware Serial | :heart: | Hardware | - | - | For boards with native USB and UARTs +| Maxim OneWire | :green_heart: | Software | 0.12.0 | `OneWire::Bus` | No overdrivee support +| Infrared Emitter | :green_heart: | Software | 0.12.0 | `IREmitter` | + +### Generic Components + +| Name | Status | Interface | Version | Component Class | Notes | +| :--------------- | :------: | :-------- | :----- | :--------------- |------ | +| Board EEPROM | :green_heart: | Built-In | 0.12.0 | `BoardEEPROM` | Not all boards have EEPROM +| Led | :green_heart: | Digi/Ana Out| 0.11.0 | `Led` | +| RGBLed | :green_heart: | Digi/Ana Out| 0.11.0 | `RGBLed` | +| Relay | :green_heart: | Digital Out | 0.11.0 | `Relay` | +| 7 Segment Display| :yellow_heart: | Digital Out | 0.12.0 | `SSD` | No decimal point +| Button | :green_heart: | Digital In | 0.11.0 | `Button` | +| Rotary Encoder | :green_heart: | Digital In | 0.12.0 | `RotaryEncoder` | Listens every 1ms +| PIR Sensor | :yellow_heart: | Digital In | 0.11.0 | `DigitalInput` | Needs class. HC-SR501 +| Analog Sensor | :green_heart: | Analog In | 0.11.0 | `Sensor` | +| Potentiometer | :green_heart: | Analog In | 0.12.0 | `Potentiometer` | Smoothing on by default +| Piezo Buzzer | :green_heart: | Tone Out | 0.12.0 | `Piezo` | Frequency > 30Hz +| Input Register | :green_heart: | ShiftIn | 0.12.0 | `Register::ShiftIn` | Tested on CD4021B +| Input Register | :green_heart: | SPI | 0.12.0 | `Register::SPIIn` | Tested on CD4021B +| Output Register | :green_heart: | ShiftOut | 0.12.0 | `Register::ShiftOut` | Tested on 74HC595 +| Output Register | :green_heart: | SPI | 0.12.0 | `Register::SPIOut` | Tested on 74HC595 + +**Note:** Most Digital In and Out components can be used seamlessley through Input and Output Registers respectively. + +### Motors / Motor Drivers + +| Name | Status | Interface | Version | Component Class | Notes | +| :--------------- | :------: | :-------- | :----- | :--------------- |------ | +| Servo | :green_heart: | Direct | 0.11.2 | `Servo` | Max 6 servos on the ATmega168, 12 otherwise +| L298N | :heart: | Direct | | - | 2ch DC motor driver +| A3967 | :green_heart: | Direct | 0.12.0 | `Stepper`| 1ch microstepper. Tested with EasyDriver board +| PCA9685 | :heart: | I2C | - | - | 16ch 12-bit PWM for servo, DC motor, or LED + +### Displays + +| Name | Status | Interface | Version | Component Class | Notes | +| :--------------- | :------: | :-------- | :----- | :--------------- |------ | +| HD44780 LCD | :green_heart: | Direct, C++ Lib | 0.12.0 | `LCD` | Could make it work through registers +| SSD1306 OLED | :heart: | I2C | - | - | + +### Addressable LEDs + +| Name | Status | Interface | Version | Component Class | Notes | +| :--------------- | :------: | :-------- | :----- | :--------------- |------ | +| Neopixel / WS2812B | :heart: | - | - | - | +| Dotstar / APA102 | :grey_exclamation: | SPI | - | - | + +### I/O Expansion + +| Name | Status | Interface | Version | Component Class | Notes | +| :--------------- | :------: | :-------- | :----- | :--------------- |------ +| PCF8574 Expander | :heart: | I2C | - | - | 8ch bi-directional digital I/O +| ADS1115 ADC | :heart: | I2C | - | - | 16-bit, 4ch analog to digital converter +| ADS1118 ADC | :heart: | SPI | - | - | 16-bit, 4ch analog to digital converter +| PCF8591 ADC/DAC | :heart: | I2C | - | - | 4ch ADC + 1ch DAC, 8-bit resolution + +### Environmental Sensors + +| Name | Status | Interface | Version | Component Class | Notes | +| :--------------- | :------: | :-------- | :----- | :--------------- |------ | +| DHT 11/21/22 | :green_heart: | Direct | 0.12.0 | `DHT` | Temperature, Humidity +| DS18B20 | :green_heart: | OneWire | 0.12.0 | `OneWire::DS18B20`| Temperature +| MAX31850 | :grey_exclamation: | OneWire | - | - | Thermocouple +| BME280 | :heart: | I2C | - | - | Pressure, Temperature, Humidity +| BMP280 | :heart: | I2C | - | - | Pressure, Temperature +| HTU21D | :heart: | I2C | - | - | Temperature, Humidity +| HTU31D | :heart: | I2C | - | - | Temperature, Humidity +| AHT10 | :heart: | I2C | - | - | Temperature, Humidity +| AHT21 | :heart: | I2C | - | - | Temperature, Humidity +| ENS160 | :heart: | I2C | - | - | CO2e, TVOC, AQI +| AGS02MA | :heart: | I2C | - | - | TVOC + + +### Light Sensors + +| Name | Status | Interface | Version | Component Class | Notes | +| :--------------- | :------: | :-------- | :----- | :--------------- |------ | +| APDS9960 | :heart: | I2C | - | - | Proximity, RGB, Gesture + + +### Distance Sensors + +| Name | Status | Interface | Version | Component Class | Notes | +| :--------------- | :------: | :-------- | :----- | :--------------- |------ | +| HC-SR04 | :heart: | Direct | - | - | Ultrasonic, 20-4000mm +| VL53L0X | :heart: | I2C | - | - | Laser, 30 - 1000mm +| GP2Y0E03 | :heart: | I2C | - | - | Infrared, 40 - 500mm + +### Motion Sensors + +| Name | Status | Interface | Version | Component Class | Notes | +| :--------------- | :------: | :-------- | :----- | :--------------- |------ | +| ADXL345 | :heart: | I2C | - | - | 3-axis Accelerometer +| IT3205 | :heart: | I2C | - | - | 3-axis Gyroscope +| HMC5883L | :heart: | I2C | - | - | 3-axis Compass + +### Real Time Clock + +| Name | Status | Interface | Version | Component Class | Notes | +| :--------------- | :------: | :-------- | :----- | :--------------- |------ | +| DS3221 | :yellow_heart: | I2C | 0.12.0 | `I2C::DS3231` | Only set and get time implemented + +### Miscellaneous + +| Name | Status | Interface | Version | Component Class | Notes | +| :--------------- | :------: | :-------- | :----- | :--------------- |------ | +| MFRC522 | :heart: | SPI / I2C | - | - | RFID tag reader / writer diff --git a/README.md b/README.md index 76942747..e56cf782 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Dino doesn't run Ruby on the microcontroller board either, like [mruby](https:// High-level abstraction in Ruby makes hardware classes easy to implement, with interfaces we expect. They "multitask" a single core microcontroller, with thread-safe state and callbacks for inputs, but no "task" priority. If you need more, integration is seamless. Simply connect another board and instantiate. -Each physical component you connect to your board(s) maps to a Ruby object you can use directly. You get to think about your hardware and appplication logic, not all the stuff in between. +Each physical component you connect to your board(s) maps to a Ruby object you can use directly. You get to think about your hardware and appplication logic, not all the stuff in between. See supported hardware [here](HARDWARE.md). ## Getting Started #### 1) Install the Gem @@ -77,7 +77,7 @@ dino sketch serial -target esp8266 ```shell dino sketch wifi -target esp8266 -ssid YOUR_SSID -password YOUR_PASSWORD ```` -**Note:** [This example](https://github.com/austinbv/dino/tree/master/examples/tcp.rb) shows how to instantiate a board over the network. You'll need to figure out what IP address your board got, and modify the TxRx line in other examples you wish to run. They are written for serial. +**Note:** [This example](examples/tcp.rb) shows how to instantiate a board over the network. You'll need to figure out what IP address your board got, and modify the TxRx line in other examples you wish to run. They are written for serial. #### 5a) IDE Flashing @@ -114,13 +114,13 @@ arduino-cli board listall #### 6) Test It! -Most boards have an on-board LED. It's internally connected to pin 13 on Arduinos, but might be different for you. Run the LED example [here](https://github.com/austinbv/dino/tree/master/examples/01-led/led.rb). Change the pin number if needed. If the LED starts blinking, you're ready for Ruby! +Most boards have an on-board LED. It's internally connected to pin 13 on Arduinos, but might be different for you. Run the LED example [here](examples/01-led/led.rb). Change the pin number if needed. If the LED starts blinking, you're ready for Ruby! ## Examples and Tutorials #### Included Examples -* The first 5 [examples](https://github.com/austinbv/dino/tree/master/examples) are sort of a mini-tutorial, to familiarize you with the basics. Read the comments and try modifying the code. You will need the following: +* The first 5 [examples](examples) are sort of a mini-tutorial, to familiarize you with the basics. Read the comments and try modifying the code. You will need the following: * 1 microcontroller (Arduino Uno, Leonardo and Mega are most compatible) * 1 button or momentary switch * 1 potentiometer (any value) From 2e1737a876846456ce002305e7693cfb12e9604a Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 20 Feb 2023 20:13:10 -0400 Subject: [PATCH 260/296] Add servo dependency to readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e56cf782..1a0f5a6c 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ Dino uses Arduino "cores" which add microcontroller support, and a few C++ libra * Search for `ESP8266 Boards` and install the latest version. * Now go to: Tools > Manage Libraries. * Find and install the following libraries, at the version numbers given: + * `Servo by Michael Margolis, Arduino` at latest version * `Liquid Crystal by Arduino, Adafruit` at latest version * `WiFi by Arduino` at latest version * `IRremote by shirriff, z3to, ArminJo` at `version 2.2.3` @@ -54,6 +55,7 @@ Dino uses Arduino "cores" which add microcontroller support, and a few C++ libra **CLI:** ````shell arduino-cli core install esp8266:esp8266 +arduino-cli lib install Servo arduino-cli lib install LiquidCrystal arduino-cli lib install WiFi arduino-cli lib install IRremote@2.2.3 From 3b29dec241ba5e9df1f41818d6ca392685a1170a Mon Sep 17 00:00:00 2001 From: vickash Date: Mon, 20 Feb 2023 23:02:13 -0400 Subject: [PATCH 261/296] Add basic ESP32 support --- HARDWARE.md | 10 ++++---- lib/dino_cli/targets.rb | 5 +++- lib/dino_cli/targets.txt | 37 +++++++++++++++++++--------- src/lib/Dino.cpp | 5 +++- src/lib/Dino.h | 15 ++++++++++++ src/lib/DinoCoreIO.cpp | 53 +++++++++++++++++++++++++++++++++++++++- 6 files changed, 106 insertions(+), 19 deletions(-) diff --git a/HARDWARE.md b/HARDWARE.md index 122c61b6..53380d27 100644 --- a/HARDWARE.md +++ b/HARDWARE.md @@ -21,17 +21,17 @@ | Chip | Status | Version| Product | Notes | | :-------- | :------: | :----- | :--------------- |------ | | Wiznet W5100/5500 | :green_heart: | 0.11.1 | Ethernet Shield | Wired Ethernet for Uno/Mega pin-compatible boards -| HDG204 + AT32UC3 | :question: | 0.12.0 | WiFi Shield | WiFi for Uno. No hardware to test, but compiles -| ATWINC1500 | :question: | 0.12.0 | WiFi Shield 101 | As above, but heavy on RAM. Compiles for Mega only +| HDG204 + AT32UC3 | :man_shrugging: | 0.12.0 | WiFi Shield | WiFi for Uno. No hardware to test, but compiles +| ATWINC1500 | :man_shrugging: | 0.12.0 | WiFi Shield 101 | As above, but heavy on RAM. Compiles for Mega only ### Espressif Chips with Built-In WiFi | Chip | Status | Version| Boards | Notes | | :-------- | :------: | :----- | :--------------- |------ | | ESP8266 | :yellow_heart: | 0.12.0 | NodeMCU | SoftwareSerial and LCD don't work yet -| ESP32 | :heart: | - | DOIT ESP32 DevKit V1 | Original ESP-WROOM-32 -| ESP32-S2 | :heart: | - | LOLIN S2 Pico | Single core Xtensa, native USB -| ESP32-S3 | :heart: | - | LOLIN S3 V1.0.0 | Dual core RISC-V, native USB +| ESP32 | :test_tube: | - | DOIT ESP32 DevKit V1 | Original ESP-WROOM-32 +| ESP32-S2 | :test_tube: | - | LOLIN S2 Pico | Single core Xtensa, native USB +| ESP32-S3 | :test_tube: | - | LOLIN S3 V1.0.0 | Dual core RISC-V, native USB **Note:** There are too many boards using these chips to be comprehensive. Most should work. These are the exact ones used for testing, chosen based on popularity. diff --git a/lib/dino_cli/targets.rb b/lib/dino_cli/targets.rb index 2d18ddfe..473de12b 100644 --- a/lib/dino_cli/targets.rb +++ b/lib/dino_cli/targets.rb @@ -16,7 +16,10 @@ class DinoCLI::Generator # ARM includes everytyhing except specific incompatibilities. sam3x: STANDARD_PACKAGES - [:serial, :tone, :ir_out], - # A surprising amount "just works" on the ESP, notably not LCD. + # ESP8266 mostly working. esp8266: STANDARD_PACKAGES - [:lcd, :serial, :ir_out] + [:ir_out_esp8266], + + # Just core implementation on the ESP32 for now. + esp32: [:core] } end diff --git a/lib/dino_cli/targets.txt b/lib/dino_cli/targets.txt index 9d3afa61..4613c0d6 100644 --- a/lib/dino_cli/targets.txt +++ b/lib/dino_cli/targets.txt @@ -7,11 +7,12 @@ primary target and default setting. All component features are included. Chips: ATmega328p, ATmega32u4, ATmega1280, ATmega2560 - Boards: UNO, Nano, Mini, Ethernet + Boards: Arduino Uno, Nano, Mini, Ethernet Leonardo, Micro, Esplora, Leonardo ETH Mega 1280, Mega 2560, Mega ADK Most Lilypads (excluding ones with an ATmega168) + esp8266 Based on the ESP8266 chip by Espressif with integrated 2.4Ghz WiFi. @@ -26,6 +27,29 @@ Note: After the initial upload via serial, the WiFi sketch for this target supports over-the-air updates from the Arduino IDE using ArduinoOTA. + + + esp32 + + Based on the ESP32 chip by Espressif with integrated 2.4Ghz WiFi. + This is still EXPERIMENTAL! Only includes the core subset. WiFi not working. + + Chips: ESP32, ESP32-S2, ESP32-S3 + Boards: ESP32 DevKit, LOLIN D32 / D32 Pro, LOLIN S2 Pico / Mini, + LOLIN S3 Pico / Mini + + + sam3x + + This is the same as mega, but omits libraries specifically known to be + incompatible with the Atmel SAMD (ARM Cortex M0) chips. Use this when + you need to load a sketch on the Arduino Due or similar boards. + This option includes all component features EXCEPT: + Serial, Tone, IR Out, I2C + + Chips: AT91SAM3X8E + Boards: Arduino Due + core @@ -37,6 +61,7 @@ This is mostly useful for testing, or if severely limited on memory, as it is the smallest sketch possible and should be universally compatible. + mega168 This specifically targets the older ATmega168 chip used in early Arduinos. @@ -58,16 +83,6 @@ These limits apply as long as an Atmega168 is selected in the Arduino IDE, regardless of the options passed to this sketch generator. - sam3x - - This is the same as mega, but omits libraries specifically known to be - incompatible with the Atmel SAMD (ARM Cortex M0) chips. Use this when - you need to load a sketch on the Arduino Due or similar boards. - This option includes all component features EXCEPT: - Serial, Tone, IR Out, I2C - - Chips: AT91SAM3X8E - Boards: Due NOTE: While different targets include different libraries, you can manually control which libraries are included at compile time by editing DinoDefines.h. diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index f48063ee..18cbdf00 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -227,7 +227,7 @@ void Dino::handshake() { #ifdef ESP8266 stream->print(ESP8266_EEPROM_LENGTH); #elif defined(EEPROM_PRESENT) - stream->print(EEPROM.length()); + stream->print(EEPROM.length()); #else stream->print('0'); #endif @@ -244,6 +244,9 @@ void Dino::handshake() { // CMD = 91 void Dino::resetState() { clearCoreListeners(); + #ifdef ESP32 + clearLedcChannels(); + #endif #ifdef DINO_SPI clearSpiListeners(); #endif diff --git a/src/lib/Dino.h b/src/lib/Dino.h index f1170389..488e6f01 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -68,6 +68,21 @@ class Dino { void clearCoreListeners (); void findLastActiveListener(); + // + // Tanslating aWrite to ledcWrite for PWM out on the ESP32. + // + #ifdef ESP32 + // + // Track which pin is assigned to each LEDC channel. + // Byte 0 = enabled or disabled + // Byte 1 = pin number attached to that channel + #define LEDC_CHANNEL_COUNT 16 + byte ledcPins[LEDC_CHANNEL_COUNT][2]; + + byte ledcChannel(byte p); + void clearLedcChannels(); + #endif + // // Store listeners as a 2 dimensional array where each gets 2 bytes: // diff --git a/src/lib/DinoCoreIO.cpp b/src/lib/DinoCoreIO.cpp index eee8d3e5..e408bc21 100644 --- a/src/lib/DinoCoreIO.cpp +++ b/src/lib/DinoCoreIO.cpp @@ -14,7 +14,11 @@ void Dino::setMode(byte p, byte m) { pinMode(p, OUTPUT); } else { + #ifdef ESP32 + pinMode(p, INPUT_PULLUP); + #else pinMode(p, INPUT); + #endif } } @@ -64,10 +68,57 @@ void Dino::aWrite(byte p, int v, boolean echo) { Serial.println(echo); #endif - analogWrite(p,v); + #ifdef ESP32 + byte channel = ledcChannel(p); + ledcWrite(channel, v); + #else + analogWrite(p,v); + #endif + if (echo) coreResponse(p, v); } +// +// Manage ESP32 LEDC channels so we can do PWM write. +// +#ifdef ESP32 +byte Dino::ledcChannel(byte p) { + // Return a useless channel if none available. + byte channel = 255; + + // Search for existing LEDC channel with our pin first. + for (byte i = 0; i < LEDC_CHANNEL_COUNT; i++){ + if (ledcPins[i][1] == p) { + channel = i; + break; + } + } + + // If channel is still 255 we didn't find one, so make one. + if (channel == 255){ + for (byte i = 0; i < LEDC_CHANNEL_COUNT; i++){ + // 0th byte being 0 means not in use. + if (ledcPins[i][0] == 0) { + // Just use similar settings to ATmega for now. + ledcSetup(i, 1000, 8); + ledcAttachPin(p, i); + ledcPins[i][1] = p; + channel = i; + break; + } + } + } + return channel; +}; + +// 0th byte = 0 for not in use. +void Dino::clearLedcChannels(){ + for (byte i = 0; i < LEDC_CHANNEL_COUNT; i++){ + ledcPins[i][0] = 0; + } +} +#endif + // CMD = 04 // Read an analog input pin. 0 for LOW, up to 1023 for HIGH @ 10-bit resolution. int Dino::aRead(byte p) { From abc39e0a5d67fb986a650d53e51c8a8d8bf00882 Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 21 Feb 2023 01:07:19 -0400 Subject: [PATCH 262/296] Fix WiFi, EEPROM and ADC/DAC reporting for ESP32 --- lib/dino_cli/targets.txt | 2 +- src/dino_wifi.ino | 43 +++++++++++++++++++++++++--------------- src/lib/Dino.cpp | 29 +++++++++++++++++---------- src/lib/Dino.h | 12 ++++++++--- src/lib/DinoEEPROM.cpp | 24 +++++++++++----------- 5 files changed, 67 insertions(+), 43 deletions(-) diff --git a/lib/dino_cli/targets.txt b/lib/dino_cli/targets.txt index 4613c0d6..757c6298 100644 --- a/lib/dino_cli/targets.txt +++ b/lib/dino_cli/targets.txt @@ -32,7 +32,7 @@ esp32 Based on the ESP32 chip by Espressif with integrated 2.4Ghz WiFi. - This is still EXPERIMENTAL! Only includes the core subset. WiFi not working. + This is still EXPERIMENTAL! Only includes the core subset. WiFi works. No OTA yet. Chips: ESP32, ESP32-S2, ESP32-S3 Boards: ESP32 DevKit, LOLIN D32 / D32 Pro, LOLIN S2 Pico / Mini, diff --git a/src/dino_wifi.ino b/src/dino_wifi.ino index 8fd53a08..4f29b366 100644 --- a/src/dino_wifi.ino +++ b/src/dino_wifi.ino @@ -1,16 +1,23 @@ #include "Dino.h" -#ifdef ESP8266 +#if defined(ESP8266) #include #include #include #include #define WIFI_STATUS_LED 2 +#elif defined(ESP32) + #include + #include + // #include + // #include + #define WIFI_STATUS_LED 2 #else #include #include #define WIFI_STATUS_LED 13 #endif + // Define 'serial' as the serial interface we want to use. // Defaults to Native USB port on the Due, whatever class "Serial" is on everything else. // Classes need to inherit from Stream to be compatible with the Dino library. @@ -26,8 +33,6 @@ int port = 3466; char* ssid = "yourNetwork"; char* pass = "yourPassword"; boolean connected = false; -long lastConnectAttempt; -int WiFiConnectTimeout = 10000; Dino dino; WiFiServer server(port); @@ -36,7 +41,7 @@ WiFiClient client; // Use the built in LED to indicate WiFi status. void indicateWiFi(byte value) { pinMode(WIFI_STATUS_LED, OUTPUT); - #ifdef ESP8266 + #if defined(ESP8266) digitalWrite(WIFI_STATUS_LED, !value); #else digitalWrite(WIFI_STATUS_LED, value); @@ -58,13 +63,21 @@ void printWifiStatus() { } void connect(){ - #ifdef ESP8266 + // Make sure we're in STA mode on ESP boards, which can also be AP. + #if defined(ESP8266) || defined(ESP32) WiFi.mode(WIFI_STA); #endif - if (millis() - lastConnectAttempt > WiFiConnectTimeout){ - WiFi.begin(ssid, pass); - lastConnectAttempt = millis(); + + // Try to connect. + serial.print("Connecting to WiFi "); + WiFi.begin(ssid, pass); + + // Delay until connected. + while (WiFi.status() != WL_CONNECTED) { + delay(500); + serial.print("."); } + connected = true; } void maintainWiFi(){ @@ -88,15 +101,10 @@ void setup() { serial.begin(115200); while(!serial); - // Enable over the air updates and "EEPROM" on the ESP8266. - #ifdef ESP8266 - EEPROM.begin(512); + // Enable over the air updates on the ESP8266. + #if defined(ESP8266) ArduinoOTA.begin(); #endif - // Start the dino TCP server. - server.begin(); - - delay(2000); // Attempt initial WiFi connection. #ifdef debug @@ -106,6 +114,9 @@ void setup() { serial.println(ssid); #endif connect(); + + // Start the dino TCP server. + server.begin(); // Add listener callbacks for local logic. dino.digitalListenCallback = onDigitalListen; @@ -135,7 +146,7 @@ void loop() { } // Handle OTA updates. - #ifdef ESP8266 + #if defined(ESP8266) ArduinoOTA.handle(); #endif } diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 18cbdf00..211285ad 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -101,11 +101,11 @@ void Dino::process() { case 4: aRead (pin); break; case 5: setListener (pin, val, auxMsg[0], auxMsg[1], false); break; - #ifdef EEPROM_PRESENT + #ifdef EEPROM_PRESENT // Implemented in DinoEEPROM.cpp case 6: eepromRead (); break; case 7: eepromWrite (); break; - #endif + #endif // Implemented in DinoServo.cpp #ifdef DINO_SERVO @@ -217,27 +217,34 @@ void Dino::handshake() { // Reset this so we never send Rx along with ACK: rxBytes = 0; - + + // First value is aux size in bytes. stream->print("ACK:"); stream->print(AUX_SIZE); - stream->print(','); - // Send the defined length for ESP8266 EEPROM and initialize later. - // Read the value and send that for other boards, except the Due. - #ifdef ESP8266 - stream->print(ESP8266_EEPROM_LENGTH); + // Second is EEPROM size in bytes. None on Due. + stream->print(','); + #if defined(ESP8266) || defined(ESP32) + stream->print(ESP_EEPROM_LENGTH); #elif defined(EEPROM_PRESENT) stream->print(EEPROM.length()); #else stream->print('0'); #endif - - stream->print(','); - stream->print(A0); + + // Third is A0. Ignore for ESP32. Pins aren't in order. + #if !defined(ESP32) + stream->print(','); + stream->print(A0); + #endif + + // 4th is DAC0 if available. Ignore fore ESP32. Also not in order. #if defined(__SAM3X8E__) stream->print(','); stream->print(DAC0); #endif + + // End stream->print('\n'); } diff --git a/src/lib/Dino.h b/src/lib/Dino.h index 488e6f01..de1ee387 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -11,13 +11,17 @@ # define PIN_COUNT 70 #elif defined(__SAM3X8E__) # define PIN_COUNT 66 +#elif defined(ESP8266) +# define PIN_COUNT 17 +#elif defined(ESP32) +# define PIN_COUNT 40 #else # define PIN_COUNT 22 #endif // Use the maximum length for the ESP8266 EEPROM. -#ifdef ESP8266 -# define ESP8266_EEPROM_LENGTH 4096 +#if defined(ESP8266) || defined(ESP32) +# define ESP_EEPROM_LENGTH 512 #endif class Dino { @@ -182,7 +186,9 @@ class Dino { byte valStr[4]; byte val; // Scale aux message allocation based on enabled features and chip. - #if defined(DINO_IR_OUT) && !defined (__AVR_ATmega168__) + # if defined(ESP8266) || defined(ESP32) + # define AUX_SIZE 528 + #elif defined(DINO_IR_OUT) && !defined(__AVR_ATmega168__) # define AUX_SIZE 528 #elif defined(DINO_SHIFT) || defined(DINO_SPI) || defined (DINO_I2C) # define AUX_SIZE 264 diff --git a/src/lib/DinoEEPROM.cpp b/src/lib/DinoEEPROM.cpp index dd898c9f..1ea4bc7e 100644 --- a/src/lib/DinoEEPROM.cpp +++ b/src/lib/DinoEEPROM.cpp @@ -13,8 +13,8 @@ // void Dino::eepromRead(){ if (val > 0) { - #ifdef ESP8266 - EEPROM.begin(ESP8266_EEPROM_LENGTH); + #if defined(ESP8266) || defined(ESP32) + EEPROM.begin(ESP_EEPROM_LENGTH); #endif uint16_t startAddress = ((uint16_t)auxMsg[1] << 8) | auxMsg[0]; @@ -30,9 +30,9 @@ void Dino::eepromRead(){ stream->print((i+1 == val) ? '\n' : ','); } - #ifdef ESP8266 - EEPROM.end(); - #endif + #if defined(ESP8266) || defined(ESP32) + EEPROM.end(); + #endif } } @@ -46,9 +46,9 @@ void Dino::eepromRead(){ // void Dino::eepromWrite(){ if (val > 0) { - #ifdef ESP8266 - EEPROM.begin(ESP8266_EEPROM_LENGTH); - #endif + #if defined(ESP8266) || defined(ESP32) + EEPROM.begin(ESP_EEPROM_LENGTH); + #endif uint16_t startAddress = ((uint16_t)auxMsg[1] << 8) | auxMsg[0]; @@ -56,10 +56,10 @@ void Dino::eepromWrite(){ EEPROM.write(startAddress + i, auxMsg[2+i]); } - #ifdef ESP8266 - EEPROM.end(); - EEPROM.commit(); - #endif + #if defined(ESP8266) || defined(ESP32) + EEPROM.end(); + EEPROM.commit(); + #endif } } #endif \ No newline at end of file From 4bfa84f07b47f848060b4db5370eb1ccfc3d0c74 Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 21 Feb 2023 11:34:25 -0400 Subject: [PATCH 263/296] Make the SPI header API method clearer --- lib/dino/api/spi.rb | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/lib/dino/api/spi.rb b/lib/dino/api/spi.rb index 40449a04..8885fd67 100644 --- a/lib/dino/api/spi.rb +++ b/lib/dino/api/spi.rb @@ -7,7 +7,7 @@ def spi_header(options) options[:read] ||= 0 options[:write] = [options[:write]].flatten.compact || [] options[:mode] ||= 0 - options[:frequency] ||= 3000000 + options[:frequency] ||= 1000000 options[:bit_order] ||= :msbfrst # Bit 0..3 of settings control the SPI mode @@ -17,18 +17,14 @@ def spi_header(options) # 1000 = SPI_MODE2 # 1100 = SPI_MODE3 # - settings = case options[:mode] - when 0 - 0b0000 - when 1 - 0b0100 - when 2 - 0b1000 - when 3 - 0b1100 - else - raise ArgumentError, "invalid SPI mode. Must be 0, 1, 2, or 3" - end + settings = case options[:mode] + when 0; 0b0000 + when 1; 0b0100 + when 2; 0b1000 + when 3; 0b1100 + else + raise ArgumentError, "invalid SPI mode. Must be 0, 1, 2, or 3" + end # Bit 7 of settings toggles MSBFIRST (1) or LSBFIRST (0) transmission order. settings = settings | 0b10000000 unless options[:bit_order] == :lsbfirst @@ -36,8 +32,9 @@ def spi_header(options) raise ArgumentError, "can't read more than 255 SPI bytes at a time" if options[:read] > 255 raise ArgumentError, "can't write more than 255 SPI bytes at a time" if options[:write].length > 255 - uint8 = pack(:uint8, [settings, options[:read], options[:write].length]) - [uint8 + pack(:uint32, options[:frequency]), options] + header = pack :uint8, [settings, options[:read], options[:write].length] + header = header + pack(:uint32, options[:frequency]) + [header, options] end # CMD = 26 From 694d061b991626cba24252a5f601e3da227f0dbf Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 21 Feb 2023 12:40:56 -0400 Subject: [PATCH 264/296] Cast SPI clock speed to uint32_t properly --- src/lib/Dino.cpp | 2 +- src/lib/DinoSPI.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index f48063ee..9a36c8f4 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -147,7 +147,7 @@ void Dino::process() { // Implemented in DinoSPI.cpp #ifdef DINO_SPI - case 26: spiTransfer (pin, auxMsg[0], auxMsg[1], auxMsg[2], (uint32_t)auxMsg[3], &auxMsg[7]); break; + case 26: spiTransfer (pin, auxMsg[0], auxMsg[1], auxMsg[2], *((uint32_t*)(auxMsg + 3)), &auxMsg[7]); break; case 27: addSpiListener (); break; case 28: removeSpiListener(); break; #endif diff --git a/src/lib/DinoSPI.cpp b/src/lib/DinoSPI.cpp index 280c0b90..45db44ab 100644 --- a/src/lib/DinoSPI.cpp +++ b/src/lib/DinoSPI.cpp @@ -102,7 +102,7 @@ void Dino::addSpiListener() { pin, auxMsg[0], auxMsg[1], - (uint32_t)auxMsg[3], + *((uint32_t*)(auxMsg + 3)), true }; return; From 9bc06284c422a07bdda30784dd3bebc6774f0e62 Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 21 Feb 2023 15:30:45 -0400 Subject: [PATCH 265/296] I2C support for ESP32 and fixed stop bit being flipped --- HARDWARE.md | 2 +- lib/dino/api/i2c.rb | 10 ++++---- src/lib/Dino.cpp | 2 +- src/lib/DinoI2C.cpp | 57 +++++++++++++++++++++++++++++++-------------- 4 files changed, 46 insertions(+), 25 deletions(-) diff --git a/HARDWARE.md b/HARDWARE.md index 53380d27..d547d449 100644 --- a/HARDWARE.md +++ b/HARDWARE.md @@ -56,7 +56,7 @@ | Name | Status | SW/HW | Version | Component Class | Notes | | :--------------- | :------: | :-------- | :----- | :--------------- |------ | -| I2C | :green_heart: | Hardware | 0.12.0 | `I2C::Bus` +| I2C | :green_heart: | Hardware | 0.12.0 | `I2C::Bus` | Only default clocks | SPI | :green_heart: | Hardware | 0.12.0 | `Register::Select` | Hardware register I/O | Shift In/Out | :green_heart: | Software | 0.12.0 | `Register::Select` | Bit bang register I/O | Software Serial | :yellow_heart: | Software | 0.12.0 | `SoftwareSerial` | No read, only write diff --git a/lib/dino/api/i2c.rb b/lib/dino/api/i2c.rb index fe7a4b41..5c3d2ddd 100644 --- a/lib/dino/api/i2c.rb +++ b/lib/dino/api/i2c.rb @@ -10,9 +10,9 @@ def i2c_search # CMD = 34 def i2c_write(address, bytes=[], options={}) - # Bit 1 of settings controls repeated start. + # Bit 0 of settings controls stop (1), or repated start (0) settings = 0b00 - settings |= 0b01 if options[:repeated_start] + settings |= 0b01 unless options[:repeated_start] aux = pack :uint8, [address, 0, bytes.length, bytes].flatten write Message.encode command: 34, @@ -24,11 +24,11 @@ def i2c_write(address, bytes=[], options={}) def i2c_read(address, register, num_bytes, options={}) raise ArgumentError, 'maximum read length from I2C devices is 32 bytes' if num_bytes > 32 - # Bit 1 of settings controls repeated start. + # Bit 0 of settings controls stop (1), or repated start (0) settings = 0b00 - settings |= 0b01 if options[:repeated_start] + settings |= 0b01 unless options[:repeated_start] - # Bit 2 of settings controls whether to write a start register before reading. + # Bit 1 of settings controls whether to write a start register before reading. settings |= 0b10 if register register = 0 unless register diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 434517f6..28814c52 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -229,7 +229,7 @@ void Dino::handshake() { #elif defined(EEPROM_PRESENT) stream->print(EEPROM.length()); #else - stream->print('0'); + stream->print('0'); #endif // Third is A0. Ignore for ESP32. Pins aren't in order. diff --git a/src/lib/DinoI2C.cpp b/src/lib/DinoI2C.cpp index 32778b3c..f0250c3f 100644 --- a/src/lib/DinoI2C.cpp +++ b/src/lib/DinoI2C.cpp @@ -20,18 +20,19 @@ void Dino::i2cBegin() { // CMD = 33 // Ask each address for a single byte to see if it exists on the bus. void Dino::i2cSearch() { + byte error; + uint8_t addr; + i2cBegin(); stream->print(SDA); - i2cBegin(); - for (uint8_t i = 0x08; i < 0x78; i++) { - uint8_t length = 1; - Wire.requestFrom(i, length); - if (Wire.available()){ - stream->print(':'); stream->print(i); - while(Wire.available()) Wire.read(); + // Only addresses from 0x08 to 0x77 are usable (8 to 127). + for (addr = 0x08; addr < 0x78; addr++) { + Wire.beginTransmission(addr); + error = Wire.endTransmission(); + if (error == 0){ + stream->print(':'); stream->print(addr); } } - stream->print('\n'); } @@ -43,7 +44,7 @@ void Dino::i2cSearch() { // val bit 1 = write register address before reading // val bit 2+ = unused // -// auxMsg[0] = 7-bit device address +// auxMsg[0] = 7-bit device addresses // auxMsg[1] = reserved // auxMsg[2] = data length // auxMsg[3]+ = data @@ -51,11 +52,17 @@ void Dino::i2cSearch() { // Max limited by aux message size. // void Dino::i2cWrite() { - byte repeatedStart = bitRead(val, 0); + // No repeated start on ESP32. + #if defined(ESP32) + bool sendStop = true; + #else + bool sendStop = bitRead(val, 0); + #endif + i2cBegin(); Wire.beginTransmission(auxMsg[0]); Wire.write(&auxMsg[3], auxMsg[2]); - Wire.endTransmission(repeatedStart); + Wire.endTransmission(sendStop); } // CMD = 35 @@ -74,19 +81,33 @@ void Dino::i2cWrite() { // Max 32 bytes, limited by Wire library buffer. Validate remotely. // void Dino::i2cRead() { + // Limit to 32 bytes. if (auxMsg[3] > 32) auxMsg[3] = 32; - byte repeatedStart = bitRead(val, 0); - i2cBegin(); + // No repeated start on ESP32. + #if defined(ESP32) + bool sendStop = true; + #else + bool sendStop = bitRead(val, 0); + #endif + + i2cBegin(); + // Optionally write a register address before reading. if (bitRead(val, 1)) { Wire.beginTransmission(auxMsg[0]); Wire.write(auxMsg[2]); - Wire.endTransmission(repeatedStart); + Wire.endTransmission(sendStop); } - Wire.beginTransmission(auxMsg[0]); - Wire.requestFrom(auxMsg[0], auxMsg[3], repeatedStart); - + + // ESP32 crashes if requestFrom gets the 3rd arg. + #if defined(ESP32) + Wire.requestFrom(auxMsg[0], auxMsg[3]); + #else + // Wire.beginTransmission(auxMsg[0]); + Wire.requestFrom(auxMsg[0], auxMsg[3], sendStop); + #endif + // Send data as if coming from SDA pin. Prefix with device adddress. // Fail silently if no bytes read / invalid device address. if(Wire.available()){ @@ -99,6 +120,6 @@ void Dino::i2cRead() { stream->print(Wire.read()); stream->print((currentByte == auxMsg[3]) ? '\n' : ','); } - Wire.endTransmission(repeatedStart); + Wire.endTransmission(sendStop); } #endif From 03bc57f12d116ecf732e727d7d12f056ea8285b3 Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 21 Feb 2023 15:32:56 -0400 Subject: [PATCH 266/296] Fix I2C stop bit being flipped --- HARDWARE.md | 2 +- lib/dino/api/i2c.rb | 10 ++++---- src/lib/Dino.cpp | 2 +- src/lib/DinoI2C.cpp | 57 +++++++++++++++++++++++++++++++-------------- 4 files changed, 46 insertions(+), 25 deletions(-) diff --git a/HARDWARE.md b/HARDWARE.md index 122c61b6..d03b390f 100644 --- a/HARDWARE.md +++ b/HARDWARE.md @@ -56,7 +56,7 @@ | Name | Status | SW/HW | Version | Component Class | Notes | | :--------------- | :------: | :-------- | :----- | :--------------- |------ | -| I2C | :green_heart: | Hardware | 0.12.0 | `I2C::Bus` +| I2C | :green_heart: | Hardware | 0.12.0 | `I2C::Bus` | Only default clocks | SPI | :green_heart: | Hardware | 0.12.0 | `Register::Select` | Hardware register I/O | Shift In/Out | :green_heart: | Software | 0.12.0 | `Register::Select` | Bit bang register I/O | Software Serial | :yellow_heart: | Software | 0.12.0 | `SoftwareSerial` | No read, only write diff --git a/lib/dino/api/i2c.rb b/lib/dino/api/i2c.rb index fe7a4b41..5c3d2ddd 100644 --- a/lib/dino/api/i2c.rb +++ b/lib/dino/api/i2c.rb @@ -10,9 +10,9 @@ def i2c_search # CMD = 34 def i2c_write(address, bytes=[], options={}) - # Bit 1 of settings controls repeated start. + # Bit 0 of settings controls stop (1), or repated start (0) settings = 0b00 - settings |= 0b01 if options[:repeated_start] + settings |= 0b01 unless options[:repeated_start] aux = pack :uint8, [address, 0, bytes.length, bytes].flatten write Message.encode command: 34, @@ -24,11 +24,11 @@ def i2c_write(address, bytes=[], options={}) def i2c_read(address, register, num_bytes, options={}) raise ArgumentError, 'maximum read length from I2C devices is 32 bytes' if num_bytes > 32 - # Bit 1 of settings controls repeated start. + # Bit 0 of settings controls stop (1), or repated start (0) settings = 0b00 - settings |= 0b01 if options[:repeated_start] + settings |= 0b01 unless options[:repeated_start] - # Bit 2 of settings controls whether to write a start register before reading. + # Bit 1 of settings controls whether to write a start register before reading. settings |= 0b10 if register register = 0 unless register diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 9a36c8f4..4e4fbbc4 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -229,7 +229,7 @@ void Dino::handshake() { #elif defined(EEPROM_PRESENT) stream->print(EEPROM.length()); #else - stream->print('0'); + stream->print('0'); #endif stream->print(','); diff --git a/src/lib/DinoI2C.cpp b/src/lib/DinoI2C.cpp index 32778b3c..f0250c3f 100644 --- a/src/lib/DinoI2C.cpp +++ b/src/lib/DinoI2C.cpp @@ -20,18 +20,19 @@ void Dino::i2cBegin() { // CMD = 33 // Ask each address for a single byte to see if it exists on the bus. void Dino::i2cSearch() { + byte error; + uint8_t addr; + i2cBegin(); stream->print(SDA); - i2cBegin(); - for (uint8_t i = 0x08; i < 0x78; i++) { - uint8_t length = 1; - Wire.requestFrom(i, length); - if (Wire.available()){ - stream->print(':'); stream->print(i); - while(Wire.available()) Wire.read(); + // Only addresses from 0x08 to 0x77 are usable (8 to 127). + for (addr = 0x08; addr < 0x78; addr++) { + Wire.beginTransmission(addr); + error = Wire.endTransmission(); + if (error == 0){ + stream->print(':'); stream->print(addr); } } - stream->print('\n'); } @@ -43,7 +44,7 @@ void Dino::i2cSearch() { // val bit 1 = write register address before reading // val bit 2+ = unused // -// auxMsg[0] = 7-bit device address +// auxMsg[0] = 7-bit device addresses // auxMsg[1] = reserved // auxMsg[2] = data length // auxMsg[3]+ = data @@ -51,11 +52,17 @@ void Dino::i2cSearch() { // Max limited by aux message size. // void Dino::i2cWrite() { - byte repeatedStart = bitRead(val, 0); + // No repeated start on ESP32. + #if defined(ESP32) + bool sendStop = true; + #else + bool sendStop = bitRead(val, 0); + #endif + i2cBegin(); Wire.beginTransmission(auxMsg[0]); Wire.write(&auxMsg[3], auxMsg[2]); - Wire.endTransmission(repeatedStart); + Wire.endTransmission(sendStop); } // CMD = 35 @@ -74,19 +81,33 @@ void Dino::i2cWrite() { // Max 32 bytes, limited by Wire library buffer. Validate remotely. // void Dino::i2cRead() { + // Limit to 32 bytes. if (auxMsg[3] > 32) auxMsg[3] = 32; - byte repeatedStart = bitRead(val, 0); - i2cBegin(); + // No repeated start on ESP32. + #if defined(ESP32) + bool sendStop = true; + #else + bool sendStop = bitRead(val, 0); + #endif + + i2cBegin(); + // Optionally write a register address before reading. if (bitRead(val, 1)) { Wire.beginTransmission(auxMsg[0]); Wire.write(auxMsg[2]); - Wire.endTransmission(repeatedStart); + Wire.endTransmission(sendStop); } - Wire.beginTransmission(auxMsg[0]); - Wire.requestFrom(auxMsg[0], auxMsg[3], repeatedStart); - + + // ESP32 crashes if requestFrom gets the 3rd arg. + #if defined(ESP32) + Wire.requestFrom(auxMsg[0], auxMsg[3]); + #else + // Wire.beginTransmission(auxMsg[0]); + Wire.requestFrom(auxMsg[0], auxMsg[3], sendStop); + #endif + // Send data as if coming from SDA pin. Prefix with device adddress. // Fail silently if no bytes read / invalid device address. if(Wire.available()){ @@ -99,6 +120,6 @@ void Dino::i2cRead() { stream->print(Wire.read()); stream->print((currentByte == auxMsg[3]) ? '\n' : ','); } - Wire.endTransmission(repeatedStart); + Wire.endTransmission(sendStop); } #endif From 2f731d86c0009ef596f346d0259fc4d53ffc7293 Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 21 Feb 2023 17:01:23 -0400 Subject: [PATCH 267/296] Add I2C package to ESP32 target --- lib/dino_cli/targets.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dino_cli/targets.rb b/lib/dino_cli/targets.rb index fc58deba..54a0378f 100644 --- a/lib/dino_cli/targets.rb +++ b/lib/dino_cli/targets.rb @@ -20,6 +20,6 @@ class DinoCLI::Generator esp8266: STANDARD_PACKAGES - [:lcd, :serial, :ir_out] + [:ir_out_esp8266], # Just core implementation on the ESP32 for now. - esp32: [:core, :shift, :spi] + esp32: [:core, :shift, :spi, :i2c] } end From a0f437b3843d2204734d64dd77fa5a5524ae0cdc Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 21 Feb 2023 23:06:39 -0400 Subject: [PATCH 268/296] Manage LEDC channels better on the ESP32 --- src/lib/Dino.h | 2 + src/lib/DinoCoreIO.cpp | 84 +++++++++++++++++++++++++++++------------- 2 files changed, 61 insertions(+), 25 deletions(-) diff --git a/src/lib/Dino.h b/src/lib/Dino.h index de1ee387..964233b3 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -84,6 +84,8 @@ class Dino { byte ledcPins[LEDC_CHANNEL_COUNT][2]; byte ledcChannel(byte p); + byte attachLEDC(byte channel, byte pin); + void detachLEDC(byte p); void clearLedcChannels(); #endif diff --git a/src/lib/DinoCoreIO.cpp b/src/lib/DinoCoreIO.cpp index e408bc21..51ca3522 100644 --- a/src/lib/DinoCoreIO.cpp +++ b/src/lib/DinoCoreIO.cpp @@ -15,9 +15,10 @@ void Dino::setMode(byte p, byte m) { } else { #ifdef ESP32 - pinMode(p, INPUT_PULLUP); + detachLEDC(p); + pinMode(p, INPUT_PULLUP); #else - pinMode(p, INPUT); + pinMode(p, INPUT); #endif } } @@ -34,6 +35,11 @@ void Dino::dWrite(byte p, byte v, boolean echo) { Serial.println(echo); #endif + // Stop using a LEDC channel if the pin was using one. + #ifdef ESP32 + detachLEDC(p); + #endif + if (v == 0) { digitalWrite(p, LOW); } @@ -50,6 +56,11 @@ byte Dino::dRead(byte p) { Serial.print("dread, pin:"); Serial.println(p); #endif + + // Stop using a LEDC channel if the pin was using one. + #ifdef ESP32 + detachLEDC(p); + #endif byte rval = digitalRead(p); coreResponse(p, rval); @@ -69,8 +80,9 @@ void Dino::aWrite(byte p, int v, boolean echo) { #endif #ifdef ESP32 - byte channel = ledcChannel(p); - ledcWrite(channel, v); + // analogWrite is implemented but we can use manual control to do more. + // analogWrite(p,v); + ledcWrite(ledcChannel(p), v); #else analogWrite(p,v); #endif @@ -83,37 +95,59 @@ void Dino::aWrite(byte p, int v, boolean echo) { // #ifdef ESP32 byte Dino::ledcChannel(byte p) { - // Return a useless channel if none available. - byte channel = 255; - - // Search for existing LEDC channel with our pin first. + // Search for enabled LEDC channel with given pin and use that if found. for (byte i = 0; i < LEDC_CHANNEL_COUNT; i++){ - if (ledcPins[i][1] == p) { - channel = i; - break; + if ((ledcPins[i][0] == 1) && (ledcPins[i][1] == p)){ + return i; } } - // If channel is still 255 we didn't find one, so make one. - if (channel == 255){ - for (byte i = 0; i < LEDC_CHANNEL_COUNT; i++){ - // 0th byte being 0 means not in use. - if (ledcPins[i][0] == 0) { - // Just use similar settings to ATmega for now. - ledcSetup(i, 1000, 8); - ledcAttachPin(p, i); - ledcPins[i][1] = p; - channel = i; - break; - } + // We didn't find a channel to reuse. + for (byte i = 0; i < LEDC_CHANNEL_COUNT; i++){ + // If the channel isn't initialized and it isn't marked as used, use it. + // should find some way to check if the channel itslef is being used + if ((ledcPins[i][0] == 0)) { + attachLEDC(i, p); + return i; } } - return channel; + + // Return a useless channel if none available. + return 255; }; -// 0th byte = 0 for not in use. +// Attach a pin to a channel and save it. +byte Dino::attachLEDC(byte channel, byte p){ + // First 8 channels: up to 40Mhz @ 16-bits + // Last 8 channels: up to 500kHz @ 13-bits + // Just use similar settings to ATmega for now. + ledcSetup(channel, 1000, 8); + ledcAttachPin(p, channel); + + // Save the pin and mark it as in use. + ledcPins[channel][0] = 1; + ledcPins[channel][1] = p; + return channel; +} + +// Stop using a LEDC channel when we're done. +void Dino::detachLEDC(byte p){ + // Detach the pin from the channel. + ledcDetachPin(p); + + // Mark any channel associated with our pin as unused. + for (byte i = 0; i < LEDC_CHANNEL_COUNT; i++){ + if (ledcPins[i][1] == p) ledcPins[i][0] = 0; + } +} + +// Clear all the LEDC channels on reset. void Dino::clearLedcChannels(){ for (byte i = 0; i < LEDC_CHANNEL_COUNT; i++){ + // Stop the channel if it was still enabled. + if (ledcPins[i][0] != 0) ledcDetachPin(ledcPins[i][1]); + + // Mark the channel as unused. ledcPins[i][0] = 0; } } From feebfa855dbb5781965b143effd568f32c8cc2ae Mon Sep 17 00:00:00 2001 From: vickash Date: Tue, 21 Feb 2023 23:40:38 -0400 Subject: [PATCH 269/296] Servo works on ESP32 by swapping library --- README.md | 47 +++++++++++++++++++++++++++++++---------- lib/dino_cli/targets.rb | 4 ++-- src/lib/DinoServo.cpp | 6 +++++- 3 files changed, 43 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 1a0f5a6c..ed463923 100644 --- a/README.md +++ b/README.md @@ -39,29 +39,54 @@ brew install arduino-cli ```` #### 3) Install Arduino Dependencies -Dino uses Arduino "cores" which add microcontroller support, and a few C++ libraries for now. Let's install them. +Dino uses Arduino "cores" which add microcontroller support, and a few libraries. Let's install them now. -**IDE:** -* Open the IDE and select Tools > Board > Board Manager from the menus. -* Search for `ESP8266 Boards` and install the latest version. -* Now go to: Tools > Manage Libraries. +**IDE method for Arduino & Clones:** +* Go to: Tools > Manage Libraries. * Find and install the following libraries, at the version numbers given: * `Servo by Michael Margolis, Arduino` at latest version * `Liquid Crystal by Arduino, Adafruit` at latest version * `WiFi by Arduino` at latest version * `IRremote by shirriff, z3to, ArminJo` at `version 2.2.3` - * `IRremoteESP82666 by David Conran, Sebastien Warin` at `version 2.3.2` -**CLI:** +**CLI method for Arduino & Clones:** ````shell -arduino-cli core install esp8266:esp8266 arduino-cli lib install Servo arduino-cli lib install LiquidCrystal arduino-cli lib install WiFi arduino-cli lib install IRremote@2.2.3 +```` + +**IDE method for ESP8266:** +* Go to Tools > Board > Board Manager. +* Search for `ESP8266 Boards` and install the latest version. +* Go to: Tools > Manage Libraries. +* Find and install the following libraries, at the version numbers given: + * `IRremoteESP82666 by David Conran, Sebastien Warin` at `version 2.3.2` + +**CLI method for ESP8266:** +````shell +arduino-cli config add board_manager.additional_urls https://arduino.esp8266.com/stable/package_esp8266com_index.json +arduino-cli core update-index +arduino-cli core install esp8266:esp8266 arduino-cli lib install IRremoteESP8266@2.3.2 ```` +**IDE method for ESP32:** +* Go to Tools > Board > Board Manager. +* Search for `esp32` and install the latest version. +* Go to: Tools > Manage Libraries. +* Find and install the following libraries, at the version numbers given: + * `ESP32Servo by Kevin Harrington, John K. Bennett` at latest version + +**CLI method for ESP32:** +````shell +arduino-cli config add board_manager.additional_urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json +arduino-cli core update-index +arduino-cli core install esp32 +arduino-cli lib install ESP32Servo +```` + #### 4) Generate the Arduino Sketch The `dino` command is included with the gem. It will make the Arduino sketch folder for you, and configure it. @@ -75,11 +100,11 @@ dino sketch serial dino sketch serial -target esp8266 ```` -**For ESP8266 over WiFi (2.4Ghz and DHCP Only):** +**For ESP32 over WiFi (2.4Ghz and DHCP Only):** ```shell -dino sketch wifi -target esp8266 -ssid YOUR_SSID -password YOUR_PASSWORD +dino sketch wifi -target esp32 -ssid YOUR_SSID -password YOUR_PASSWORD ```` -**Note:** [This example](examples/tcp.rb) shows how to instantiate a board over the network. You'll need to figure out what IP address your board got, and modify the TxRx line in other examples you wish to run. They are written for serial. +**Note:** [This example](examples/tcp.rb) shows how to use a board over a TCP connection, but the WiFi sketches fall back to the serial interface if no TCP client is connected. You should be able to run the examples over serial while still connected. #### 5a) IDE Flashing diff --git a/lib/dino_cli/targets.rb b/lib/dino_cli/targets.rb index 54a0378f..192ececf 100644 --- a/lib/dino_cli/targets.rb +++ b/lib/dino_cli/targets.rb @@ -11,7 +11,7 @@ class DinoCLI::Generator core: [:core], # Specific features for the old mega168 chips. - mega168: [:core, :servo, :shift,:tone, :spi, :i2c], + mega168: [:core, :servo, :shift, :tone, :spi, :i2c], # ARM includes everytyhing except specific incompatibilities. sam3x: STANDARD_PACKAGES - [:serial, :tone, :ir_out], @@ -20,6 +20,6 @@ class DinoCLI::Generator esp8266: STANDARD_PACKAGES - [:lcd, :serial, :ir_out] + [:ir_out_esp8266], # Just core implementation on the ESP32 for now. - esp32: [:core, :shift, :spi, :i2c] + esp32: [:core, :servo, :shift, :spi, :i2c] } end diff --git a/src/lib/DinoServo.cpp b/src/lib/DinoServo.cpp index 631d0225..6f8913a6 100644 --- a/src/lib/DinoServo.cpp +++ b/src/lib/DinoServo.cpp @@ -4,7 +4,11 @@ #include "Dino.h" #ifdef DINO_SERVO -#include +#ifdef ESP32 + #include +#else if + #include +#endif // 12 servos on most boards. 6 on the ATmega168. // Could be up to 48 on Arduino Mega. From 0ffe095474da99165683689b0122557e0adc4300 Mon Sep 17 00:00:00 2001 From: vickash Date: Wed, 22 Feb 2023 10:55:22 -0400 Subject: [PATCH 270/296] OneWire (bit bang, not RMT) support on ESP32 --- lib/dino_cli/targets.rb | 4 ++-- src/lib/Dino.cpp | 10 ++++++++++ src/lib/Dino.h | 3 +++ src/lib/DinoOneWire.cpp | 24 ++++++++++++++---------- 4 files changed, 29 insertions(+), 12 deletions(-) diff --git a/lib/dino_cli/targets.rb b/lib/dino_cli/targets.rb index 192ececf..9a7bd70c 100644 --- a/lib/dino_cli/targets.rb +++ b/lib/dino_cli/targets.rb @@ -19,7 +19,7 @@ class DinoCLI::Generator # ESP8266 mostly working. esp8266: STANDARD_PACKAGES - [:lcd, :serial, :ir_out] + [:ir_out_esp8266], - # Just core implementation on the ESP32 for now. - esp32: [:core, :servo, :shift, :spi, :i2c] + # ESP is missing LCD, IR, and Software Serial + esp32: [:core, :tone, :one_wire, :servo, :shift, :spi, :i2c] } end diff --git a/src/lib/Dino.cpp b/src/lib/Dino.cpp index 28814c52..6612cd02 100644 --- a/src/lib/Dino.cpp +++ b/src/lib/Dino.cpp @@ -289,3 +289,13 @@ void Dino::setAnalogResolution() { Serial.print("Called Dino::setAnalogResolution()\n"); #endif } + +// Use a different blocking microsecond delay on different platforms. +void Dino::microDelay(uint32_t microseconds){ + #if defined(ESP32) + ets_delay_us(microseconds); + #else + delayMicroseconds(microseconds); + #endif +} + diff --git a/src/lib/Dino.h b/src/lib/Dino.h index 964233b3..aeb688d8 100644 --- a/src/lib/Dino.h +++ b/src/lib/Dino.h @@ -168,6 +168,9 @@ class Dino { unsigned long lastTime; unsigned long timeDiff; byte registerDivider; + + // Wrapper for different delay implementations by platform. + void microDelay(uint32_t microseconds); // // Main loop input functions. diff --git a/src/lib/DinoOneWire.cpp b/src/lib/DinoOneWire.cpp index d762b0c6..9ced43fa 100644 --- a/src/lib/DinoOneWire.cpp +++ b/src/lib/DinoOneWire.cpp @@ -14,11 +14,12 @@ void Dino::owReset(){ byte present; pinMode(pin, OUTPUT); digitalWrite(pin, LOW); - delayMicroseconds(500); + microDelay(500); pinMode(pin, INPUT); - delayMicroseconds(80); + digitalWrite(pin, HIGH); // Essential on ESP32. Enables pullup on others. + microDelay(80); present = digitalRead(pin); - delayMicroseconds(420); + microDelay(420); if(val>0) coreResponse(pin, present); } @@ -96,19 +97,21 @@ void Dino::owWriteBit(byte b){ // Write slot always starts with pulling the bus low for at least 1us. pinMode(pin, OUTPUT); digitalWrite(pin, LOW); - delayMicroseconds(1); + microDelay(1); // If 1, release so the bus idles high, and wait out the 60us write slot. if(b == 1){ pinMode(pin, INPUT); - delayMicroseconds(59); + digitalWrite(pin, HIGH); // Essential on ESP32. Enables pullup on others. + microDelay(59); // If 0, keep it low for the rest of the 60us write slot, then release. } else { - delayMicroseconds(59); + microDelay(59); pinMode(pin, INPUT); + digitalWrite(pin, HIGH); // Essential on ESP32. Enables pullup on others. } // Minimum 1us recovery time after each slot. - delayMicroseconds(1); + microDelay(1); } // CMD = 44 @@ -137,17 +140,18 @@ byte Dino::owReadBit(){ // Pull low for at least 1us to start a read slot, then release. pinMode(pin, OUTPUT); digitalWrite(pin, LOW); - delayMicroseconds(1); + microDelay(1); pinMode(pin, INPUT); + digitalWrite(pin, HIGH); // Essential on ESP32. Enables pullup on others. // Wait for the slave to write to the bus. It should hold for up to 15us. - delayMicroseconds(5); + microDelay(5); // If slave pulled the bus high, the bit is a 1, else 0. b = digitalRead(pin); // Wait out the 60us read slot + recovery time. - delayMicroseconds(55); + microDelay(55); return b; } #endif From 867567f4ca156499f0e0ac0ae5fbdc36345f6553 Mon Sep 17 00:00:00 2001 From: vickash Date: Wed, 22 Feb 2023 11:30:55 -0400 Subject: [PATCH 271/296] Fix some hardware list errors. --- HARDWARE.md | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/HARDWARE.md b/HARDWARE.md index d547d449..f67f0a98 100644 --- a/HARDWARE.md +++ b/HARDWARE.md @@ -31,10 +31,12 @@ | ESP8266 | :yellow_heart: | 0.12.0 | NodeMCU | SoftwareSerial and LCD don't work yet | ESP32 | :test_tube: | - | DOIT ESP32 DevKit V1 | Original ESP-WROOM-32 | ESP32-S2 | :test_tube: | - | LOLIN S2 Pico | Single core Xtensa, native USB -| ESP32-S3 | :test_tube: | - | LOLIN S3 V1.0.0 | Dual core RISC-V, native USB +| ESP32-S3 | :test_tube: | - | LOLIN S3 V1.0.0 | Dual core Xtensa, native USB **Note:** There are too many boards using these chips to be comprehensive. Most should work. These are the exact ones used for testing, chosen based on popularity. +**Note:** For these boards, "pin" numbers are always based on GPIO numbers defined by the CHIP, not the pin numbers labeled on the BOARD. They might coincide, but not always. Keep a mapping reference handy for your particular board. + # Supported Components :green_heart: Full support :yellow_heart: Partial support :heart: Planned. No support yet @@ -59,10 +61,10 @@ | I2C | :green_heart: | Hardware | 0.12.0 | `I2C::Bus` | Only default clocks | SPI | :green_heart: | Hardware | 0.12.0 | `Register::Select` | Hardware register I/O | Shift In/Out | :green_heart: | Software | 0.12.0 | `Register::Select` | Bit bang register I/O -| Software Serial | :yellow_heart: | Software | 0.12.0 | `SoftwareSerial` | No read, only write +| Software Serial | :yellow_heart: | Software | 0.12.0 | `SoftwareSerial` | Library on Board. Write only | Hardware Serial | :heart: | Hardware | - | - | For boards with native USB and UARTs -| Maxim OneWire | :green_heart: | Software | 0.12.0 | `OneWire::Bus` | No overdrivee support -| Infrared Emitter | :green_heart: | Software | 0.12.0 | `IREmitter` | +| Maxim OneWire | :green_heart: | Software | 0.12.0 | `OneWire::Bus` | No overdrive support +| Infrared Emitter | :green_heart: | Software | 0.12.0 | `IREmitter` | Library on Board ### Generic Components @@ -79,28 +81,28 @@ | Analog Sensor | :green_heart: | Analog In | 0.11.0 | `Sensor` | | Potentiometer | :green_heart: | Analog In | 0.12.0 | `Potentiometer` | Smoothing on by default | Piezo Buzzer | :green_heart: | Tone Out | 0.12.0 | `Piezo` | Frequency > 30Hz -| Input Register | :green_heart: | ShiftIn | 0.12.0 | `Register::ShiftIn` | Tested on CD4021B +| Input Register | :green_heart: | Shift In | 0.12.0 | `Register::ShiftIn` | Tested on CD4021B | Input Register | :green_heart: | SPI | 0.12.0 | `Register::SPIIn` | Tested on CD4021B -| Output Register | :green_heart: | ShiftOut | 0.12.0 | `Register::ShiftOut` | Tested on 74HC595 +| Output Register | :green_heart: | Shift Out | 0.12.0 | `Register::ShiftOut` | Tested on 74HC595 | Output Register | :green_heart: | SPI | 0.12.0 | `Register::SPIOut` | Tested on 74HC595 **Note:** Most Digital In and Out components can be used seamlessley through Input and Output Registers respectively. ### Motors / Motor Drivers -| Name | Status | Interface | Version | Component Class | Notes | -| :--------------- | :------: | :-------- | :----- | :--------------- |------ | -| Servo | :green_heart: | Direct | 0.11.2 | `Servo` | Max 6 servos on the ATmega168, 12 otherwise -| L298N | :heart: | Direct | | - | 2ch DC motor driver -| A3967 | :green_heart: | Direct | 0.12.0 | `Stepper`| 1ch microstepper. Tested with EasyDriver board -| PCA9685 | :heart: | I2C | - | - | 16ch 12-bit PWM for servo, DC motor, or LED +| Name | Status | Interface | Version | Component Class | Notes | +| :--------------- | :------: | :-------- | :----- | :--------------- |------ | +| Servo | :green_heart: | PWM + Library | 0.11.2 | `Servo` | Max 6 servos on ATmega168, 12 otherwise +| L298N | :heart: | PWM Out | - | | 2ch DC motor driver +| A3967 | :green_heart: | Digital Out | 0.12.0 | `Stepper`| 1ch microstepper (EasyDriver) +| PCA9685 | :heart: | I2C | - | - | 16ch 12-bit PWM for servo, DC motor, or LED ### Displays -| Name | Status | Interface | Version | Component Class | Notes | -| :--------------- | :------: | :-------- | :----- | :--------------- |------ | -| HD44780 LCD | :green_heart: | Direct, C++ Lib | 0.12.0 | `LCD` | Could make it work through registers -| SSD1306 OLED | :heart: | I2C | - | - | +| Name | Status | Interface | Version | Component Class | Notes | +| :--------------- | :------: | :-------- | :----- | :--------------- |------ | +| HD44780 LCD | :green_heart: | Digital Out + Library | 0.12.0 | `LCD` | No register passthrough +| SSD1306 OLED | :heart: | I2C | - | - | ### Addressable LEDs @@ -122,7 +124,7 @@ | Name | Status | Interface | Version | Component Class | Notes | | :--------------- | :------: | :-------- | :----- | :--------------- |------ | -| DHT 11/21/22 | :green_heart: | Direct | 0.12.0 | `DHT` | Temperature, Humidity +| DHT 11/21/22 | :green_heart: | Digi In/Out| 0.12.0 | `DHT` | Temperature, Humidity | DS18B20 | :green_heart: | OneWire | 0.12.0 | `OneWire::DS18B20`| Temperature | MAX31850 | :grey_exclamation: | OneWire | - | - | Thermocouple | BME280 | :heart: | I2C | - | - | Pressure, Temperature, Humidity From 665a48a7e1a2539c384bc7eaf0df91993cbdf359 Mon Sep 17 00:00:00 2001 From: vickash Date: Wed, 22 Feb 2023 16:30:29 -0400 Subject: [PATCH 272/296] IR out for ESP32. Newest IR library for all platforms --- .gitmodules | 6 ---- README.md | 10 +++--- examples/ir_emitter/ir_emitter.rb | 56 +++++++++++++++---------------- lib/dino/api/infrared.rb | 9 +++-- lib/dino_cli/packages.rb | 19 +++-------- lib/dino_cli/targets.rb | 6 ++-- src/lib/DinoIROut.cpp | 40 ++++++++++------------ src/lib/DinoIROutESP.cpp | 26 ++++++++++++++ src/vendor/Arduino-IRremote | 1 - src/vendor/IRremoteESP8266 | 1 - 10 files changed, 92 insertions(+), 82 deletions(-) create mode 100644 src/lib/DinoIROutESP.cpp delete mode 160000 src/vendor/Arduino-IRremote delete mode 160000 src/vendor/IRremoteESP8266 diff --git a/.gitmodules b/.gitmodules index d1468cc8..e69de29b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +0,0 @@ -[submodule "src/vendor/Arduino-IRremote"] - path = src/vendor/Arduino-IRremote - url = https://github.com/z3t0/Arduino-IRremote.git -[submodule "src/vendor/IRremoteESP8266"] - path = src/vendor/IRremoteESP8266 - url = https://github.com/markszabo/IRremoteESP8266.git diff --git a/README.md b/README.md index ed463923..064c991b 100644 --- a/README.md +++ b/README.md @@ -47,14 +47,14 @@ Dino uses Arduino "cores" which add microcontroller support, and a few libraries * `Servo by Michael Margolis, Arduino` at latest version * `Liquid Crystal by Arduino, Adafruit` at latest version * `WiFi by Arduino` at latest version - * `IRremote by shirriff, z3to, ArminJo` at `version 2.2.3` + * `IRremote by shirriff, z3to, ArminJo` at `version 4.0.0` **CLI method for Arduino & Clones:** ````shell arduino-cli lib install Servo arduino-cli lib install LiquidCrystal arduino-cli lib install WiFi -arduino-cli lib install IRremote@2.2.3 +arduino-cli lib install IRremote@4.0.0 ```` **IDE method for ESP8266:** @@ -62,14 +62,14 @@ arduino-cli lib install IRremote@2.2.3 * Search for `ESP8266 Boards` and install the latest version. * Go to: Tools > Manage Libraries. * Find and install the following libraries, at the version numbers given: - * `IRremoteESP82666 by David Conran, Sebastien Warin` at `version 2.3.2` + * `IRremoteESP82666 by David Conran, Sebastien Warin` at `version 2.8.4` **CLI method for ESP8266:** ````shell arduino-cli config add board_manager.additional_urls https://arduino.esp8266.com/stable/package_esp8266com_index.json arduino-cli core update-index arduino-cli core install esp8266:esp8266 -arduino-cli lib install IRremoteESP8266@2.3.2 +arduino-cli lib install IRremoteESP8266@2.8.4 ```` **IDE method for ESP32:** @@ -78,6 +78,7 @@ arduino-cli lib install IRremoteESP8266@2.3.2 * Go to: Tools > Manage Libraries. * Find and install the following libraries, at the version numbers given: * `ESP32Servo by Kevin Harrington, John K. Bennett` at latest version + * `IRremoteESP82666 by David Conran, Sebastien Warin` at `version 2.8.4` **CLI method for ESP32:** ````shell @@ -85,6 +86,7 @@ arduino-cli config add board_manager.additional_urls https://raw.githubuserconte arduino-cli core update-index arduino-cli core install esp32 arduino-cli lib install ESP32Servo +arduino-cli lib install IRremoteESP8266@2.8.4 ```` #### 4) Generate the Arduino Sketch diff --git a/examples/ir_emitter/ir_emitter.rb b/examples/ir_emitter/ir_emitter.rb index ed51e779..92344e27 100644 --- a/examples/ir_emitter/ir_emitter.rb +++ b/examples/ir_emitter/ir_emitter.rb @@ -1,21 +1,18 @@ # # This is a simple test of the IREmitter (infrared blaster) class. -# It is based on part of "IRtest2" from the included Arduino library: -# https://github.com/z3t0/Arduino-IRremote/tree/master/examples +# It is based on this example from the Arduino library: +# https://github.com/Arduino-IRremote/Arduino-IRremote/tree/master/examples/SendDemo # -# To verify your emitter is working, you could flash "IRrecvDump2" from that -# examples directory to a second board. With an IR receiver on the correct pin -# of the board (see https://github.com/z3t0/Arduino-IRremote#hardware-specifications), -# open a serial monitor at 9600 and check that it receives the signal sent by this script. +# To verify your emitter is working, you can flash this sketch on a second board: +# https://github.com/Arduino-IRremote/Arduino-IRremote/tree/master/examples/ReceiveDemo # -# If you don't have 2 boards, use "IRrecvDump2" to capture a code from a button +# Attach an IR receiver to the receive pin (2 for Atmel AVR) and observe for serial output. +# +# If you don't have 2 boards, use the receive sketch to capture a code from a button # of a remote you have. Copy the raw code (long list of numbers in curly braces), -# from the serial output, replace the curly braces with square brackets, and -# those raw values are now compatible with Ruby and this script. +# and modify it into a Ruby array. # -# Flash the dino sketch onto your board and connect the IR emitter. -# Substitute a code you captured for the NEC example below. -# The corresponding action should happen on your device, eg. "Power" on your TV. +# Reflash the dino sketch onto your board and test the IR code operates your device. # # Both of these methods require you to have an IR receiver handy. # If you do not have one, there are IR codes in Raw format for many devices @@ -30,26 +27,29 @@ # explicit about which serial device this script must use. The relevant # TxRx call is below, but commented out. Enable it and substitute the approriate # device for the board that has the IR emitter connected and dino sketch loaded. +# Open the receiver board in the Arduino IDE's or another serial monitor. +# txrx = Dino::TxRx::Serial.new # txrx = Dino::TxRx::Serial.new(device: "/dev/ttyACM0") board = Dino::Board.new(txrx) -# Setting the IR emitter pin is currently unsupported. -# Although the value gets passed to the board, it always uses the default pin -# for your specfic board/chip, as defined by the library (in bold) at: -# https://github.com/z3t0/Arduino-IRremote#hardware-specifications -ir = Dino::Components::IREmitter.new(board: board, pin: 3) +# +# The IR emitter can be set up on most pins for most boards, but there might be conflicts +# with other hardware or libraries. Try different pins if one doesn't work. +# +ir = Dino::Components::IREmitter.new(board: board, pin: 4) -# This is the raw data corresponding to NEC 0x12345678 -code = [9000, 4500, - 560, 560, 560, 560, 560, 560, 560, 1690, - 560, 560, 560, 560, 560, 1690, 560, 560, - 560, 560, 560, 560, 560, 1690, 560, 1690, - 560, 560, 560, 1690, 560, 560, 560, 560, - 560, 560, 560, 1690, 560, 560, 560, 1690, - 560, 560, 560, 1690, 560, 1690, 560, 560, - 560, 560, 560, 1690, 560, 1690, 560, 1690, - 560, 1690, 560, 560, 560, 560, 560, 560, - 560] +# NEC Raw-Data=0xF708FB04. LSBFIRST, so the binary for each hex digit below is backward. +code = [ 9000, 4500, # Start bit + 560, 560, 560, 560, 560, 1690, 560, 560, # 0010 0x4 command + 560, 560, 560, 560, 560, 560, 560, 560, # 0000 0x0 command + 560, 1690, 560, 1690, 560,560, 560, 1690, # 1101 0xB command inverted + 560, 1690, 560, 1690, 560, 1690, 560, 1690, # 1111 0xF command inverted + 560, 560, 560, 560, 560, 560, 560, 1690, # 0001 0x8 address + 560, 560, 560, 560, 560, 560, 560, 560, # 0000 0x0 address + 560, 1690, 560, 1690, 560, 1690, 560, 560, # 1110 0x7 address inverted + 560, 1690, 560, 1690, 560, 1690, 560, 1690, # 1111 0xF address inverted + 560] # Stop bit ir.emit(code) +board.finish_write diff --git a/lib/dino/api/infrared.rb b/lib/dino/api/infrared.rb index 68fa112e..abb668f7 100644 --- a/lib/dino/api/infrared.rb +++ b/lib/dino/api/infrared.rb @@ -4,10 +4,13 @@ module Infrared include Helper def infrared_emit(pin, frequency, pulses) - # Need to start using length - 1, but doesn't work on board yet. - # 0 = 1 pulse, 255 = 256 pulses. + # + # Limit to 255 marks/spaces (not pairs) for now. + # + # Length must be 1-byte long, not literally 1 + # Pulses max is 2x255 bytes long since each is 2 bytes. length = pack :uint8, pulses.length, max: 1 - bytes = pack :uint16, pulses, min: 1, max: 512 + bytes = pack :uint16, pulses, min: 1, max: 510 write Message.encode command: 16, pin: convert_pin(pin), diff --git a/lib/dino_cli/packages.rb b/lib/dino_cli/packages.rb index bb4238e6..613b8d46 100644 --- a/lib/dino_cli/packages.rb +++ b/lib/dino_cli/packages.rb @@ -61,26 +61,17 @@ class DinoCLI::Generator ir_out: { description: "Transmit infrared signals", directive: "DINO_IR_OUT", - exclude: [:esp8266], + exclude: [:esp8266, :esp32], files: [ "lib/DinoIROut.cpp", - "vendor/Arduino-IRremote/boarddefs.h", - "vendor/Arduino-IRremote/IRremote.h", - "vendor/Arduino-IRremote/IRremoteInt.h", - "vendor/Arduino-IRremote/irSend.cpp", ] }, - ir_out_esp8266: { - description: "Transmit infrared signals with the ESP8266", + ir_out_esp: { + description: "Transmit infrared signals with the ESP8266 and ESP32", directive: "DINO_IR_OUT", - only: [:esp8266], + only: [:esp8266, :esp32], files: [ - "lib/DinoIROut.cpp", - "vendor/IRremoteESP8266/src/IRremoteESP8266.h", - "vendor/IRremoteESP8266/src/IRsend.h", - "vendor/IRremoteESP8266/src/IRsend.cpp", - "vendor/IRremoteESP8266/src/IRtimer.h", - "vendor/IRremoteESP8266/src/IRtimer.cpp", + "lib/DinoIROutESP.cpp", ] }, one_wire: { diff --git a/lib/dino_cli/targets.rb b/lib/dino_cli/targets.rb index 9a7bd70c..e4416f99 100644 --- a/lib/dino_cli/targets.rb +++ b/lib/dino_cli/targets.rb @@ -17,9 +17,9 @@ class DinoCLI::Generator sam3x: STANDARD_PACKAGES - [:serial, :tone, :ir_out], # ESP8266 mostly working. - esp8266: STANDARD_PACKAGES - [:lcd, :serial, :ir_out] + [:ir_out_esp8266], + esp8266: STANDARD_PACKAGES - [:lcd, :serial, :ir_out] + [:ir_out_esp], - # ESP is missing LCD, IR, and Software Serial - esp32: [:core, :tone, :one_wire, :servo, :shift, :spi, :i2c] + # ESP32 mostly working. + esp32: STANDARD_PACKAGES - [:lcd, :serial, :ir_out] + [:ir_out_esp], } end diff --git a/src/lib/DinoIROut.cpp b/src/lib/DinoIROut.cpp index 4847de35..12889ee9 100644 --- a/src/lib/DinoIROut.cpp +++ b/src/lib/DinoIROut.cpp @@ -2,33 +2,29 @@ // This file adds to the Dino class only if DINO_IR_OUT is defined in Dino.h. // #include "Dino.h" -#ifdef DINO_IR_OUT +#if defined(DINO_IR_OUT) && !defined(ESP8266) && !defined(ESP32) -#ifdef ESP8266 - #include "IRremoteESP8266.h" - #include "Irsend.h" -#else - #include "IRremote.h" - IRsend infraredOut; -#endif +#include + +// Save memory? +#define RAW_BUFFER_LENGTH 2 +#define DISABLE_CODE_FOR_RECEIVER +#define IR_REMOTE_DISABLE_RECEIVE_COMPLETE_CALLBACK +#define EXCLUDE_UNIVERSAL_PROTOCOLS +#define EXCLUDE_EXOTIC_PROTOCOLS +#define NO_LED_FEEDBACK_CODE // CMD = 16 // Send an infrared signal. void Dino::irSend(){ - #ifdef ESP8266 - IRsend infraredOut(pin); - #endif - - infraredOut.enableIROut(val); + // Byte 1+ of auxMsg is already little-endian uint16 pulses. + uint16_t *pulseArray = reinterpret_cast(auxMsg + 1); + + // Dynamically set the sending pin. Needs to be PWM capable. + IrSender.setSendPin(pin); - for (int i=0; i<(uint8_t)auxMsg[0]; i++){ - uint16_t pulse = ((uint16_t)auxMsg[(i*2)+2] << 8) | auxMsg[(i*2)+1]; - if ((i % 2) == 0) { - infraredOut.mark(pulse); - } else { - infraredOut.space(pulse); - } - } - infraredOut.space(0); + // auxMsg[0] contains number of uint16_t + // Val contains frequency + IrSender.sendRaw(pulseArray, auxMsg[0], val); } #endif diff --git a/src/lib/DinoIROutESP.cpp b/src/lib/DinoIROutESP.cpp new file mode 100644 index 00000000..f3c74dc7 --- /dev/null +++ b/src/lib/DinoIROutESP.cpp @@ -0,0 +1,26 @@ +// +// Dino IR output for the ESP8266 and ESP32. +// Depends on: https://github.com/crankyoldgit/IRremoteESP8266 +// DINO_IR_OUT must be defeind in DinoDefines.h +// +#include "Dino.h" +#if defined(DINO_IR_OUT) && (defined(ESP8266) || defined(ESP32)) + +#include +#include + +// CMD = 16 +// Send an infrared signal. +void Dino::irSend(){ + // Byte 1+ of auxMsg is already little-endian uint16 pulses. + uint16_t *pulseArray = reinterpret_cast(auxMsg + 1); + + // Can work on any pin. + IRsend infraredOut(pin); + infraredOut.begin(); + + // auxMsg[0] is how many pulses were packed. + // val is frequency + infraredOut.sendRaw(pulseArray, auxMsg[0], val); +} +#endif diff --git a/src/vendor/Arduino-IRremote b/src/vendor/Arduino-IRremote deleted file mode 160000 index 96186317..00000000 --- a/src/vendor/Arduino-IRremote +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 96186317b251c04da595159402f80538c0be5eed diff --git a/src/vendor/IRremoteESP8266 b/src/vendor/IRremoteESP8266 deleted file mode 160000 index fce4ccf0..00000000 --- a/src/vendor/IRremoteESP8266 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit fce4ccf0bd58eb7e0d905ee279eeccdca6ba1e69 From 5ad86190fde12e386f5c64d307193cfa6c5b6c89 Mon Sep 17 00:00:00 2001 From: vickash Date: Wed, 22 Feb 2023 18:09:37 -0400 Subject: [PATCH 273/296] Update I2C and SPI tests with new defaults --- src/lib/DinoSPI.cpp | 6 +++--- test/api/i2c_test.rb | 10 +++++----- test/api/spi_test.rb | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lib/DinoSPI.cpp b/src/lib/DinoSPI.cpp index 45db44ab..56857d16 100644 --- a/src/lib/DinoSPI.cpp +++ b/src/lib/DinoSPI.cpp @@ -37,9 +37,9 @@ void Dino::spiBegin(byte settings, uint32_t clockRate){ // Convenience wrapper for SPI.end void Dino::spiEnd(){ SPI.endTransaction(); - // If the sketch is using SPI for TxRx (Wi-Fi/Ethernet) we don't want to end. - // CLI generator will define TXRX_SPI in Dino.h for those cases. - #ifndef TXRX_SPI + // CLI generator will define TXRX_SPI in Dino.h for all WiFi sketches. + // Only matters if using the SPI-based WiFi Arduino shield. We don't want to end. + #if !defined(TXRX_SPI) || defined(ESP8266) || defined(ESP32) SPI.end(); #endif } diff --git a/test/api/i2c_test.rb b/test/api/i2c_test.rb index 1aa84eb4..0fb7ec9e 100644 --- a/test/api/i2c_test.rb +++ b/test/api/i2c_test.rb @@ -26,9 +26,9 @@ def test_write board aux = pack :uint8, [0x30, 0, 4, [1,2,3,4]] # Normal - message1 = Dino::Message.encode command: 34, value: 0b00, aux_message: aux + message1 = Dino::Message.encode command: 34, value: 0b01, aux_message: aux # Repeated start - message2 = Dino::Message.encode command: 34, value: 0b01, aux_message: aux + message2 = Dino::Message.encode command: 34, value: 0b00, aux_message: aux mock = MiniTest::Mock.new mock.expect :call, nil, [message1] @@ -45,9 +45,9 @@ def test_read board aux = pack :uint8, [0x30, 0, 0x03, 4] # Normal - message1 = Dino::Message.encode command: 35, value: 0b10, aux_message: aux + message1 = Dino::Message.encode command: 35, value: 0b11, aux_message: aux # Repeated start - message2 = Dino::Message.encode command: 35, value: 0b11, aux_message: aux + message2 = Dino::Message.encode command: 35, value: 0b10, aux_message: aux mock = MiniTest::Mock.new mock.expect :call, nil, [message1] @@ -63,7 +63,7 @@ def test_read def test_read_without_register board aux = pack :uint8, [0x30, 0, 0, 4] - message = Dino::Message.encode command: 35, value: 0b00, aux_message: aux + message = Dino::Message.encode command: 35, value: 0b01, aux_message: aux mock = MiniTest::Mock.new mock.expect :call, nil, [message] diff --git a/test/api/spi_test.rb b/test/api/spi_test.rb index 136d33db..c888f59d 100644 --- a/test/api/spi_test.rb +++ b/test/api/spi_test.rb @@ -27,7 +27,7 @@ def test_spi_lsbfirst end def test_spi_frequency - assert_equal (board.spi_header(frequency: nil )[0][3..6]), (pack :uint32, 3000000) + assert_equal (board.spi_header(frequency: nil )[0][3..6]), (pack :uint32, 1000000) assert_equal (board.spi_header(frequency: 8000000)[0][3..6]), (pack :uint32, 8000000) end From 8b965c7239c231d052d374f395748df097d6e6f8 Mon Sep 17 00:00:00 2001 From: vickash Date: Wed, 22 Feb 2023 18:14:41 -0400 Subject: [PATCH 274/296] Update stepper tests --- lib/dino/components/stepper.rb | 5 ++++- test/components/stepper_test.rb | 8 ++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/dino/components/stepper.rb b/lib/dino/components/stepper.rb index b6b12af4..d859c9c7 100644 --- a/lib/dino/components/stepper.rb +++ b/lib/dino/components/stepper.rb @@ -16,7 +16,10 @@ class Stepper def after_initialize(options={}) wake; on; - self.microsteps = 8 + + if (ms1 && ms2) + self.microsteps = 8 + end end def sleep diff --git a/test/components/stepper_test.rb b/test/components/stepper_test.rb index c12e6c9b..3696695a 100644 --- a/test/components/stepper_test.rb +++ b/test/components/stepper_test.rb @@ -16,7 +16,9 @@ def test_initialize end def test_step_cw - dir_mock = MiniTest::Mock.new.expect :low, nil + dir_mock = MiniTest::Mock.new + dir_mock.expect :low?, false + dir_mock.expect :low, nil step_mock = MiniTest::Mock.new step_mock.expect :high, nil step_mock.expect :low, nil @@ -31,7 +33,9 @@ def test_step_cw end def test_step_cc - dir_mock = MiniTest::Mock.new.expect :high, nil + dir_mock = MiniTest::Mock.new + dir_mock.expect :high?, false + dir_mock.expect :high, nil step_mock = MiniTest::Mock.new step_mock.expect :high, nil step_mock.expect :low, nil From 4113d67cf589c5191f2c576e7758caddc8455456 Mon Sep 17 00:00:00 2001 From: vickash Date: Wed, 22 Feb 2023 18:19:59 -0400 Subject: [PATCH 275/296] Use #digital_write in LED example so it doesn't PWM if on a capable pin --- examples/01-led/led.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/01-led/led.rb b/examples/01-led/led.rb index d78b0a77..18bf732a 100644 --- a/examples/01-led/led.rb +++ b/examples/01-led/led.rb @@ -30,9 +30,9 @@ # We can use led.write to set it directly, or named convenience methods. # These 3 lines all do the same thing: turn on, wait half a second, turn off. # -led.write(1); sleep 0.5; led.write(0) -led.high; sleep 0.5; led.low -led.on; sleep 0.5; led.off +led.digital_write(1); sleep 0.5; led.digital_write(0) +led.high; sleep 0.5; led.low +led.on; sleep 0.5; led.off # # led.toggle will set it to the opposite state each time it's called. From c94b093cca98da65450dd6ce3e6a2d0db1a7d330 Mon Sep 17 00:00:00 2001 From: vickash Date: Wed, 22 Feb 2023 20:02:52 -0400 Subject: [PATCH 276/296] Add Led/Potentiometer PWM example --- examples/04-pwm_led/pwm_led.rb | 67 ++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/examples/04-pwm_led/pwm_led.rb b/examples/04-pwm_led/pwm_led.rb index e69de29b..70792863 100644 --- a/examples/04-pwm_led/pwm_led.rb +++ b/examples/04-pwm_led/pwm_led.rb @@ -0,0 +1,67 @@ +# +# Example 4: LED Brightness and PWM +# +require 'bundler/setup' +require 'dino' + +# Set up the board, connecting with serial over USB +board = Dino::Board.new(Dino::TxRx::Serial.new) + +# +# LEDs don't change brightness much with voltage, but we can vary it with a +# technique called PWM. PWM sets up a fast (500Hz+) wave on the LED pin. Then we +# set a duty cycle, telling the LED what fraction of those cycles to be on for. +# +# Eg. 1 cycle on, 4 cycles off = 20% duty cycle. +# This happens so fast that it looks like 20% brightness to the naked eye. +# +# Depending on your board, some pins may not be PWM capable. Pin 13 on Arduinos +# isn't, so the earlier example was limited to on/off. There might be a wave +# symbol printed next to the pin number, but check your documentation. +# +# Note: If you have pins labeled "DAC", do not use them here. DACs generate steady +# analog voltages, not waves, but we access both DACs and PWM with #analog_write. +# +# Set up LED on a PWM pin. See pwm_led.png in this folder for hook-up diagram. +# +led = Dino::Components::Led.new(board: board, pin: 11) + +# led.analog_write sets duty cycle from 0 to 255, where 255 maps to 100%. +[0, 63, 127, 191, 255].each do |duty| + led.analog_write duty + percentage = (((duty+1)/256.to_f) * 100).floor + print "LED at #{percentage}% duty cycle. Press Enter..."; gets +end +puts + +# Now let's add the potentiometer from the previous examp to control it. +potentiometer = Dino::Components::Potentiometer.new(pin: 'A0', board: board) + +# Helper method to calculate brightness. +def map_pot_value(value) + # Map 10 bit value into 0 to 1 range. + fraction = value / 1023.to_f + + # Linearization hack for audio taper potentiometers. + # Adjust k for different tapers. This was an A500K. + k = 5 + linearized = (fraction * (k + 1)) / ((k * fraction) + 1) + # Use this for linear potentiometers instead. + # linearized = fraction + + # Map to the linearized 0-1 range to a 0-255 range for 8-bit PWM. + (linearized * 255).floor +end + +# Callback to change brightness. +potentiometer.on_change do |value| + duty = map_pot_value(value) + + # led.write uses digital_write OR analog_write, based on argument given. + led.write(duty) + percentage = (((duty+1)/256.to_f) * 100).floor + print "LED brightness: #{percentage}% \r" +end + +puts "Turn potentiometer to control the LED brightness. Press Ctrl+C to exit..." +sleep From bb0910dfe27f9a7169ba5b6c38475a762c722072 Mon Sep 17 00:00:00 2001 From: vickash Date: Thu, 23 Feb 2023 01:04:05 -0400 Subject: [PATCH 277/296] Fix register Shift In for ESP32 --- examples/register/shift_in.rb | 4 ---- src/lib/DinoShift.cpp | 39 ++++++++++++++++++++++------------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/examples/register/shift_in.rb b/examples/register/shift_in.rb index 9d0520f8..ca68aa07 100644 --- a/examples/register/shift_in.rb +++ b/examples/register/shift_in.rb @@ -9,10 +9,6 @@ # The Button object is created by using the register in place of board, and # the register output pin that it's connected to. # -# Note: rising_clock must be set to true if using TI CD4021B register or similar. -# This should apply to any register which outputs on a rising clock edge. -# Change as needed. -# require 'bundler/setup' require 'dino' diff --git a/src/lib/DinoShift.cpp b/src/lib/DinoShift.cpp index bbe630e7..4f77da27 100644 --- a/src/lib/DinoShift.cpp +++ b/src/lib/DinoShift.cpp @@ -11,7 +11,7 @@ struct shiftListener{ byte len; byte dataPin; byte clockPin; - byte clockHighFirst; + byte settings; boolean enabled; }; shiftListener shiftListeners[SHIFT_LISTENER_COUNT]; @@ -24,8 +24,9 @@ shiftListener shiftListeners[SHIFT_LISTENER_COUNT]; // auxMsg[0] = data pin (byte) // auxMsg[1] = clock pin (byte) // auxMsg[2] = settings -// bit 0 = transmission bit order (1 = MSBFIRST, 0 = LSBFIRST) -// bit 1 = send clock high before reading (byte) (0/1) (only matters for reading) +// bit 0 = transmission bit order, 1 = MSBFIRST, 0 = LSBFIRST (default) +// bit 1 = clock state before reading (only), 1 = HIGH, 0 = LOW (default) +// but 2+ = unused // auxMsg[3]+ = data (bytes) (write func only) // // CMD = 21 @@ -51,10 +52,19 @@ void Dino::shiftWrite(int latchPin, int len, byte dataPin, byte clockPin, byte s // CMD = 22 // Read from a shift register. void Dino::shiftRead(int latchPin, int len, byte dataPin, byte clockPin, byte settings) { - // Send clock pin high if using a register that clocks on rising edges. - // If not, the MSB will not be read on those registers (always 1), - // and all other bits will be shifted by 1 towards the LSB. - if (bitRead(settings, 1)) digitalWrite(clockPin, HIGH); + // This matters on the ESP32 for some reason. + pinMode(dataPin, INPUT); + pinMode(clockPin, OUTPUT); + pinMode(latchPin, OUTPUT); + + // Some registers want the clock pin high at the start. If not, first bit won't be read, + // and other bits will shift by 1 toward the new "first" bit. + // Default is to start with clock low. + if (bitRead(settings, 1)) { + digitalWrite(clockPin, HIGH); + } else { + digitalWrite(clockPin, LOW); + } // Latch high to read parallel state, then low again to stop. digitalWrite(latchPin, HIGH); @@ -66,14 +76,15 @@ void Dino::shiftRead(int latchPin, int len, byte dataPin, byte clockPin, byte se stream->print(':'); byte reading = 0; + // Read a number of bytes from the register. for (int i = 1; i <= len; i++) { - - // Read a single byte from the register. - if (bitRead(settings, 0)) { - reading = shiftIn(dataPin, clockPin, MSBFIRST); - } else { + + // Read a single byte from the register. + if (bitRead(settings, 0)) { + reading = shiftIn(dataPin, clockPin, MSBFIRST); + } else { reading = shiftIn(dataPin, clockPin, LSBFIRST); - } + } // Print it, then a comma or \n if it's the last byte. stream->print(reading); @@ -126,7 +137,7 @@ void Dino::updateShiftListeners() { shiftListeners[i].len, shiftListeners[i].dataPin, shiftListeners[i].clockPin, - shiftListeners[i].clockHighFirst); + shiftListeners[i].settings); } } } From e55cdbab4d9cd7f58ec31944ec6ebddf2cf1473e Mon Sep 17 00:00:00 2001 From: vickash Date: Thu, 23 Feb 2023 02:38:35 -0400 Subject: [PATCH 278/296] SPI works on ESP32, but needs different mode than AVR ? --- examples/register/spi_in.rb | 8 ++++---- lib/dino/api/spi.rb | 20 +++++-------------- src/lib/DinoSPI.cpp | 38 ++++++++++++++++++++++++------------- test/api/spi_test.rb | 6 +++--- 4 files changed, 37 insertions(+), 35 deletions(-) diff --git a/examples/register/spi_in.rb b/examples/register/spi_in.rb index bdf177d4..b0ade717 100644 --- a/examples/register/spi_in.rb +++ b/examples/register/spi_in.rb @@ -22,13 +22,13 @@ board = Dino::Board.new(Dino::TxRx::Serial.new) -# SPI mode and frequency are specific to a TI CD4021B register. Change as needed. +# Tested with CD4021B register, which is LSBFIRST, but needs SPI mode 0 on some boards and 2 on others. shift_register = Dino::Components::Register::SPIIn.new board: board, pin: 8, - spi_mode: 0, - frequency: 3000000 + spi_mode: 2, + bit_order: :lsbfirst + # frequency: 1000000 # bytes: 1 - # bit_order: :lsbfirst button = Dino::Components::Button.new(pin: 0, board: shift_register) diff --git a/lib/dino/api/spi.rb b/lib/dino/api/spi.rb index 8885fd67..27cd6e83 100644 --- a/lib/dino/api/spi.rb +++ b/lib/dino/api/spi.rb @@ -10,21 +10,11 @@ def spi_header(options) options[:frequency] ||= 1000000 options[:bit_order] ||= :msbfrst - # Bit 0..3 of settings control the SPI mode - # - # 0000 = SPI_MODE0 - # 0100 = SPI_MODE1 - # 1000 = SPI_MODE2 - # 1100 = SPI_MODE3 - # - settings = case options[:mode] - when 0; 0b0000 - when 1; 0b0100 - when 2; 0b1000 - when 3; 0b1100 - else - raise ArgumentError, "invalid SPI mode. Must be 0, 1, 2, or 3" - end + # Lowest 2 bits of settings control the SPI mode + settings = options[:mode] + unless (0..3).include? settings + raise ArgumentError, "invalid SPI mode. Must be 0, 1, 2, or 3" + end # Bit 7 of settings toggles MSBFIRST (1) or LSBFIRST (0) transmission order. settings = settings | 0b10000000 unless options[:bit_order] == :lsbfirst diff --git a/src/lib/DinoSPI.cpp b/src/lib/DinoSPI.cpp index 45db44ab..cfbccbb0 100644 --- a/src/lib/DinoSPI.cpp +++ b/src/lib/DinoSPI.cpp @@ -21,25 +21,36 @@ SpiListener spiListeners[SPI_LISTENER_COUNT]; void Dino::spiBegin(byte settings, uint32_t clockRate){ SPI.begin(); - // SPI mode is the lowest 4 bits of settings. - byte mode = settings & 0B00001111; - - // Bit 7 of settings toggles MSBFIRST. - // Don't try to refactor t his and just pass bit 7. - // On the Due, MSBFIRST and LSBFIRST are ByteOrder class objects. - if (bitRead(settings, 7)) { - SPI.beginTransaction(SPISettings(clockRate, MSBFIRST, mode)); + // SPI mode is the lowest 2 bits of settings. + byte mode = settings & 0B00000011; + // Bit 7 of settings controls bit order. 0 = LSBFIRST, 1 = MSBFIRST. + byte bitOrder = bitRead(settings, 7); + + // Do this nonsense so I don't have to care about varying macro definitions. + if (bitOrder == 0) { + switch(mode){ + case 0: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE0)); break; + case 1: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE1)); break; + case 2: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE2)); break; + case 3: SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, SPI_MODE3)); break; + } } else { - SPI.beginTransaction(SPISettings(clockRate, LSBFIRST, mode)); + switch(mode){ + case 0: SPI.beginTransaction(SPISettings(clockRate, MSBFIRST, SPI_MODE0)); break; + case 1: SPI.beginTransaction(SPISettings(clockRate, MSBFIRST, SPI_MODE1)); break; + case 2: SPI.beginTransaction(SPISettings(clockRate, MSBFIRST, SPI_MODE2)); break; + case 3: SPI.beginTransaction(SPISettings(clockRate, MSBFIRST, SPI_MODE3)); break; + } } } // Convenience wrapper for SPI.end void Dino::spiEnd(){ SPI.endTransaction(); - // If the sketch is using SPI for TxRx (Wi-Fi/Ethernet) we don't want to end. - // CLI generator will define TXRX_SPI in Dino.h for those cases. - #ifndef TXRX_SPI + // CLI generator will define TXRX_SPI in Dino.h for all WiFi sketches. + // Only matters if using the SPI-based WiFi Arduino shield. We don't want to end. + // ESP32 doesn't like when SPI.end is called either. Might be safe to never do it. + #if !defined(TXRX_SPI) && defined(__AVR__) SPI.end(); #endif } @@ -51,7 +62,8 @@ void Dino::spiEnd(){ // pin = slave select pin (int) // val = empty // auxMsg[0] = SPI settings -// Bit 0..3 = SPI mode +// Bit 0..1 = SPI mode +// Bit 2..6 = unused // Bit 7 = MSBFIRST(1) or LSBFIRST(0) // auxMsg[1] = read length (number of bytes) // auxMsg[2] = write length (number of bytes) diff --git a/test/api/spi_test.rb b/test/api/spi_test.rb index 136d33db..d62412a0 100644 --- a/test/api/spi_test.rb +++ b/test/api/spi_test.rb @@ -16,9 +16,9 @@ def board def test_spi_modes assert_equal (board.spi_header(mode: nil)[0][0]), (pack :uint8, 0b10000000) - assert_equal (board.spi_header(mode: 1 )[0][0]), (pack :uint8, 0b10000100) - assert_equal (board.spi_header(mode: 2 )[0][0]), (pack :uint8, 0b10001000) - assert_equal (board.spi_header(mode: 3 )[0][0]), (pack :uint8, 0b10001100) + assert_equal (board.spi_header(mode: 1 )[0][0]), (pack :uint8, 0b10000001) + assert_equal (board.spi_header(mode: 2 )[0][0]), (pack :uint8, 0b10000010) + assert_equal (board.spi_header(mode: 3 )[0][0]), (pack :uint8, 0b10000011) assert_raises(ArgumentError) { board.spi_header(mode: 4) } end From b2a2a2601a10abbc6eccbdfdd5a2429d21804ee3 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 25 Feb 2023 11:52:25 -0400 Subject: [PATCH 279/296] Change pinMode interface so ESP32 can pull pins up or down --- lib/dino/api/core.rb | 21 +++++++++++------- lib/dino/components/mixins/board_proxy.rb | 4 +--- lib/dino/components/setup/input.rb | 17 +++++++------- lib/dino/components/setup/multi_pin.rb | 8 ++++--- lib/dino/components/setup/single_pin.rb | 26 ++++++++++++++++------ src/lib/DinoCoreIO.cpp | 27 +++++++++++++++-------- test/api/core_test.rb | 24 +++++++------------- test/components/setup/input_test.rb | 27 +++++++---------------- test/components/setup/output_test.rb | 2 +- test/components/setup/single_pin_test.rb | 10 +++++++-- 10 files changed, 89 insertions(+), 77 deletions(-) diff --git a/lib/dino/api/core.rb b/lib/dino/api/core.rb index f79b359b..a1219e25 100644 --- a/lib/dino/api/core.rb +++ b/lib/dino/api/core.rb @@ -6,11 +6,21 @@ module Core DIVIDERS = [1, 2, 4, 8, 16, 32, 64, 128] # CMD = 0 - def set_pin_mode(pin, mode) - pin, value = convert_pin(pin), mode == :out ? 0 : 1 + def set_pin_mode(pin, direction, pull=nil) + pin = convert_pin(pin) + + # bit 0: 1 = input, 0 = output + # bit 1: 1 = pulldown + # but 2: 1 = pullup + # + settings = 0b001 + settings = 0b000 if direction == :output + settings |= 0b010 if pull == :pulldown + settings |= 0b100 if pull == :pullup + write Message.encode command: 0, pin: convert_pin(pin), - value: value + value: settings end # CMD = 1 @@ -33,11 +43,6 @@ def analog_read(pin) write Message.encode command: 4, pin: convert_pin(pin) end - def set_pullup(pin, pullup) - pin = convert_pin(pin) - pullup ? digital_write(pin, @high) : digital_write(pin, @low) - end - # CMD = 5 def set_listener(pin, state=:off, options={}) mode = options[:mode] || :digital diff --git a/lib/dino/components/mixins/board_proxy.rb b/lib/dino/components/mixins/board_proxy.rb index cf9395ec..6e242fdd 100644 --- a/lib/dino/components/mixins/board_proxy.rb +++ b/lib/dino/components/mixins/board_proxy.rb @@ -16,9 +16,7 @@ def convert_pin(pin) pin.to_i end - def set_pin_mode(pin, mode); end - - def set_pullup(pin, pullup); end + def set_pin_mode(pin, mode, pull=nil); end def start_read; end end diff --git a/lib/dino/components/setup/input.rb b/lib/dino/components/setup/input.rb index 52df4f8d..8bb22154 100644 --- a/lib/dino/components/setup/input.rb +++ b/lib/dino/components/setup/input.rb @@ -3,13 +3,7 @@ module Components module Setup module Input include SinglePin - attr_reader :pullup - - def pullup=(pullup) - @pullup = pullup - board.set_pullup(self.pin, pullup) - end - + def _stop_listener board.stop_listener(pin) end @@ -18,8 +12,13 @@ def _stop_listener def initialize_pins(options={}) super(options) - self.mode = :in - self.pullup = options[:pullup] + if options[:pullup] + self.mode = :input_pullup + elsif options[:pulldown] + self.mode = :input_pulldown + else + self.mode = :input + end end end end diff --git a/lib/dino/components/setup/multi_pin.rb b/lib/dino/components/setup/multi_pin.rb index 301c65b5..1111989d 100644 --- a/lib/dino/components/setup/multi_pin.rb +++ b/lib/dino/components/setup/multi_pin.rb @@ -9,7 +9,7 @@ module MultiPin # class and stores it in @proxies. # include Base - attr_reader :pin, :pins, :pullups, :proxies + attr_reader :pin, :pins, :pulldowns, :pullups, :proxies # Return a hash with the state of each proxy component. def proxy_states @@ -22,7 +22,7 @@ def proxy_states protected - attr_writer :pins, :pullups, :proxies + attr_writer :pins, :pulldowns, :pullups, :proxies def self.included(base) base.extend ClassMethods @@ -69,6 +69,7 @@ def proxy_pins(options={}) def initialize_pins(options={}) self.pins = options[:pins] + self.pulldowns = options[:pulldowns] || {} self.pullups = options[:pullups] || {} self.proxies = {} validate_pins @@ -88,7 +89,8 @@ def build_proxies # Build proxies for named pins once a pin number was given. pins.each_pair do |pin_name, pin_number| if proxy_classes[pin_name] - component = proxy_classes[pin_name].new(board: board, pin: pin_number, pullup: pullups[pin_name]) + params = { board: board, pin: pin_number, pullup: pullups[pin_name], pulldown: pulldowns[pin_name] } + component = proxy_classes[pin_name].new(params) self.proxies[pin_name] = component instance_variable_set("@#{pin_name}", component) singleton_class.class_eval { attr_reader pin_name } diff --git a/lib/dino/components/setup/single_pin.rb b/lib/dino/components/setup/single_pin.rb index 3dac83c5..03111482 100644 --- a/lib/dino/components/setup/single_pin.rb +++ b/lib/dino/components/setup/single_pin.rb @@ -3,9 +3,26 @@ module Components module Setup module SinglePin include Base - attr_reader :pin, :mode + attr_reader :pin, :directon, :pull, :mode + + def mode=(mode) + @direction = :input + @direction = :output if [:out, :output].include? mode - protected + @pull = nil + if @direction == :input + @pull = :pullup if mode == :input_pullup + @pull = :pulldown if mode == :input_pulldown + end + + @mode = "#{@direction}" + @mode << "_#{@pull}" if @pull + @mode = @mode.to_sym + + board.set_pin_mode(pin, @direction, @pull) + end + + protected attr_writer :pin @@ -16,11 +33,6 @@ def initialize_pins(options={}) raise ArgumentError, 'a pin is required for this component' end end - - def mode=(mode) - board.set_pin_mode(pin, mode) - @mode = mode - end end end end diff --git a/src/lib/DinoCoreIO.cpp b/src/lib/DinoCoreIO.cpp index 51ca3522..e6fb4379 100644 --- a/src/lib/DinoCoreIO.cpp +++ b/src/lib/DinoCoreIO.cpp @@ -10,17 +10,26 @@ void Dino::setMode(byte p, byte m) { Serial.println(m); #endif - if (m == 0) { + // bit 0: 1=input, 0=output + // bit 1: 1=pulldown + // bit 2: 1=pullup + + if (bitRead(m, 0) == 0) { pinMode(p, OUTPUT); + } else { + pinMode(p, INPUT); } - else { - #ifdef ESP32 - detachLEDC(p); - pinMode(p, INPUT_PULLUP); - #else - pinMode(p, INPUT); - #endif - } + + // Set pullup or pulldown for ESP32 + #ifdef ESP32 + detachLEDC(p); + if(bitRead(m, 1)) pinMode(p, INPUT_PULLDOWN); + if(bitRead(m, 2)) pinMode(p, INPUT_PULLUP); + + // Write high on other platforms if pullup. + #else + if (bitRead(m, 2)) digitalWrite(p, HIGH); + #endif } // CMD = 01 diff --git a/test/api/core_test.rb b/test/api/core_test.rb index 5cdd33d4..25b20093 100644 --- a/test/api/core_test.rb +++ b/test/api/core_test.rb @@ -13,24 +13,16 @@ def board def test_set_pin_mode mock = MiniTest::Mock.new - mock.expect(:call, nil, [Dino::Message.encode(command: 0, pin: 1, value: board.low)]) - mock.expect(:call, nil, [Dino::Message.encode(command: 0, pin: 1, value: board.high)]) + mock.expect(:call, nil, [Dino::Message.encode(command: 0, pin: 1, value: 0)]) + mock.expect(:call, nil, [Dino::Message.encode(command: 0, pin: 1, value: 1)]) + mock.expect(:call, nil, [Dino::Message.encode(command: 0, pin: 1, value: 0b011)]) + mock.expect(:call, nil, [Dino::Message.encode(command: 0, pin: 1, value: 0b101)]) board.stub(:write, mock) do - board.set_pin_mode 1, :out - board.set_pin_mode 1, :in - end - mock.verify - end - - def test_set_pullup - mock = MiniTest::Mock.new - mock.expect(:call, nil, [Dino::Message.encode(command: 1, pin: 1, value: board.low)]) - mock.expect(:call, nil, [Dino::Message.encode(command: 1, pin: 1, value: board.high)]) - - board.stub(:write, mock) do - board.set_pullup 1, false - board.set_pullup 1, true + board.set_pin_mode 1, :output + board.set_pin_mode 1, :input + board.set_pin_mode 1, :input, :pulldown + board.set_pin_mode 1, :input, :pullup end mock.verify end diff --git a/test/components/setup/input_test.rb b/test/components/setup/input_test.rb index 164fb286..51e8bb42 100644 --- a/test/components/setup/input_test.rb +++ b/test/components/setup/input_test.rb @@ -13,24 +13,20 @@ def part @part ||= InputComponent.new(board: board, pin: 1) end - def test_pin_mode - mock = Minitest::Mock.new.expect :call, nil, [1, :in] + def test_pin_mode_and_pullup + mock = Minitest::Mock.new + mock.expect :call, nil, [1, :input, nil] + mock.expect :call, nil, [1, :input, :pulldown] + mock.expect :call, nil, [1, :input, :pullup] + board.stub(:set_pin_mode, mock) do part + InputComponent.new(board: board, pin: 1, pulldown: true) + InputComponent.new(board: board, pin: 1, pullup: true) end mock.verify end - def test_pullup - mock = Minitest::Mock.new - mock.expect :call, nil, [1, nil] - mock.expect :call, nil, [1, true] - board.stub(:set_pullup, mock) do - part.pullup = true - end - mock.verify - end - def test_stop_listener mock = Minitest::Mock.new mock.expect :call, nil, [1] @@ -39,11 +35,4 @@ def test_stop_listener end mock.verify end - - def test_pullup_in_options - mock = Minitest::Mock.new.expect :call, nil, [2, true] - board.stub(:set_pullup, mock) do - InputComponent.new(board: board, pin: 2, pullup: true) - end - end end diff --git a/test/components/setup/output_test.rb b/test/components/setup/output_test.rb index be44580a..cca9a4ea 100644 --- a/test/components/setup/output_test.rb +++ b/test/components/setup/output_test.rb @@ -14,6 +14,6 @@ def part end def test_pin_mode - assert_equal part.mode, :out + assert_equal part.mode, :output end end diff --git a/test/components/setup/single_pin_test.rb b/test/components/setup/single_pin_test.rb index 7d67cc7b..801f02c9 100644 --- a/test/components/setup/single_pin_test.rb +++ b/test/components/setup/single_pin_test.rb @@ -19,9 +19,15 @@ def test_requires_pin def test_mode= mock = Minitest::Mock.new - mock.expect :call, nil, [1, :out] + mock.expect :call, nil, [1, :output, nil] + mock.expect :call, nil, [1, :input, :pullup] + mock.expect :call, nil, [1, :input, :pulldown] + board.stub(:set_pin_mode, mock) do - part.send(:mode=, :out) + part.mode = :output + part.mode = :input_pullup + part.mode = :input_pulldown end + mock.verify end end From d6afbbce0b0f36b6edb74905cbcb02dc554c5986 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 25 Feb 2023 12:39:32 -0400 Subject: [PATCH 280/296] Use LEDC channels in reverse order to minimize servo conflict --- CHANGELOG.md | 10 ++++++++-- HARDWARE.md | 4 ++-- src/lib/DinoCoreIO.cpp | 8 ++++---- src/lib/DinoServo.cpp | 3 ++- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7024f047..25afe8df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,16 @@ - This is the default sketch if `--target` isn't specified, and works for Arduino (and other) products based on the ATmega AVR chips, like the Uno, Nano, Leonardo and Mega. - ESP8266 (`--target esp8266`): - - Works with `Dino::Board.new`, but calling `Dino::Board::ESP8266.new` instead allows pins to be referred to as any of `'GPIO4'`, `4`, or `'D2'`, as printed on the NodeMCU dev board. When in doubt, look up your board's GPIO mapping and use those numbers instead. + - Works with `Dino::Board.new`, but calling `Dino::Board::ESP8266.new` instead allows pins to be referred to as any of `'GPIO4'`, `4`, or `'D2'`, specifically for the NodeMCU dev board. When in doubt, look up your board's GPIO mapping and use those numbers instead. - Works with either built in WiFi or Serial. - WiFi version supports OTA (over-the-air) update in the Arduino IDE. Initial flash must still be done via serial. - - **Note**: SoftwareSerial is incompatible with the ESP8266. LiquidCrystal (LCD) compiles for the ESP8266, but does not work. Both of these are excluded. + - **Note:** SoftwareSerial and LiquidCrystal (LCD) both do not work on the ESP8266, and are excluded from th sketch. + +- ESP32 (`--target esp32`): + - Works with either built in WiFi or Serial. + - WiFi version does NOT support OTA (over-the-air) updates yet. + - **Note:** Servos and analog outputs share the `LEDC` channels on the board. Maximum of 16 combined. + - **Note:** SoftwareSerial and LiquidCrystal (LCD) both do not work on the ESP32, and are excluded from th sketch. - Arduino Due (`--target sam3x`) : - Up to 12-bit analog in/out. Pass a `bits:` option to `Board#new` to set resolution for both. diff --git a/HARDWARE.md b/HARDWARE.md index f67f0a98..e6a3c496 100644 --- a/HARDWARE.md +++ b/HARDWARE.md @@ -92,9 +92,9 @@ | Name | Status | Interface | Version | Component Class | Notes | | :--------------- | :------: | :-------- | :----- | :--------------- |------ | -| Servo | :green_heart: | PWM + Library | 0.11.2 | `Servo` | Max 6 servos on ATmega168, 12 otherwise +| Servo | :green_heart: | PWM + Library | 0.11.2 | `Servo` | Maximum of 6 on ATmega168, 16 on ESP32 and 12 otherwise | L298N | :heart: | PWM Out | - | | 2ch DC motor driver -| A3967 | :green_heart: | Digital Out | 0.12.0 | `Stepper`| 1ch microstepper (EasyDriver) +| A3967 | :green_heart: | Digital Out | 0.12.0 | `Stepper`| 1ch microstepper (EasyDriver)å | PCA9685 | :heart: | I2C | - | - | 16ch 12-bit PWM for servo, DC motor, or LED ### Displays diff --git a/src/lib/DinoCoreIO.cpp b/src/lib/DinoCoreIO.cpp index e6fb4379..6d731d0b 100644 --- a/src/lib/DinoCoreIO.cpp +++ b/src/lib/DinoCoreIO.cpp @@ -105,14 +105,14 @@ void Dino::aWrite(byte p, int v, boolean echo) { #ifdef ESP32 byte Dino::ledcChannel(byte p) { // Search for enabled LEDC channel with given pin and use that if found. - for (byte i = 0; i < LEDC_CHANNEL_COUNT; i++){ + for (int i = LEDC_CHANNEL_COUNT -1; i > 0; i--){ if ((ledcPins[i][0] == 1) && (ledcPins[i][1] == p)){ return i; } } // We didn't find a channel to reuse. - for (byte i = 0; i < LEDC_CHANNEL_COUNT; i++){ + for (int i = LEDC_CHANNEL_COUNT -1; i > 0; i--){ // If the channel isn't initialized and it isn't marked as used, use it. // should find some way to check if the channel itslef is being used if ((ledcPins[i][0] == 0)) { @@ -145,14 +145,14 @@ void Dino::detachLEDC(byte p){ ledcDetachPin(p); // Mark any channel associated with our pin as unused. - for (byte i = 0; i < LEDC_CHANNEL_COUNT; i++){ + for (int i = LEDC_CHANNEL_COUNT -1; i > 0; i--){ if (ledcPins[i][1] == p) ledcPins[i][0] = 0; } } // Clear all the LEDC channels on reset. void Dino::clearLedcChannels(){ - for (byte i = 0; i < LEDC_CHANNEL_COUNT; i++){ + for (int i = LEDC_CHANNEL_COUNT -1; i > 0; i--){ // Stop the channel if it was still enabled. if (ledcPins[i][0] != 0) ledcDetachPin(ledcPins[i][1]); diff --git a/src/lib/DinoServo.cpp b/src/lib/DinoServo.cpp index 6f8913a6..c87436bf 100644 --- a/src/lib/DinoServo.cpp +++ b/src/lib/DinoServo.cpp @@ -14,11 +14,12 @@ // Could be up to 48 on Arduino Mega. #if defined (__AVR_ATmega168__) #define SERVO_COUNT 6 +#elif defined(ESP32) + #define SERVO_COUNT 16 #else #define SERVO_COUNT 12 #endif - // Create an array of wrapper structs that link pins to servo objects. struct ServoWrapper{ byte pin; From 6242fce3eecdb489954cf960049878d80084ca38 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 25 Feb 2023 12:50:33 -0400 Subject: [PATCH 281/296] Correct error if calling A or DAC pin on boards without --- lib/dino/board/default.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/dino/board/default.rb b/lib/dino/board/default.rb index 271e4fac..6b32ff9c 100644 --- a/lib/dino/board/default.rb +++ b/lib/dino/board/default.rb @@ -17,8 +17,8 @@ def convert_pin(pin) return nil if pin == nil pin = pin.to_s return pin.to_i if pin.match(DIGITAL_REGEX) - return analog_pin_to_i(pin) if pin.match(ANALOG_REGEX) - return dac_pin_to_i(pin) if pin.match(DAC_REGEX) + return analog_pin_to_i(pin) if pin.match(ANALOG_REGEX) && analog_zero + return dac_pin_to_i(pin) if pin.match(DAC_REGEX) && dac_zero return "EE" if pin == "EE" raise ArgumentError, "incorrect pin format: #{pin.inspect}" end From 2c49f195db8222eca2e1227687b242c49dbf285f Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 25 Feb 2023 13:00:50 -0400 Subject: [PATCH 282/296] Add ESP32 to changelog and hardware list --- CHANGELOG.md | 11 +++++++---- HARDWARE.md | 8 ++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25afe8df..2b7b230f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,22 +9,25 @@ - This is the default sketch if `--target` isn't specified, and works for Arduino (and other) products based on the ATmega AVR chips, like the Uno, Nano, Leonardo and Mega. - ESP8266 (`--target esp8266`): - - Works with `Dino::Board.new`, but calling `Dino::Board::ESP8266.new` instead allows pins to be referred to as any of `'GPIO4'`, `4`, or `'D2'`, specifically for the NodeMCU dev board. When in doubt, look up your board's GPIO mapping and use those numbers instead. - Works with either built in WiFi or Serial. - WiFi version supports OTA (over-the-air) update in the Arduino IDE. Initial flash must still be done via serial. - - **Note:** SoftwareSerial and LiquidCrystal (LCD) both do not work on the ESP8266, and are excluded from th sketch. + - Dev boards can map GPIOs to physical pins differently. Always look up the GPIO numbers and use those for pin numbers. + - **Note:** SoftwareSerial and LiquidCrystal (LCD) both do not work on the ESP8266, and are excluded from the sketch. - ESP32 (`--target esp32`): - Works with either built in WiFi or Serial. - WiFi version does NOT support OTA (over-the-air) updates yet. + - Only tested with the original ESP32 module so far, not the later revisions with slightly different hardware. + - Dev boards can map GPIOs to physical pins differently. Always look up the GPIO numbers and use those for pin numbers. - **Note:** Servos and analog outputs share the `LEDC` channels on the board. Maximum of 16 combined. - - **Note:** SoftwareSerial and LiquidCrystal (LCD) both do not work on the ESP32, and are excluded from th sketch. + - **Note:** SoftwareSerial and LiquidCrystal (LCD) both do not work on the ESP32, and are excluded from the sketch. + - **Note:** SPI bug exists where input modes don't match other platforms. Eg. For a register using mode 0 on AVR, mode 2 needs to be set on ESP32 for it to work. Using mode 0 misses a bit. - Arduino Due (`--target sam3x`) : - Up to 12-bit analog in/out. Pass a `bits:` option to `Board#new` to set resolution for both. - DAC support. Refer to DAC pins as `'DAC0'`, `'DAC1'`, just as labeled on the board. Call `#analog_write` or just `#write` on an `AnalogOutput` component that uses the pin. - Uses the native ARM serial port by default. Configurable in sketch to use programming port. - - **Note**: SoftwareSerial, Infrared, and Tone are currently incompatible with the Arduino Due, and excluded from the sketch. + - **Note**: SoftwareSerial, Infrared, and Tone are incompatible with the Arduino Due, and excluded from the sketch. - ATmega168 (`--target mega168`): - By excluding a lot of features, we can still fit the memory constraints of the ATmega168 chips found in older Arduinos. diff --git a/HARDWARE.md b/HARDWARE.md index e6a3c496..8d64fef9 100644 --- a/HARDWARE.md +++ b/HARDWARE.md @@ -22,16 +22,16 @@ | :-------- | :------: | :----- | :--------------- |------ | | Wiznet W5100/5500 | :green_heart: | 0.11.1 | Ethernet Shield | Wired Ethernet for Uno/Mega pin-compatible boards | HDG204 + AT32UC3 | :man_shrugging: | 0.12.0 | WiFi Shield | WiFi for Uno. No hardware to test, but compiles -| ATWINC1500 | :man_shrugging: | 0.12.0 | WiFi Shield 101 | As above, but heavy on RAM. Compiles for Mega only +| ATWINC1500 | :man_shrugging: | 0.12.0 | WiFi Shield 101 | As above, high memory use, Mega only ### Espressif Chips with Built-In WiFi | Chip | Status | Version| Boards | Notes | | :-------- | :------: | :----- | :--------------- |------ | | ESP8266 | :yellow_heart: | 0.12.0 | NodeMCU | SoftwareSerial and LCD don't work yet -| ESP32 | :test_tube: | - | DOIT ESP32 DevKit V1 | Original ESP-WROOM-32 -| ESP32-S2 | :test_tube: | - | LOLIN S2 Pico | Single core Xtensa, native USB -| ESP32-S3 | :test_tube: | - | LOLIN S3 V1.0.0 | Dual core Xtensa, native USB +| ESP32 | :yellow_heart: | 0.12.0 | DOIT ESP32 DevKit V1 | No LCD or SoftSerial. SPI input bug. See changelog +| ESP32-S2 | :test_tube: | 0.12.0 | LOLIN S2 Pico | Might work, not tested yet +| ESP32-S3 | :test_tube: | 0.12.0 | LOLIN S3 V1.0.0 | Might work, not tested yet **Note:** There are too many boards using these chips to be comprehensive. Most should work. These are the exact ones used for testing, chosen based on popularity. From 204f6e60592dd5675c3739b8b2177fa211912b41 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 25 Feb 2023 13:15:32 -0400 Subject: [PATCH 283/296] Fix -- instead of - in CLI arguments --- README.md | 4 ++-- lib/dino_cli/usage.txt | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 064c991b..d79fa7fd 100644 --- a/README.md +++ b/README.md @@ -99,12 +99,12 @@ dino sketch serial **For ESP8266, Serial over USB:** ```shell -dino sketch serial -target esp8266 +dino sketch serial --target esp8266 ```` **For ESP32 over WiFi (2.4Ghz and DHCP Only):** ```shell -dino sketch wifi -target esp32 -ssid YOUR_SSID -password YOUR_PASSWORD +dino sketch wifi --target esp32 --ssid YOUR_SSID --password YOUR_PASSWORD ```` **Note:** [This example](examples/tcp.rb) shows how to use a board over a TCP connection, but the WiFi sketches fall back to the serial interface if no TCP client is connected. You should be able to run the examples over serial while still connected. diff --git a/lib/dino_cli/usage.txt b/lib/dino_cli/usage.txt index 5e4202a7..0c60fd42 100644 --- a/lib/dino_cli/usage.txt +++ b/lib/dino_cli/usage.txt @@ -9,26 +9,26 @@ Global options: - -target TARGET (default: mega. Run 'dino targets' for more info) - -debug (default: off) + --target TARGET (default: mega. Run 'dino targets' for more info) + --debug (default: off) Available sketches and sketch for each sketch: serial - -baud BAUD (default: 115200) + --baud BAUD (default: 115200) ethernet - -mac XX:XX:XX:XX:XX:XX (required) - -ip XXX.XXX.XXX.XXX (required, no DHCP) - -port PORT (default: 3466) + --mac XX:XX:XX:XX:XX:XX (required) + --ip XXX.XXX.XXX.XXX (required, no DHCP) + --port PORT (default: 3466) wifi - -ssid SSID (required) - -password PASSWORD (required) - -port PORT (default: 3466) + --ssid SSID (required) + --password PASSWORD (required) + --port PORT (default: 3466) Examples: - dino sketch ethernet -mac 12:34:56:78:90:12 - ip 192.168.1.2 - dino sketch serial -baud 57600 - dino sketch serial -baud 9600 -target core + dino sketch ethernet --mac 12:34:56:78:90:12 --ip 192.168.1.2 + dino sketch serial --baud 57600 + dino sketch serial --baud 9600 -target core From 64c537cfdfe07368ff5b5f5dd3dec919231e2908 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 25 Feb 2023 14:18:11 -0400 Subject: [PATCH 284/296] The Nano Every ATmega4809 should just work --- HARDWARE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/HARDWARE.md b/HARDWARE.md index 8d64fef9..1c239448 100644 --- a/HARDWARE.md +++ b/HARDWARE.md @@ -11,6 +11,7 @@ | ATmega32u4 | :green_heart: | 0.11.0 | Leonardo, Micro, Leonardo ETH, Esplora, LilyPad USB | **v0.11.1** for Leonardo ETH | ATmega1280 | :green_heart: | 0.11.1 | Mega | | ATmega2560 | :green_heart: | 0.11.1 | Mega2560, Arduino Mega ADK | +| ATmega4809 | :man_shrugging: | 0.12.0 | Nano Every | No hardware to test, but should work | ATSAM3X8E | :yellow_heart: | 0.12.0 | Due | Uses native USB. SoftSerial, Tone, IR Out, and I2C don't work yet | ATSAMD21 | :heart: | - | Zero, M0, M0 Pro | From 2721bedeb1629b9fa8484e13aeb068562722c302 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 25 Feb 2023 15:02:27 -0400 Subject: [PATCH 285/296] Tests for remaining mixins --- test/components/mixins/board_proxy_test.rb | 24 ++++++++++++ test/components/mixins/bus_master_test.rb | 28 ++++++++++++++ test/components/mixins/bus_slave_test.rb | 44 ++++++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 test/components/mixins/board_proxy_test.rb create mode 100644 test/components/mixins/bus_master_test.rb create mode 100644 test/components/mixins/bus_slave_test.rb diff --git a/test/components/mixins/board_proxy_test.rb b/test/components/mixins/board_proxy_test.rb new file mode 100644 index 00000000..4642d7bb --- /dev/null +++ b/test/components/mixins/board_proxy_test.rb @@ -0,0 +1,24 @@ +require 'test_helper' + +class ProxiedComponent + include Dino::Components::Setup::Base + include Dino::Components::Mixins::BoardProxy +end + +class BoardProxyTest < Minitest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= ProxiedComponent.new(board: board) + end + + def test_methods + assert_equal part.high, 1 + assert_equal part.low, 0 + assert_equal part.convert_pin("7"), 7 + part.set_pin_mode(1, :output, :pullup) + part.start_read + end +end diff --git a/test/components/mixins/bus_master_test.rb b/test/components/mixins/bus_master_test.rb new file mode 100644 index 00000000..cb46d0e5 --- /dev/null +++ b/test/components/mixins/bus_master_test.rb @@ -0,0 +1,28 @@ +require 'test_helper' + +class BusMasterComponent + include Dino::Components::Setup::Base + include Dino::Components::Mixins::BusMaster +end + +class BusMasterTest < Minitest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= BusMasterComponent.new(board: board) + end + + def test_has_mutex + assert_equal part.mutex.class, Mutex + end + + def test_components + part.add_component "1" + assert_equal part.components, ["1"] + + part.remove_component "1" + assert_equal part.components, [] + end +end diff --git a/test/components/mixins/bus_slave_test.rb b/test/components/mixins/bus_slave_test.rb new file mode 100644 index 00000000..f6959411 --- /dev/null +++ b/test/components/mixins/bus_slave_test.rb @@ -0,0 +1,44 @@ +require 'test_helper' + +class BusMasterComponent + include Dino::Components::Setup::Base + include Dino::Components::Mixins::BusMaster +end + +class BusSlaveComponent + include Dino::Components::Setup::Base + include Dino::Components::Mixins::BusSlave +end + +class BusSlaveTest < Minitest::Test + def board + @board ||= BoardMock.new + end + + def bus + @bus ||= BusMasterComponent.new(board: board) + end + + def part + @part ||= BusSlaveComponent.new(bus: bus, address: 0x22) + end + + def test_initialize + assert_equal part.board, bus + assert_equal part.address, 0x22 + end + + def test_requires_address + assert_raises(ArgumentError) { BusSlaveComponent.new(bus: bus) } + end + + def test_can_use_bus_atomically + mock = MiniTest::Mock.new + 1.times {mock.expect(:call, nil)} + + bus.mutex.stub(:synchronize, mock) do + part.atomically { true; false; } + end + mock.verify + end +end From d3a1630e97d6de9ca95fdb3114304528aa6cac9a Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 25 Feb 2023 16:06:32 -0400 Subject: [PATCH 286/296] Input register tests. Fix bug where bit order was reversed, but not byte order. Switched default order back to LSBFIRST to match output registers, since. Using MSBFIRST just hid the bug. --- examples/register/spi_in.rb | 2 +- lib/dino/components/register/input.rb | 8 +- lib/dino/components/register/shift_in.rb | 10 +- lib/dino/components/register/spi_in.rb | 2 +- test/components/i2c/slave_test.rb | 2 +- test/components/register/input_test.rb | 108 ++++++++++++++++++++++ test/components/register/shift_in_test.rb | 37 ++------ test/components/register/spi_in_test.rb | 10 +- 8 files changed, 132 insertions(+), 47 deletions(-) create mode 100644 test/components/register/input_test.rb diff --git a/examples/register/spi_in.rb b/examples/register/spi_in.rb index b0ade717..bc56d747 100644 --- a/examples/register/spi_in.rb +++ b/examples/register/spi_in.rb @@ -26,7 +26,7 @@ shift_register = Dino::Components::Register::SPIIn.new board: board, pin: 8, spi_mode: 2, - bit_order: :lsbfirst + # bit_order: :lsbfirst # frequency: 1000000 # bytes: 1 diff --git a/lib/dino/components/register/input.rb b/lib/dino/components/register/input.rb index 76d85ec1..b4ecd657 100644 --- a/lib/dino/components/register/input.rb +++ b/lib/dino/components/register/input.rb @@ -20,14 +20,14 @@ def before_initialize(options={}) # When used as a board proxy, store the state of each register # pin as a 0 or 1 in an array that is (@bytes * 8) long. Zero out to start. # - @state = Array.new(@bytes*8) {|i| 0} + @state = Array.new(@bytes*8) { 0 } # # Keep track of whether anything is listening or reading a specific pin. # This might be separate from whether a component is attached or not. # - @reading_pins = Array.new(@bytes*8) {|i| false} - @listening_pins = Array.new(@bytes*8) {|i| false} + @reading_pins = Array.new(@bytes*8) { false} + @listening_pins = Array.new(@bytes*8) { false} end def after_initialize(options={}) @@ -66,7 +66,7 @@ def update(message) # def byte_array_to_bit_array(byte_array) byte_array.map do |byte| - byte.to_i.to_s(2).rjust(8, "0").split("") + byte.to_i.to_s(2).rjust(8, "0").split("").reverse end.flatten.map { |bit| bit.to_i } end diff --git a/lib/dino/components/register/shift_in.rb b/lib/dino/components/register/shift_in.rb index 2a217875..6fd6bd1d 100644 --- a/lib/dino/components/register/shift_in.rb +++ b/lib/dino/components/register/shift_in.rb @@ -18,7 +18,7 @@ class ShiftIn def before_initialize(options={}) super(options) self.rising_clock = options[:rising_clock] || false - self.bit_order = options[:bit_order] || :msbfirst + self.bit_order = options[:bit_order] || :lsbfirst end def after_initialize(options={}) @@ -38,14 +38,14 @@ def rising_clock=(value) @rising_clock = [0, nil, false].include?(value) ? false : true end - def read(num_bytes=@bytes) - board.shift_read latch.pin, data.pin, clock.pin, num_bytes, + def read + board.shift_read latch.pin, data.pin, clock.pin, @bytes, preclock_high: rising_clock, bit_order: bit_order end - def listen(num_bytes=@bytes) - board.shift_listen latch.pin, data.pin, clock.pin, num_bytes, + def listen + board.shift_listen latch.pin, data.pin, clock.pin, @bytes, preclock_high: rising_clock, bit_order: bit_order end diff --git a/lib/dino/components/register/spi_in.rb b/lib/dino/components/register/spi_in.rb index 586b7ec0..5c28fc28 100644 --- a/lib/dino/components/register/spi_in.rb +++ b/lib/dino/components/register/spi_in.rb @@ -16,7 +16,7 @@ def initialize(options) super(options) @spi_mode = options[:spi_mode] || 0 @frequency = options[:frequency] || 1000000 - @bit_order = options[:bit_order] || :msbfirst + @bit_order = options[:bit_order] || :lsbfirst end def read diff --git a/test/components/i2c/slave_test.rb b/test/components/i2c/slave_test.rb index 194fea06..317a7971 100644 --- a/test/components/i2c/slave_test.rb +++ b/test/components/i2c/slave_test.rb @@ -1,6 +1,6 @@ require 'test_helper' -class I2SlaveTest < MiniTest::Test +class I2CSlaveTest < MiniTest::Test def board @board ||= BoardMock.new end diff --git a/test/components/register/input_test.rb b/test/components/register/input_test.rb new file mode 100644 index 00000000..fbe56228 --- /dev/null +++ b/test/components/register/input_test.rb @@ -0,0 +1,108 @@ +require 'test_helper' + +class InputRegisterTest < Minitest::Test + def board + @board ||= BoardMock.new + end + + def options + { board: board, pins: { clock: 12, data: 11, latch: 8 } } + end + + # Shift in is probably the simplest form of an input register. + def part + @part ||= Dino::Components::Register::ShiftIn.new(options) + end + + def button + @button ||= Dino::Components::Button.new(board: part, pin: 0) + end + + def test_sets_byte_length + new_part = Dino::Components::Register::ShiftIn.new(options.merge(bytes: 2)) + assert_equal 2, new_part.bytes + end + + def test_state_setup + new_part = Dino::Components::Register::ShiftIn.new(options.merge(bytes: 3)) + assert_equal new_part.state, Array.new(24) {|i| 0} + assert_equal new_part.instance_variable_get(:@reading_pins), Array.new(24) { false } + assert_equal new_part.instance_variable_get(:@listening_pins), Array.new(24) { false } + refute_nil new_part.callbacks[:board_proxy] + end + + def test_updates_child_components + button + part.update("1") + assert button.high? + part.update("0") + assert button.low? + end + + def test_bit_array_conversion_and_state_update + part.update("127") + assert_equal [1,1,1,1,1,1,1,0], part.state + + new_part = Dino::Components::Register::ShiftIn.new(options.merge(bytes: 2)) + new_part.update("127,128") + assert_equal [1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,1], new_part.state + end + + def test_callbacks_get_bit_array + mock = MiniTest::Mock.new.expect :call, nil, [[1,1,1,1,1,1,1,0]] + part.add_callback do |data| + mock.call(data) + end + part.update("127") + mock.verify + end + + def test_read_proxy + # Stop automatic listening first. + button.stop + + # Give #read some value so it stops blocking. + Thread.new do + sleep while !part.callbacks[:force_update] + part.update("255") + end + + mock = MiniTest::Mock.new + mock.expect :call, nil + part.stub(:read, mock) do + button.read + end + mock.verify + end + + def test_listener_proxy + mock = MiniTest::Mock.new + mock.expect :call, nil + part.stub(:listen, mock) do + # Tells the register to start listening when it initializees. + button + + # Should not make a second listen call to the board. + button1 = Dino::Components::Button.new(board: part, pin: 1) + end + mock.verify + + # Should be listening to the lowest 2 bits now. + assert_equal part.instance_variable_get(:@listening_pins), [true, true, false, false, false, false, false, false] + end + + def test_stop_listener_proxy + button + + # Calling stop on a child part, when only it is listening, should call stop on the register too. + mock = MiniTest::Mock.new.expect :call, nil + part.stub(:stop, mock) do + button.stop + end + mock.verify + + # Check listener tracking is correct. + assert_equal part.instance_variable_get(:@listening_pins), Array.new(8) { false } + refute part.any_listening + end +end diff --git a/test/components/register/shift_in_test.rb b/test/components/register/shift_in_test.rb index 8bf9176c..e8045ed6 100644 --- a/test/components/register/shift_in_test.rb +++ b/test/components/register/shift_in_test.rb @@ -19,11 +19,6 @@ def test_proxies assert_equal Dino::Components::Register::Select, part.latch.class end - def test_byte_length - new_part = Dino::Components::Register::ShiftIn.new(options.merge(bytes: 2)) - assert_equal 2, new_part.bytes - end - def test_rising_clock assert_equal part.rising_clock, false new_part = Dino::Components::Register::ShiftIn.new(options.merge(rising_clock: :yes)) @@ -32,13 +27,13 @@ def test_rising_clock def test_read mock = MiniTest::Mock.new - mock.expect :call, nil, [8, 11, 12, 2], preclock_high: false, bit_order: :msbfirst - mock.expect :call, nil, [8, 11, 12, 2], preclock_high: true, bit_order: :lsbfirst + mock.expect :call, nil, [8, 11, 12, 2], preclock_high: false, bit_order: :lsbfirst + mock.expect :call, nil, [8, 11, 12, 2], preclock_high: true, bit_order: :msbfirst board.stub(:shift_read, mock) do new_part = Dino::Components::Register::ShiftIn.new options.merge(bytes: 2) new_part.read - new_part = Dino::Components::Register::ShiftIn.new options.merge(bytes: 2, rising_clock: true, bit_order: :lsbfirst) + new_part = Dino::Components::Register::ShiftIn.new options.merge(bytes: 2, rising_clock: true, bit_order: :msbfirst) new_part.read end mock.verify @@ -46,13 +41,13 @@ def test_read def test_listen mock = MiniTest::Mock.new - mock.expect :call, nil, [8, 11, 12, 2], preclock_high: false, bit_order: :msbfirst - mock.expect :call, nil, [8, 11, 12, 2], preclock_high: true, bit_order: :lsbfirst + mock.expect :call, nil, [8, 11, 12, 2], preclock_high: false, bit_order: :lsbfirst + mock.expect :call, nil, [8, 11, 12, 2], preclock_high: true, bit_order: :msbfirst board.stub(:shift_listen, mock) do new_part = Dino::Components::Register::ShiftIn.new options.merge(bytes: 2) new_part.listen - new_part = Dino::Components::Register::ShiftIn.new options.merge(bytes: 2, rising_clock: true, bit_order: :lsbfirst) + new_part = Dino::Components::Register::ShiftIn.new options.merge(bytes: 2, rising_clock: true, bit_order: :msbfirst) new_part.listen end mock.verify @@ -67,29 +62,11 @@ def test_stop mock.verify end - def test_callback_bubble + def test_gets_reads_through_latch_pin mock = MiniTest::Mock.new.expect :call, nil, ["127,255"] part.stub(:update, mock) do part.latch.update "127,255" end mock.verify end - - def test_bit_array_conversion - part.update("127") - assert_equal [0,1,1,1,1,1,1,1], part.state - - new_part = Dino::Components::Register::ShiftIn.new(options.merge(bytes: 2)) - new_part.update("127,255") - assert_equal [0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], new_part.state - end - - def test_callbacks_get_bit_array - mock = MiniTest::Mock.new.expect :call, nil, [[0,1,1,1,1,1,1,1]] - part.add_callback do |data| - mock.call(data) - end - part.update("127") - mock.verify - end end diff --git a/test/components/register/spi_in_test.rb b/test/components/register/spi_in_test.rb index 199b95f2..e07b96be 100644 --- a/test/components/register/spi_in_test.rb +++ b/test/components/register/spi_in_test.rb @@ -6,7 +6,7 @@ def board end def options - { board: board, pin: 9, frequency: 800000, spi_mode: 2, bit_order: :lsbfirst, bytes: 2 } + { board: board, pin: 9, frequency: 800000, spi_mode: 2, bit_order: :msbfirst, bytes: 2 } end def part @@ -17,19 +17,19 @@ def test_defaults part = Dino::Components::Register::SPIIn.new board: board, pin: 9 assert_equal part.frequency, 1000000 assert_equal part.spi_mode, 0 - assert_equal part.bit_order, :msbfirst + assert_equal part.bit_order, :lsbfirst assert_equal part.bytes, 1 end def test_options assert_equal part.frequency, 800000 assert_equal part.spi_mode, 2 - assert_equal part.bit_order, :lsbfirst + assert_equal part.bit_order, :msbfirst assert_equal part.bytes, 2 end def test_read - mock = MiniTest::Mock.new.expect :call, nil, [9], mode: 2, frequency: 800000, read: 2, bit_order: :lsbfirst + mock = MiniTest::Mock.new.expect :call, nil, [9], mode: 2, frequency: 800000, read: 2, bit_order: :msbfirst board.stub(:spi_transfer, mock) do part.read end @@ -37,7 +37,7 @@ def test_read end def test_listen - mock = MiniTest::Mock.new.expect :call, nil, [9], mode: 2, frequency: 800000, read: 2, bit_order: :lsbfirst + mock = MiniTest::Mock.new.expect :call, nil, [9], mode: 2, frequency: 800000, read: 2, bit_order: :msbfirst board.stub(:spi_listen, mock) do part.listen end From ae56c6cbcc2d497be4cdfbdabd222098d808c2e1 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 25 Feb 2023 18:36:37 -0400 Subject: [PATCH 287/296] Make input register bytes to bits conversion clearer --- lib/dino/components/register/input.rb | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/dino/components/register/input.rb b/lib/dino/components/register/input.rb index b4ecd657..6f9a211a 100644 --- a/lib/dino/components/register/input.rb +++ b/lib/dino/components/register/input.rb @@ -61,13 +61,22 @@ def update(message) end # - # Convert an array of cached bytes into an array of integer 1s and 0s - # Convenient for bubbling to proxy components, but is used for all callbacks. + # Convert array of bytes coming from the register into an array of bits + # to update self state and give to component callbacks. # def byte_array_to_bit_array(byte_array) - byte_array.map do |byte| - byte.to_i.to_s(2).rjust(8, "0").split("").reverse - end.flatten.map { |bit| bit.to_i } + # + # For each array element (1 byte): + # decimal number as string -> integer -> padded string of binary digits + # -> reverse digits from reading order to array indexing order + # -> join digits of all bytes into one string + # + binary_string = byte_array.map do |byte| + byte.to_i.to_s(2).rjust(8, "0").reverse + end.join + + # Split the digits out of the string into individual integers. + bits = binary_string.split("").map { |bit| bit.to_i } end def read From 2bf7443ceabbca3156e6ac94507b8da18d50e8f4 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 25 Feb 2023 19:41:19 -0400 Subject: [PATCH 288/296] Tests and fixes for output registers. Default to MSBFIRST Bit order was reversed for these, like input registers, but byte order was also wrong. MS byte always first now. --- examples/register/shift_in.rb | 5 +- examples/register/shift_out.rb | 4 +- examples/register/shift_ssd.rb | 4 +- examples/register/spi_combined.rb | 17 ++--- examples/register/spi_in.rb | 6 +- examples/register/spi_ssd.rb | 3 +- lib/dino/api/shift_io.rb | 8 ++- lib/dino/components/register/output.rb | 22 ++++-- lib/dino/components/register/shift_out.rb | 4 +- lib/dino/components/register/spi_out.rb | 2 +- test/components/register/output_test.rb | 83 ++++++++++++++++++++++ test/components/register/shift_out_test.rb | 2 +- test/components/register/spi_out_test.rb | 10 +-- 13 files changed, 135 insertions(+), 35 deletions(-) create mode 100644 test/components/register/output_test.rb diff --git a/examples/register/shift_in.rb b/examples/register/shift_in.rb index ca68aa07..fc40d3cc 100644 --- a/examples/register/shift_in.rb +++ b/examples/register/shift_in.rb @@ -16,8 +16,9 @@ shift_register = Dino::Components::Register::ShiftIn.new board: board, pins: {latch: 10, data: 12, clock: 13}, - rising_clock: true, - bytes: 1 + rising_clock: true + # bit_order: :lsbfirst + # bytes: 1 button = Dino::Components::Button.new(pin: 0, board: shift_register) diff --git a/examples/register/shift_out.rb b/examples/register/shift_out.rb index 6f92daca..87e6ee73 100644 --- a/examples/register/shift_out.rb +++ b/examples/register/shift_out.rb @@ -11,8 +11,10 @@ board = Dino::Board.new(Dino::TxRx::Serial.new) register = Dino::Components::Register::ShiftOut.new board: board, pins: {latch: 9, data: 11, clock: 13} + # bit_order: :msbfirst # bytes: 1 - + # buffer_writes: true + # Write a single byte register.write(255) diff --git a/examples/register/shift_ssd.rb b/examples/register/shift_ssd.rb index a6b80a6e..31a77fa6 100644 --- a/examples/register/shift_ssd.rb +++ b/examples/register/shift_ssd.rb @@ -14,8 +14,10 @@ board = Dino::Board.new(Dino::TxRx::Serial.new) shift_register = Dino::Components::Register::ShiftOut.new board: board, - pins: {data: 11, clock: 13, latch: 9}, + pins: {data: 11, clock: 13, latch: 9} + # bit_order: :msbfirst # bytes: 1 + # buffer_writes: true ssd = Dino::Components::SSD.new board: shift_register, pins: { cathode: 0, a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7 } diff --git a/examples/register/spi_combined.rb b/examples/register/spi_combined.rb index e981e52b..1cca9cc8 100644 --- a/examples/register/spi_combined.rb +++ b/examples/register/spi_combined.rb @@ -8,21 +8,22 @@ board = Dino::Board.new(Dino::TxRx::Serial.new) output_register = Dino::Components::Register::SPIOut.new board: board, pin: 9 - # frequency: 3000000, - # spi_mode: 0, + # frequency: 1000000 + # spi_mode: 0 + # bit_order: :msbfirst # bytes: 1 - # bit_order: :lsbfirst + # buffer_writes: true ssd = Dino::Components::SSD.new board: output_register, pins: { cathode: 0, a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7 } input_register = Dino::Components::Register::SPIIn.new board: board, - pin: 8, - spi_mode: 0, - frequency: 3000000 - # bytes: 1 + pin: 8 + # frequency: 1000000 + # spi_mode: 0 # bit_order: :lsbfirst - + # bytes: 1 + button = Dino::Components::Button.new(pin: 0, board: input_register) button.down { puts "down"} diff --git a/examples/register/spi_in.rb b/examples/register/spi_in.rb index bc56d747..e70b88e0 100644 --- a/examples/register/spi_in.rb +++ b/examples/register/spi_in.rb @@ -24,10 +24,10 @@ # Tested with CD4021B register, which is LSBFIRST, but needs SPI mode 0 on some boards and 2 on others. shift_register = Dino::Components::Register::SPIIn.new board: board, - pin: 8, - spi_mode: 2, - # bit_order: :lsbfirst + pin: 8 # frequency: 1000000 + # spi_mode: 0 + # bit_order: :lsbfirst # bytes: 1 button = Dino::Components::Button.new(pin: 0, board: shift_register) diff --git a/examples/register/spi_ssd.rb b/examples/register/spi_ssd.rb index 1d01a076..16050190 100644 --- a/examples/register/spi_ssd.rb +++ b/examples/register/spi_ssd.rb @@ -26,7 +26,8 @@ # frequency: 3000000, # spi_mode: 0, # bytes: 1 - # bit_order: :lsbfirst + # bit_order: :msbfirst + # buffer_writes: true ssd = Dino::Components::SSD.new board: shift_register, pins: { cathode: 0, a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7 } diff --git a/lib/dino/api/shift_io.rb b/lib/dino/api/shift_io.rb index 55f8a672..a7130796 100644 --- a/lib/dino/api/shift_io.rb +++ b/lib/dino/api/shift_io.rb @@ -10,12 +10,11 @@ def shift_settings(data, clock, bit_order=:lsbfirst, preclock_high=false) pack :uint8, [convert_pin(data), convert_pin(clock), settings] end + # CMD = 21 def shift_write(latch, data, clock, bytes, options={}) settings = shift_settings(data, clock, options[:bit_order]) limit = aux_limit - settings.length - bytes = pack :uint8, - bytes, - max: limit + bytes = pack :uint8, bytes, max: limit write Message.encode command: 21, pin: convert_pin(latch), @@ -23,6 +22,7 @@ def shift_write(latch, data, clock, bytes, options={}) aux_message: settings + bytes end + # CMD = 22 def shift_read(latch, data, clock, num_bytes, options={}) settings = shift_settings(data, clock, options[:bit_order], options[:preclock_high]) write Message.encode command: 22, @@ -31,6 +31,7 @@ def shift_read(latch, data, clock, num_bytes, options={}) aux_message: settings end + # CMD = 23 def shift_listen(latch, data, clock, num_bytes, options={}) settings = shift_settings(data, clock, options[:bit_order], options[:preclock_high]) write Message.encode command: 23, @@ -39,6 +40,7 @@ def shift_listen(latch, data, clock, num_bytes, options={}) aux_message: settings end + # CMD = 24 def shift_stop(latch) write Message.encode command: 24, pin: convert_pin(latch) end diff --git a/lib/dino/components/register/output.rb b/lib/dino/components/register/output.rb index 695a514b..03e1f06a 100644 --- a/lib/dino/components/register/output.rb +++ b/lib/dino/components/register/output.rb @@ -26,7 +26,9 @@ def before_initialize(options={}) # have passed since this object last got input. Better for things like SSDs # where many bits change in sequence, but not at exactly the same time. # - @write_delay = options[:write_delay] || 0.005 + @buffer_writes = true + @buffer_writes = false if options[:buffer_writes] == false + @write_delay = options[:write_delay] || 0.001 end def after_initialize(options={}) @@ -44,8 +46,8 @@ def write # include Mixins::BoardProxy def digital_write(pin, value) - state[pin] = value - delayed_write(state) + state[pin] = value # Might not be atomic? + @buffer_writes ? write_buffered(state) : write_state end def digital_read(pin) @@ -57,7 +59,7 @@ def digital_read(pin) # Lets us catch multiple changed bits, like when hosting an SSD. # include Mixins::Threaded - def delayed_write(old_state) + def write_buffered(old_state) threaded do sleep @write_delay # Keep delaying if state has changed. @@ -71,9 +73,15 @@ def delayed_write(old_state) def write_state bytes = [] state.each_slice(8) do |slice| - # Convert nil to 0 to ensure bit order is consistent. - zeroed = slice.map { |bit| bit.to_i }.join.to_i(2) - bytes << zeroed + # Convert nils in the slice to zero. + zeroed = slice.map { |bit| bit.to_i } + + # Each slice is 8 bits of a byte, with the lowest on the left. + # Reverse to reading order (lowest right) then join into string, and convert to integer. + byte = zeroed.reverse.join.to_i(2) + + # Pack bytes in reverse order. + bytes.unshift byte end write(bytes) end diff --git a/lib/dino/components/register/shift_out.rb b/lib/dino/components/register/shift_out.rb index ef1ab19d..2d271b2f 100644 --- a/lib/dino/components/register/shift_out.rb +++ b/lib/dino/components/register/shift_out.rb @@ -17,13 +17,13 @@ class ShiftOut def before_initialize(options={}) super(options) - self.bit_order = options[:bit_order] || :lsbfirst + self.bit_order = options[:bit_order] || :msbfirst end attr_accessor :bit_order def write(*bytes) - board.shift_write(latch.pin, data.pin, clock.pin, bytes) + board.shift_write(latch.pin, data.pin, clock.pin, bytes, bit_order: bit_order) end end end diff --git a/lib/dino/components/register/spi_out.rb b/lib/dino/components/register/spi_out.rb index 3740429c..dbba7cf7 100644 --- a/lib/dino/components/register/spi_out.rb +++ b/lib/dino/components/register/spi_out.rb @@ -16,7 +16,7 @@ def before_initialize(options) super(options) @spi_mode = options[:spi_mode] || 0 @frequency = options[:frequency] || 1000000 - @bit_order = options[:bit_order] || :lsbfirst + @bit_order = options[:bit_order] || :msbfirst end def write(*bytes) diff --git a/test/components/register/output_test.rb b/test/components/register/output_test.rb new file mode 100644 index 00000000..039cf3ab --- /dev/null +++ b/test/components/register/output_test.rb @@ -0,0 +1,83 @@ +require 'test_helper' + +class OutputRegisterTest < Minitest::Test + def board + @board ||= BoardMock.new + end + + def options + { board: board, pins: { clock: 12, data: 11, latch: 8 } } + end + + def part + @part ||= Dino::Components::Register::ShiftOut.new(options) + end + + def led + @led ||= Dino::Components::Led.new(board: part, pin: 0) + end + + def test_sets_byte_length + new_part = Dino::Components::Register::ShiftOut.new(options.merge(bytes: 2)) + assert_equal 2, new_part.bytes + end + + def test_state_setup + new_part = Dino::Components::Register::ShiftOut.new(options.merge(bytes: 3)) + assert_equal new_part.state, Array.new(24) {|i| 0} + assert_equal new_part.instance_variable_get(:@write_delay), 0.001 + assert_equal new_part.instance_variable_get(:@buffer_writes), true + end + + def test_write_buffering_control + new_part = Dino::Components::Register::ShiftOut.new(options.merge(bytes: 3, buffer_writes: false, write_delay: 0.5)) + assert_equal new_part.instance_variable_get(:@write_delay), 0.5 + assert_equal new_part.instance_variable_get(:@buffer_writes), false + end + + def test_updates_and_writes_state_for_children + led + + mock = MiniTest::Mock.new.expect :call, nil, [[1]] + part.stub(:write, mock) do + led.on + sleep 0.002 + end + mock.verify + + assert_equal part.state, [1,0,0,0,0,0,0,0] + end + + def test_implements_digital_read_for_children + led + + mock = MiniTest::Mock.new.expect :call, nil, [0] + part.stub(:digital_read, mock) do + led.board.digital_read(led.pin) + end + mock.verify + end + + def test_bit_and_byte_orders_correct + part.instance_variable_set(:@bytes, 2) + bit_array = "0101010100001111".split("") + + mock = MiniTest::Mock.new + mock.expect :call, nil, [[0b11110000, 0b10101010]] + part.stub(:write, mock) do + part.instance_variable_set(:@state, bit_array) + part.write_state + sleep 0.002 + end + mock.verify + + assert_equal part.state, bit_array + end + + def test_disable_buffering + part.instance_variable_set(:@buffer_writes, false) + part.instance_variable_set(:@write_delay, 1) + led.on + assert_nil part.thread + end +end diff --git a/test/components/register/shift_out_test.rb b/test/components/register/shift_out_test.rb index ff6b5ffd..ae1b8860 100644 --- a/test/components/register/shift_out_test.rb +++ b/test/components/register/shift_out_test.rb @@ -21,7 +21,7 @@ def test_proxies def test_write new_part = Dino::Components::Register::ShiftOut.new(options.merge(bytes: 2)) - mock = MiniTest::Mock.new.expect :call, nil, [8, 11, 12, [255,127]] + mock = MiniTest::Mock.new.expect :call, nil, [8, 11, 12, [255,127]], bit_order: :msbfirst board.stub(:shift_write, mock) do new_part.write(255,127) end diff --git a/test/components/register/spi_out_test.rb b/test/components/register/spi_out_test.rb index 6a2655f1..3208d0cc 100644 --- a/test/components/register/spi_out_test.rb +++ b/test/components/register/spi_out_test.rb @@ -6,7 +6,7 @@ def board end def options - { board: board, pin: 9, frequency: 800000, spi_mode: 2, bit_order: :msbfirst } + { board: board, pin: 9, frequency: 800000, spi_mode: 2, bit_order: :lsbfirst } end def part @@ -17,19 +17,19 @@ def test_defaults part = Dino::Components::Register::SPIOut.new board: board, pin: 9 assert_equal part.frequency, 1000000 assert_equal part.spi_mode, 0 - assert_equal part.bit_order, :lsbfirst + assert_equal part.bit_order, :msbfirst end def test_options assert_equal part.frequency, 800000 assert_equal part.spi_mode, 2 - assert_equal part.bit_order, :msbfirst + assert_equal part.bit_order, :lsbfirst end def test_write mock = MiniTest::Mock.new - mock.expect :call, nil, [9], mode: 2, frequency: 800000, write: [0], bit_order: :msbfirst - mock.expect :call, nil, [9], mode: 2, frequency: 800000, write: [255,127], bit_order: :msbfirst + mock.expect :call, nil, [9], mode: 2, frequency: 800000, write: [0], bit_order: :lsbfirst + mock.expect :call, nil, [9], mode: 2, frequency: 800000, write: [255,127], bit_order: :lsbfirst board.stub(:spi_transfer, mock) do part.write(255,127) end From 1d5730f0329fda65bdbcdfc3c10a214e8fedb004 Mon Sep 17 00:00:00 2001 From: vickash Date: Sat, 25 Feb 2023 20:50:09 -0400 Subject: [PATCH 289/296] Switch input register default back to MSBFIRST This should finally be correct. MSBFIRST per byte, but LSBYTE FIRST --- examples/register/shift_in.rb | 2 +- examples/register/shift_out.rb | 2 +- examples/register/spi_combined.rb | 2 +- examples/register/spi_in.rb | 2 +- lib/dino/components/register/shift_in.rb | 2 +- lib/dino/components/register/spi_in.rb | 2 +- test/components/register/shift_in_test.rb | 12 ++++++------ test/components/register/spi_in_test.rb | 10 +++++----- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/examples/register/shift_in.rb b/examples/register/shift_in.rb index fc40d3cc..b179e9d0 100644 --- a/examples/register/shift_in.rb +++ b/examples/register/shift_in.rb @@ -17,7 +17,7 @@ shift_register = Dino::Components::Register::ShiftIn.new board: board, pins: {latch: 10, data: 12, clock: 13}, rising_clock: true - # bit_order: :lsbfirst + # bit_order: :msbfirst # bytes: 1 button = Dino::Components::Button.new(pin: 0, board: shift_register) diff --git a/examples/register/shift_out.rb b/examples/register/shift_out.rb index 87e6ee73..9439232b 100644 --- a/examples/register/shift_out.rb +++ b/examples/register/shift_out.rb @@ -14,7 +14,7 @@ # bit_order: :msbfirst # bytes: 1 # buffer_writes: true - + # Write a single byte register.write(255) diff --git a/examples/register/spi_combined.rb b/examples/register/spi_combined.rb index 1cca9cc8..262ddeec 100644 --- a/examples/register/spi_combined.rb +++ b/examples/register/spi_combined.rb @@ -21,7 +21,7 @@ pin: 8 # frequency: 1000000 # spi_mode: 0 - # bit_order: :lsbfirst + # bit_order: :msbfirst # bytes: 1 button = Dino::Components::Button.new(pin: 0, board: input_register) diff --git a/examples/register/spi_in.rb b/examples/register/spi_in.rb index e70b88e0..15fcf4bd 100644 --- a/examples/register/spi_in.rb +++ b/examples/register/spi_in.rb @@ -27,7 +27,7 @@ pin: 8 # frequency: 1000000 # spi_mode: 0 - # bit_order: :lsbfirst + # bit_order: :msbfirst # bytes: 1 button = Dino::Components::Button.new(pin: 0, board: shift_register) diff --git a/lib/dino/components/register/shift_in.rb b/lib/dino/components/register/shift_in.rb index 6fd6bd1d..0b5d208b 100644 --- a/lib/dino/components/register/shift_in.rb +++ b/lib/dino/components/register/shift_in.rb @@ -18,7 +18,7 @@ class ShiftIn def before_initialize(options={}) super(options) self.rising_clock = options[:rising_clock] || false - self.bit_order = options[:bit_order] || :lsbfirst + self.bit_order = options[:bit_order] || :msbfirst end def after_initialize(options={}) diff --git a/lib/dino/components/register/spi_in.rb b/lib/dino/components/register/spi_in.rb index 5c28fc28..586b7ec0 100644 --- a/lib/dino/components/register/spi_in.rb +++ b/lib/dino/components/register/spi_in.rb @@ -16,7 +16,7 @@ def initialize(options) super(options) @spi_mode = options[:spi_mode] || 0 @frequency = options[:frequency] || 1000000 - @bit_order = options[:bit_order] || :lsbfirst + @bit_order = options[:bit_order] || :msbfirst end def read diff --git a/test/components/register/shift_in_test.rb b/test/components/register/shift_in_test.rb index e8045ed6..646d8397 100644 --- a/test/components/register/shift_in_test.rb +++ b/test/components/register/shift_in_test.rb @@ -27,13 +27,13 @@ def test_rising_clock def test_read mock = MiniTest::Mock.new - mock.expect :call, nil, [8, 11, 12, 2], preclock_high: false, bit_order: :lsbfirst - mock.expect :call, nil, [8, 11, 12, 2], preclock_high: true, bit_order: :msbfirst + mock.expect :call, nil, [8, 11, 12, 2], preclock_high: false, bit_order: :msbfirst + mock.expect :call, nil, [8, 11, 12, 2], preclock_high: true, bit_order: :lsbfirst board.stub(:shift_read, mock) do new_part = Dino::Components::Register::ShiftIn.new options.merge(bytes: 2) new_part.read - new_part = Dino::Components::Register::ShiftIn.new options.merge(bytes: 2, rising_clock: true, bit_order: :msbfirst) + new_part = Dino::Components::Register::ShiftIn.new options.merge(bytes: 2, rising_clock: true, bit_order: :lsbfirst) new_part.read end mock.verify @@ -41,13 +41,13 @@ def test_read def test_listen mock = MiniTest::Mock.new - mock.expect :call, nil, [8, 11, 12, 2], preclock_high: false, bit_order: :lsbfirst - mock.expect :call, nil, [8, 11, 12, 2], preclock_high: true, bit_order: :msbfirst + mock.expect :call, nil, [8, 11, 12, 2], preclock_high: false, bit_order: :msbfirst + mock.expect :call, nil, [8, 11, 12, 2], preclock_high: true, bit_order: :lsbfirst board.stub(:shift_listen, mock) do new_part = Dino::Components::Register::ShiftIn.new options.merge(bytes: 2) new_part.listen - new_part = Dino::Components::Register::ShiftIn.new options.merge(bytes: 2, rising_clock: true, bit_order: :msbfirst) + new_part = Dino::Components::Register::ShiftIn.new options.merge(bytes: 2, rising_clock: true, bit_order: :lsbfirst) new_part.listen end mock.verify diff --git a/test/components/register/spi_in_test.rb b/test/components/register/spi_in_test.rb index e07b96be..199b95f2 100644 --- a/test/components/register/spi_in_test.rb +++ b/test/components/register/spi_in_test.rb @@ -6,7 +6,7 @@ def board end def options - { board: board, pin: 9, frequency: 800000, spi_mode: 2, bit_order: :msbfirst, bytes: 2 } + { board: board, pin: 9, frequency: 800000, spi_mode: 2, bit_order: :lsbfirst, bytes: 2 } end def part @@ -17,19 +17,19 @@ def test_defaults part = Dino::Components::Register::SPIIn.new board: board, pin: 9 assert_equal part.frequency, 1000000 assert_equal part.spi_mode, 0 - assert_equal part.bit_order, :lsbfirst + assert_equal part.bit_order, :msbfirst assert_equal part.bytes, 1 end def test_options assert_equal part.frequency, 800000 assert_equal part.spi_mode, 2 - assert_equal part.bit_order, :msbfirst + assert_equal part.bit_order, :lsbfirst assert_equal part.bytes, 2 end def test_read - mock = MiniTest::Mock.new.expect :call, nil, [9], mode: 2, frequency: 800000, read: 2, bit_order: :msbfirst + mock = MiniTest::Mock.new.expect :call, nil, [9], mode: 2, frequency: 800000, read: 2, bit_order: :lsbfirst board.stub(:spi_transfer, mock) do part.read end @@ -37,7 +37,7 @@ def test_read end def test_listen - mock = MiniTest::Mock.new.expect :call, nil, [9], mode: 2, frequency: 800000, read: 2, bit_order: :msbfirst + mock = MiniTest::Mock.new.expect :call, nil, [9], mode: 2, frequency: 800000, read: 2, bit_order: :lsbfirst board.stub(:spi_listen, mock) do part.listen end From f9282be94950a0199c3dd67025a0d0f5b7cc2a05 Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 26 Feb 2023 00:34:34 -0400 Subject: [PATCH 290/296] Finish OneWire tests --- HARDWARE.md | 1 + lib/dino/components/one_wire/bus.rb | 2 +- lib/dino/components/one_wire/helper.rb | 7 +- test/components/one_wire/bus_test.rb | 131 ++++++++++++++++++++ test/components/one_wire/enumerator_test.rb | 26 ++-- test/components/one_wire/helper_test.rb | 18 ++- test/components/one_wire/slave_test.rb | 2 - 7 files changed, 168 insertions(+), 19 deletions(-) create mode 100644 test/components/one_wire/bus_test.rb diff --git a/HARDWARE.md b/HARDWARE.md index 1c239448..7da24340 100644 --- a/HARDWARE.md +++ b/HARDWARE.md @@ -66,6 +66,7 @@ | Hardware Serial | :heart: | Hardware | - | - | For boards with native USB and UARTs | Maxim OneWire | :green_heart: | Software | 0.12.0 | `OneWire::Bus` | No overdrive support | Infrared Emitter | :green_heart: | Software | 0.12.0 | `IREmitter` | Library on Board +| Infrared Receiver| :red_heart: | Software | - | - | Doable with existing library ### Generic Components diff --git a/lib/dino/components/one_wire/bus.rb b/lib/dino/components/one_wire/bus.rb index 881e37a5..1c9bd2ec 100644 --- a/lib/dino/components/one_wire/bus.rb +++ b/lib/dino/components/one_wire/bus.rb @@ -16,7 +16,7 @@ def after_initialize(options = {}) def read_power_supply mutex.synchronize do # Without driving low first, results are inconsistent. - board.set_pin_mode(self.pin, :out) + board.set_pin_mode(self.pin, :output) board.digital_write(self.pin, board.low) sleep 0.1 diff --git a/lib/dino/components/one_wire/helper.rb b/lib/dino/components/one_wire/helper.rb index b4fcbd29..38df0112 100644 --- a/lib/dino/components/one_wire/helper.rb +++ b/lib/dino/components/one_wire/helper.rb @@ -7,6 +7,11 @@ def self.address_to_bytes(address) end def self.crc_check(data) + calculated, received = self.calculate_crc(data) + calculated == received + end + + def self.calculate_crc(data) if data.class == Integer bytes = address_to_bytes(data) else @@ -22,7 +27,7 @@ def self.crc_check(data) crc = crc | (xor * (2 ** 7)) end end - crc == bytes.last + [crc, bytes.last] end end end diff --git a/test/components/one_wire/bus_test.rb b/test/components/one_wire/bus_test.rb new file mode 100644 index 00000000..0e1c1b54 --- /dev/null +++ b/test/components/one_wire/bus_test.rb @@ -0,0 +1,131 @@ +require 'test_helper' + +class OneWireBusTest < MiniTest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= Dino::Components::OneWire::Bus.new(board: board, pin: 1) + end + + def test_read_power_supply_locks_mutex + mock = MiniTest::Mock.new.expect(:call, nil) + + part.mutex.stub(:synchronize, mock) do + part.read_power_supply + end + mock.verify + end + + def test_read_power_supply_sends_board_commands + board_mock = MiniTest::Mock.new + board_mock.expect(:set_pin_mode, nil, [part.pin, :output]) + board_mock.expect(:low, 0) + board_mock.expect(:digital_write, nil, [part.pin, 0]) + board_mock.expect(:one_wire_reset, nil, [part.pin, 0]) + board_mock.expect(:one_wire_write, nil, [part.pin, false, [0xCC, 0xB4]]) + + part.stub(:board, board_mock) do + part.read_power_supply + end + board_mock.verify + end + + def test_sets_parasite_power + mock = MiniTest::Mock.new + mock.expect(:call, 0, [1]) + mock.expect(:call, 255, [1]) + + part.stub(:read, mock) do + part.read_power_supply + assert part.parasite_power + + part.read_power_supply + refute part.parasite_power + end + mock.verify + end + + def test_device_present_in_mutex + mock = MiniTest::Mock.new.expect(:call, nil) + + part.mutex.stub(:synchronize, mock) do + part.device_present + end + mock.verify + end + + def inject_reading(reading) + Thread.new do + sleep while !part.callbacks[:read] + part.update(reading.to_s) + end + end + + def test_set_device_present + part + mock = MiniTest::Mock.new + mock.expect(:call, nil, [1]) + mock.expect(:call, nil, [1]) + + part.stub(:reset, mock) do + # Give 0 for first reading, device present + inject_reading(0) + assert part.device_present + + # Give 1 for second reading, no device + inject_reading(1) + refute part.device_present + end + mock.verify + end + + def test_pre_callback_filter + assert_equal part.pre_callback_filter("255,180,120"), [255, 180, 120] + assert_equal part.pre_callback_filter("127"), 127 + end + + def reset_test + mock = MiniTest::Mock.new + mock.expect(:call, [1, true]) + mock.expect(:call, [1]) + board.stub(:one_wire_reset, mock) do + part.reset(true) + part.reset + end + mock.verify + end + + def _read_test + mock = MiniTest::Mock.new.expect(:call, [1, 4]) + board.stub(:one_wire_read, mock) do + part._read(4) + end + mock.verify + end + + def write + mock = MiniTest::Mock.new + expect(:call, [1, true, [255, 177, 0x44]]) + mock = MiniTest::Mock.new.expect(:call, [1, true, [255, 177, 0x44]]) + mock = MiniTest::Mock.new.expect(:call, [1, true, [255, 177, 0x48]]) + mock = MiniTest::Mock.new.expect(:call, [1, false, [255, 177, 0x55]]) + mock = MiniTest::Mock.new.expect(:call, [1, false, [255, 177, 0x44]]) + + board.stub(:one_wire_write, mock) do + # Parasite power on and parasite power functions. + part.instance_variable_set(:@parasite_power, true) + part.write [255, 177, 0x44] + part.write [255, 177, 0x48] + + # Parasite power on and not parasite power functions. + part.write [255, 177, 0x55] + + # Parasite power off and would-be parasite power function. + part.instance_variable_set(:@parasite_power, false) + part.write [255, 177, 0x44] + end + mock.verify + end +end \ No newline at end of file diff --git a/test/components/one_wire/enumerator_test.rb b/test/components/one_wire/enumerator_test.rb index 04d69a86..a2d05d6b 100644 --- a/test/components/one_wire/enumerator_test.rb +++ b/test/components/one_wire/enumerator_test.rb @@ -1,12 +1,16 @@ require 'test_helper' -# State machine simulating a bus during address search. Initialize with n for -# n devices with random (CRC-invalid) addresses. Call #reset before each search. +# State machine simulating a bus during address search. Initialize with +# n devices with random addresses. Call #reset before each search. class BusSimulator def initialize(device_count) @devices = [] device_count.times do - @devices << { rom: rand(2**64), in_search: true } + rando = rand(2**56) + crc = Dino::Components::OneWire::Helper.calculate_crc(rando)[0] + crc = crc << 56 + rando = rando & crc + @devices << { rom: rando, in_search: true } end @devices = @devices.uniq @index = -1 @@ -96,7 +100,7 @@ module Dino module Components module OneWire - class Bus + class BusStub < Bus def read_power_supply @parasite_power = false end @@ -113,12 +117,6 @@ def _search(branch_mask) self.update(result) end end - - class Helper - def self.crc_check(bytes) - true - end - end end end end @@ -126,11 +124,11 @@ def self.crc_check(bytes) class OneWireEnumeratorTest < Minitest::Test def test_find_all_addresses # This gets slow with large number of devices. - bus_sim = BusSimulator.new(100) + bus_sim = BusSimulator.new(20) board_sim = BoardSimulator.new(bus_sim) - bus = Dino::Components::OneWire::Bus.new board: BoardMock.new, - pin: 1, - board_sim: board_sim + bus = Dino::Components::OneWire::BusStub.new board: BoardMock.new, + pin: 1, + board_sim: board_sim bus.search found_addresses = bus.found_devices.map { |d| d[:address] } diff --git a/test/components/one_wire/helper_test.rb b/test/components/one_wire/helper_test.rb index 152fe234..95e5e3c1 100644 --- a/test/components/one_wire/helper_test.rb +++ b/test/components/one_wire/helper_test.rb @@ -1,2 +1,18 @@ require 'test_helper' -# Test crc check. + +class OneWireHelper < Minitest::Test + + def test_with_valid_crc + assert Dino::Components::OneWire::Helper.crc_check(18086456125349333800) + assert Dino::Components::OneWire::Helper.crc_check([121, 117, 144, 185, 6, 165, 43, 26]) + end + + def test_with_invalid_crc + refute Dino::Components::OneWire::Helper.crc_check(18086456125349333801) + end + + def test_arbitrary_length_read + assert Dino::Components::OneWire::Helper.crc_check([181, 1, 75, 70, 127, 255, 11, 16, 163]) + refute Dino::Components::OneWire::Helper.crc_check([181, 1, 75, 70, 127, 255, 11, 16, 164]) + end +end diff --git a/test/components/one_wire/slave_test.rb b/test/components/one_wire/slave_test.rb index e8d49d96..e3c4e39d 100644 --- a/test/components/one_wire/slave_test.rb +++ b/test/components/one_wire/slave_test.rb @@ -10,8 +10,6 @@ def read(num_bytes) data << "," unless data.empty? data << "255" end - self.update(data) - self.pre_callback_filter(data) end end end From 3bc8be4e2973a781db93172cb9b124062c70e7a9 Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 26 Feb 2023 12:36:34 -0400 Subject: [PATCH 291/296] Update hook-up diagrams for first 4 examples --- examples/01-led/led.fzz | Bin 0 -> 2395 bytes examples/01-led/led.pdf | Bin 0 -> 367270 bytes examples/01-led/led.png | Bin 69321 -> 0 bytes examples/01-led/led.rb | 6 ++++-- examples/02-button/button.fzz | Bin 0 -> 4005 bytes examples/02-button/button.pdf | Bin 0 -> 446386 bytes examples/02-button/button.png | Bin 55959 -> 0 bytes examples/02-button/button.rb | 5 +++-- examples/03-potentiometer/potentiometer.fzz | Bin 0 -> 4457 bytes examples/03-potentiometer/potentiometer.pdf | Bin 0 -> 457715 bytes examples/03-potentiometer/potentiometer.rb | 2 +- examples/04-pwm_led/pwm_led.fzz | Bin 0 -> 4458 bytes examples/04-pwm_led/pwm_led.pdf | Bin 0 -> 457670 bytes examples/04-pwm_led/pwm_led.rb | 2 +- 14 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 examples/01-led/led.fzz create mode 100644 examples/01-led/led.pdf delete mode 100644 examples/01-led/led.png create mode 100644 examples/02-button/button.fzz create mode 100644 examples/02-button/button.pdf delete mode 100644 examples/02-button/button.png create mode 100644 examples/03-potentiometer/potentiometer.fzz create mode 100644 examples/03-potentiometer/potentiometer.pdf create mode 100644 examples/04-pwm_led/pwm_led.fzz create mode 100644 examples/04-pwm_led/pwm_led.pdf diff --git a/examples/01-led/led.fzz b/examples/01-led/led.fzz new file mode 100644 index 0000000000000000000000000000000000000000..d38a69be2c6b9ed449117cd47a2e0d9edce9f29d GIT binary patch literal 2395 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�yJ`eH=-+Bu9x+l`xy0(hK7t72)2V^sjE*E=i?f2 z+`i)*hnQI%Wz9d&Tw1QJ2;ldF>l@TO>5aXXk7<^i(6n z2&X&nroG%!p4F_n8d5vlo!U1>-l28N)SWa#@@pkKkycgD&Uz0|ZD9}hmyz(5f1UmB zhGG6`2qdGVe19*&ge35Xq@glA=j$_f zT_S-esB+s>qlCpSG$h{mLWDCx&8YisxrE%9t^J>ALoA(VKo6}zQaQ4lCK33nj1v<_ z|Jy=`XliEskzSUndpx(kQ>ABfjCICOBj-j`I0@n)0Nvjo1f-oJoc0>8A z5{TJ0^{!^a<2F^R!(^8hmpo{s-jJG{dAptp8K^+xKB!Qc@9XZfZ>x2{4jdn{5M`@1 ziAJckZg?I&^P6lMcQA^=)xJbI%r0nftlR5ut!CtZ@B+VUHgKn#o)?|`N!dqFekJi0 z+hzJtyX{PJD;LITpTPxGsqu4^1xF z#c$)U@h9NrrM>)gXCg;`>!|>-xVb6j4ba1cLERpyybhjRxEKO^Qr`pNVcAtJ1Bq{# z-~)v)&3C29uW^kpcsPiIMVV+1v)j%S!dJ@=uJ(4Kl-Y<4{~+G-W>Dvp1N>fJ)}^=m zc5dGJJtoZT`A_atHw};C^`F4VqdWN@F?NRsw%yO@NAZGtY+oy|Jz_Nf*(CwrQY$=k z{*lOc6sSy=IP`Zw5G3oB&RmIniL;BdMGA~yh(`9=!ptZ7A**7txqp9UZ;Rk$?80de zJm~o@KJzTf$#@<_U97S_JYJ|Y2nypBvNpP4?5ap31Zlspa6e&RS_#~~No#s??Dlg80QqrVzyg4&?(7Wc*fv%G9Jp?*)4&_6~YvfE2IWgoX zeW#$hgPMybh2c4`M;EV~F8D{4$tGz}V1|R8jmZF6+4Sa%h{^cW!F{)tkRi#tjDJ5zjY(ncuj%G%&&hmTIUyxhh0=rDr`f4}Qi?yda*1h0Y0C)sauVGV=FI z@(9k~Nlf{<>)}s=ibp*a9Ke+oe51ABH}x8Il9ETX-;iKH6F1^mWXw!m_xNF6 zw#u6Lmhr&h(9(cMfAd6_up?(8)EY!N}-6OD;sjsbjXed+M1^L~i z=ux7$xRHPOXc>9cE89pb)(;^h85sC82n=_GgPrhr^7MTSGq~&(GBv3*Zi3F!31qIS2qqCI{v z6REk{$F#K`hk9P(+&TSHknTF5?%6pcC%^BiyM;7=(v$z%o=M5i@_F}He%Z+Fd8CD= zFs|-Rh7J*7;IGdfSe`L0@7>PHpfZ>XldsN0Jf5FG+HB)vZ%V{zp*5Rs0^*T+&86DC zTeH?x1|e&gM_#}Ym=i=K=ELxMk=cQ>PUijKD}sjkJubI~E3AG&F@ip|omPtUJhwK% zu=1p>=MGudZPsk?;7zfB?8LuT*epn@O$n&uTpWhS?Q1$3+=JXNE>whXmTdSTQ*V(R zhaC&JMfF*9eLbfY>5PIObEz;#kg+OW@{8nw=6^b?Ih?r$PaODJJn*8V&2M5a0KPV` zSFvv@(`TI)3)ucHf2c$G6dy{K`C*HsoY0kxGB~>X8*LtEY2;i*5dF@Ko&IoSeU35A z5;by1IYYZMQv~UcaqbeoTB+W-bX&c#SaQ2Q2xcZxiT*41_emZ|s*W)O#X^(wA#Bgi{hBXJ>nZKK%u;?vMOeVU`}cu!&hp=PHQavr=t(mE zNS&C8ahhhfnA5cwHCsaiuX^0tP(55NhzNe>AD`3oRBQNS>g3Nwo$kQnpY1xTx4AS_ z(iZ(}2cqPDbv^`{`ew_zsoiq_3G4nmdnIpm#p|FmvHr8u9aiMM5UuAOku$1NOtJUz zNwqg()|%#0JVShE&ku4(v^ydcByq?QxxhPtKEeMnRlP_0ROQEkbuw>_`> zr#)&2n|E+X>AGfJtiRdm?@`7yH^`l16)F9mU8(5G2_V{TYlyBALPDmqaK_)EOeg#L z!#oul^L8;@H22ePcDt)pv}(@ztn)2k>X-3Py4&s6t@pXx<8$yPh0eQ;NE#~Xyc8tv z6A>pc(&A1@-*R#J!|v=&fcuy(oN~F-EhH|+#Ez5`Tz^2|#2`##4rLg(_cJ z0nU||TUWU21Y#B59pPo{rCj#=Ow#;hZpS#i_R7%VcP@c;YuphhA_WK9Zl|*q(`z}3 zey=}!s-0OS7{L_j7XU5Gf)u$MjaBV4a_{D}66z5q)WXx6MVW5saWMV9Ip%0;s&y?B zam`iinlI~jJ@g^M_hD02Y{f~U+Cd=1#sRN%#AMQW1+}qL)V~@LsbJO2Ua%C;mR6W5 zcw~6mR^wxDzPC^Cmcz^S>q*~$<=%ka<=)myX<63O_XFjUgAB`l{${-2gzroIGki8{ z?w6&6R@u@nFEk$rh(zP}KL4q1D6qq%>{c=$i)8@+A_#>IEyogshAow#c?@~prNv+f z|0@;^P?&Vu8`z4p?VJy#G<|IN=`g4w1>jR%lhaF`u&PEJ1k%A*&W>{v2U^!EpjDmy zJ}IFruR!^!gJ9FmM2RC_#hLX&!-*sL9QQn$lRW=hc#_(51`xaO zet7u3a{tmz>M@`(ZETghbm0m((L$`GYiSm%T5$XG8YG(c?bz)R%gap_uz10luc%O) zHvvnwQmNdl&jv-MXy5L!mE1a=kL<9?lDerHc{C=DR1ELzbvHjUA~3!-G*e3rOu?vM z0}9jBLR+xnIY=N`E#c<7qBhQlqO^8h-Z5*CcdajLzYN|k)nU2R-oBmGHqftYqL{e@ z_3WfMkq$%Hmn&+Onh_b%1M>?>5d|*|yxjCw0H8w~kDmv{ud5$=oBjC)52JB{sMB1T z$#^PVrx&XTNqAh4foU@@-AP9Mi_l;tg+7S90@sj%;+RSHJinMI3o} zwnuf~r211CW3hRw4A@EQ+2FTEb+F+^IVn;~0Mw16bKRN7ExNGJKv4?4n5vKAvL>IG zzB$G1dfg)S`&vfD7ORE6^fYRXdWFitYd@8uKtw}fKCrCGy7DZNxt;pH8`%*X-%K4{ z9lr(RIZagLw)q?i4XeoYx|Il+o}fLN!+?r`dlzpQMm26HFeu%kO2zXJ0JGj$x%cCp zu8%i6WlQ@OFY4#d;e5B)U@e`i`zZ1(!NRs%`7n`m#U!ux>^!2nZ4-(3V^*V!ZIk!I zA^)6a#BV4@$2o8#m*KrnlHw;Bg~felE|w$9>&HhZAR`m{ug!s>E-RRU;U^Q;Z{*zQSL;PLVkdf{Pn-CoM@jjHW*$%-CQqM#kP@^CJr4v529aF%h!1 z?rfH|6MwF7RLKaA$^wf8;n(zeIUGR)bHa%)gNLY2`Xc)ll?16nj^7sQaTP;Z4(5gj zoS>RI>gYIA^7fg+PfM2mh)iX;K#{I(NfOKn%$s2>K+H21pyNPDM3%8OR_P>oJ_U9l zdevY6xLv1_o>C#Y(p_LzR7&rZ1bHcXhQk-eGM!Mwbfn{I}C)wcQEYd6KJ+l#&%+)%P( z#d_StpT?cO8);KRZy)_bv{vpPtA6CxB9_KeHroS7W$rSsO;-81N1}(|9ofATJ6?k8 z;^_R7byL${9^wXEMzPBNZE6%bWDcOg;VJJ%YGk)>OL*Ph!rbhGh1$yc zE7nBb$HOt09S?WmCHR(%j6h+A^w&iiei?{u>Ae~4kqgyWi={qi9*7O7b{Mbvs{++9 z-IxEgdb^S-eZbey3upJiDb8QUvstlY}pRX^+;hJ2(rv@Xyo+qT#i}f8aUBi}$ zaFLmu*g&E*x%+lhg^*GeFkFN1PXZnx0Vls1jm1<1n)Nu=-Av_>Zg189t<{!G1OlV1 zoWd#X>Vfe=V->IeFT(lV`^fLeP=UWGCHE$GKQ?m>PB+jM;s<+n^c#5B!KbHXwf0tT zgnDrJEMCp`UVn1^Gi{v;;qQS_3-awpAXCG+%*jL@kiG^AkJVx(VIVZt5<4i2$UL$C z*nq7E*3PQOZIaY=?ag?}X&;6)DN`y!_~-@KY2A@_Hefs_QZcJP{g@$?S8T8Ic31_Wo7PA;%^7OUN;B)X!B;$hh61ndK>V zlldpib@r+?rwTlWYnuJ~fQAwy$%p^S$CJbm!s+=qXCuX0pmds0jHL5&&sZqUE_wSG z(ar)~xI$Any3x{+&ARRWc5SIX(dhkZ4tW>$DuPLDCVc3?UYkkw_n_+OC#xw0(ej8d z)0D>}77+@*0d4*G2Dcn%KLfMU&{t;A`NR(IaYl{QbYYBPQFtQW1qXlUA&Otq zOVFZVMpdw*B>O!61$~mjS5F*4zMQye@#ZngCY|b1hyfqpo3PHS2+X@fr*C!HvdS8$ ziT_INtXL!eOP5zKll;OU6cR~uKavcI32^GHHUEXMtjJghw4a7pf3sQl7H5lFljLT#qN==LvlC!0Ww+_Zl}h;(e_=D~>F`g06u_ zYm^#gQXaV4$OE_Bz2Z6X>!On#ZW|BV_j;}l_YjrjD(vAMjbdEXGpIMO?~XY`tvJ6` z8TqZ@%BnRuEBBvzkJ=W7tsK53!QGzYGi7~J^6g!w%Qhu+(EHZ$OmLn@qTvEA^ab^l z`aK^H;avD)WUtdj^s>x6+~+S2!XV8!YI!JsLRFhc>Xfk6v()HMu@ewA(Ja$4G@Yd1 zutE{C8Xl3YDH!mOkWyl7kDV;Ld@uPBVH&;G^I-6I8?@N<7(4dY!4+Js?o#tLPv4*P z=IZDP|bbtb4{wVSpB!>RTY>^1(9Q?;jB?+?wO_d>WluS%=fmC zpxR9l^-_L3G?=vaPS~$eftixjC@P-s)-?7#nInp7ttnx;f@yUeN^9Q|fb5s0FkdCK z!bZV%9zRTL7N|}+uX%Fk=%&R!+|lmzeG)sstg3G?$jXKh;a7Jiec`cIp-;depxR!Y zt*yHlOs+EHQpqd$+Rq$QTp*i$&v7W$rWd6<&@bDXc9Xt|2poP^C?yjW^&KK_nK(F_pCL!a7TgR7T=+8=5kCQHuW8^TbpV*z`V@g|zSfxmJz!#NW2Sy;oP zznFW2X&7CMo5-HlDkwyR8M6r-YS2(;&1A|r6CVHk1WzQJrh*aWwfR?|4B0s5pcbJn`vxl-IJ z`mIF{JAlB#F<#)H;X2`%-uxPSL*SY9houZGmu&_q&#??+!Uzp48THY`>!%K?)M`LN z6&M*dR~fGyP<{J$e~8J8Inn5GBmEJLH|OGwdgXslB!nx@gz6=m^1pdbJ35> zXl~K;A9JHMYBABU+j={7Wrf`H-4ao}x+2j~!RM|4>~pBlYfS@n$;E4xsgKJ?+Z&cW z9fy)IjXeHeHL;&xlg|G{t2q%aDH zt5kSKKaX+2I?M^)Em?X|SF{u&xKCE~I&guT%kIVZOpN9Y0Dq~-F@RpHipne!C3JLt z`C4=T1EZg#Xn`j+>cztzs(TnN?r!9FBjj(mHIlQoY1u`q^OeYK^ON`$cT?n}@0IAI z(WV5}r&2Ha#djTB1`L?Q-~aW>bnP4JX1=T079LoX<<+;Ez(OQ-x?0V3`^Q|0!mjSQ zb)Sw{ZB|^$eW`sVuDd%wz3hS3;nJ1t)ZusB z(IO?axqD+@PS1cj{1D(eitm6gOWySWqQA-cP_gcsBLm_o#7|GGfl!a~-eGjUf@NQ7`VGk79KD6-vc#QFI6>7R=jrydg)SozanIhFObut4X z$(_0E8TH5WVJR@Bgl$AzyA!7G#EN#;z11Edt$d|GM)zi0B!?07$nr{Ro zL$Z#{NuXySANPhk>e%hqH~BuB>37NGj%@I5qHG_ERH<$IS@JFw_2t3NsS_;#?HoyS zQ5!Y+v-CE+Ev&QlaKPtNOQhIF($}V7)sDKilZpu%XAVR$SoBY~dN%wo8M6#K0V3wk zk<^%SXxEq~M!SaSL)VQU*Pjnwy|g6LgWz!YmYKQpTT#OJ;B|&48b#^}s#&spaevV% z$q}!;VMQE*otui}m_kOiKWE%)eG)n!vLrPWq%aKSJvv;hxeDigheyoo$s06U)l}c{wqPV{_(SmHr%b$PG57mZ)Z}}QDk<<2P{~hRL3Nc2 z3o-R;a^jmNUXc+-y5nv8lbmaTWl^>BA;GJh7Jto}*$H#3_XnF}CD{+>s%>&p^_|^; zr>F71AK|0F#dK4|c<*%^g?O5q?ik_V74CAx5DaAqO{Ix=HeWTZ=LLTAlNDiSUlrii z%jR;s4VYYE`3Nz?x_1qYPJsFdb2GUqFu&60KD&tO3R<4Ry^fOpdbQTl3lfX&Yn=%Y zb7P4*fk!PLvP8+I4(~=VV^1pl?D|5o2bbrHHxh~NGZ7mxR0z9{?HY3Vq=D`YYx=j8 z_DLaZH@3^0USTK&H+usR9aKfxbVJjS?pd-#6UltGAB)1Oh2LeVkTBUs%Y%lMQyQC? zvFpLn*E(qr9MRflRI?K_<)4!<9*0{y+=Q^*KT&wlI3Avcl$YQi;s5cz$8W?Llwfy?TM4Rxd=&7YnbmlzBHe|-4=h8roqe2 z4gkrR^<6-P8(g;uCLO9ebA!0vLYNy=`)Ph_QLn5*1zeP(gs>eB3LfuBPxKFS5^*g~ z8yN4iu(aNKl5x%*1CD8g+7UzG1#<0M2Zwt z%ux_0e9`D#HSS;x@#wy}M&SsKV2UdEf>KteFS9*YWpR3pbn!p!9zzn$ede(ZEt0)| z;2tOoaiZ+f^rS*mIurAlrsJCp%``Ez5czcjt3b%3gx*03D1doi0BVn^&}4Nlnf9O* zJ-fH-kKYxRB41 zYi7p2lmEHH%EQx%*6rM`z{4UCM?o57XdZ@^6sp&`oE1&+l`WVKExo=)zhx*^`T|LN z<|avUPgADSMFaC(;s~ZIS)(NcA@?lMeYs-asawMYr zjz8NXRfZPDtbsAo_uDGAO)7Wns*kzUc+QjRR?i6>Hot?MclLTZ?%W;lRE=sfyMzda zHB4z(!e|BEz0C_#+VLluO=A4;-|hq-{yK4e6OsPg%(kOn^XPMVwS6=1N%Ttr_z>ZA z9n9K9_?5H8^KRUc;hE4g0F}LD)Gyk!V{25|4qW@)amH;JCM>7^oYxY4A*or%2XvI{ zhr6KLUlR(-cU~e4S`j#M&s}41d)Z$!Q2N6N?i(x1SKdnp36ngr++Kcf{u872mGj!D zO46s!^?acmZp0H`iWP>q9IM)OHBUL?;k-MM)Ouj&t5y$^KUOV! zBN`58=yxE`qA38?>6Rh2%bFY&qCTF@BtjS{6+6%xw6QRz>iIE*XlWCwm9-!^>a&Zt zE=o&C$cn)W>Xl9Y*@4%06zB@*b5{p*yGed{rH`Cf8 z`?QVG8x1wYoe6#sNzR)&HaTm{&!!Amm^;67uY(ISw#V`A#$0H<#J4o?wsWkQXFpM| zMkq(3S<@tVdL%0`!({`mGDucpf*JaYgd$sl`|j{8Ig$`jZZNrJT^)|5;(c+fC*lxb z(0nkOQ8oq?<;95o%Q$M`bt=d{Z0hCWa6_5=G<@u-C7I36%+}Lk0g)2Hsv@GdxvKS| z{0=lsMJPBvjldz&(c+uobIiPt<2W+^wxb}dARe4?!=?Mx)GvcJBq}3Z+l<&kN6{Lh!+3gP3aDlU+Blgc$kRdbj)HUZ|lzFrGlP3?7t*Xv%x_@}Qn@^8Mn zD~{u8IyY*sMehth=~mY3doq0^;^Vy`e*;@RJD^UX7JT!MNkJLmlW&9nksRtRDQ>Xd{@>ENQqpqlQvAII!hDnK$71@BEUHz z_7m_zgU38^=N0>jHD*W4A4Y3KB0ossP<6)H;CVAoPM>-zppz@c!RbTHlIL|7`hB&d zElmT`xrTlbobKl}AmJI#Y_=c{PAGR`%=#9D4jWKxXW3$Br& z+}^Ux9E^1XT~!k9Hxw1`yDvd_)oGoWNh z38Q&R=n2}{!l56plU*xhERk;Dp4vc>+|UH2nXqkg&Mdrxecdf*{ayuxHiSYCbM-#! z{>_}hbO~V+@GU3YtX;Y_rHm9-JyT6WkQ|MSD47eKY!#gCKwIa4GSEVkm-V=K1NB!) zlEWQvF#9oY8c@<-(DAU+2_p$6lsUta-a{&VBc0XXufv{LcwUn z2}b_L0dgWlmdc1v7f$sp&ztEO$Wosl$59%0RZMZzgpSUcHhzX4we`?|As|^q;p?&I zc)H#v7fLk86qE#g>n*7-Tygmw@X6_o5_MlILFAU(gEoM3Yd`MQJcA3fJ#NTsiM z3&z6K~9Yd9?^nn%(S5T;by$6D#P|wC}^&*hpg-)Gcj5e|j^?pc;zIYr+ z=G}4whUJ&Kjsuo+Vm*%Q;6P3ypYv0)o`9mZB?&r`Sbk?8v8{kJnmg?F1_QH+z2U5u z!K&v)&6W`>$E|j-ay$-lLs%$`f@`J-y4zwKYB9qF?<6wN2i>ERef2&jQv2zgg6&*P zMnKO7v~z2(rt(xc=LIMjZHrkVB#C@?ZyaCmDatC%vQ{WImUe`bnxK1Tz3Xth%)EJg zAQ1tP1f$S6{Pbc+@)DF($Qpu@Sk!5|aZ9I$&M8}^>fYU3XGnXrp&}q?g@Zm8(ZSUs z+QRyDakhPu-W1O-GD^#yTO6NQ2DQp>Lg>=e)0@k}274hkXI;*yvnxC^m~cd~^XhJ| zCNFS)HQ}%eIIKjj9QMCh;8N^jb=>hWQPP~Mh3|amFOsp6tE>suP<%F->*}*==QKFU z_TF~Fx`w-v`wro5#Jv`o3(McI&|tnLvDBGVll2^$r;fp1wv0m?S-2`wriW2hKIO}U zOL&M)PK#4&nN!fTIg6c?2x^|vFwIKFsN5K^DJtI~5nXAVtcIH<4D~H2>a+B|IhCP0 z?7cj>Mbn5nl*|PB!CoVJZZ3a*(E(gZ#~Ej|U-FmpsQmljX3H{4TBxXJOMErZ*eH?b zedatnh0_lc4l3sdDo8%ud+-_QZU>#i9enQ(cMMSP_u2aMG^`iBy%bi!kZzbPB$wu6 z0rP{r1F7p>JYcw?F~@OY-*oDFGa@a&=7UaT=Bhn{V-(FCMAMDE)po|AE7ecXNc1#V z0Xbi_aQ6`CBM{Bv7rAfvVkzMS>LOKUTualYXs^Xp^!#*zJn(Fs&frqEQUZ#1Ok{P- zz6qKKdbD#v0QA6apq+Yf=UI&S{qC?Qn%+w8cgJ4P^E!NYbb+ExGU9iK6|{5o6&5BF zmVU7DyCYee3ozo-D8dtq$-jB97Rc{7kQqb!?vYmH=ky`IwL3$PUe98{!(G7R*HU)B z=&WitzF>p9**J^c&D`0iZk@tiz^$r#+I<1{X7|0qINaBDqjr+uX6R9W7w|Z0aI)}S zGo?e{W-p2b$T-wwwtER}K9+gJQYNP$``HXOj`w=!?)HMS&3X5cz13EHO9wY7_rvN^ zh|H%JM(Y85Ki7rp{P5ApDhYoBlgd(|(YgX@yBT`wwn7^~yCg7_;#I$S}xVZ#v zl7*{CM((r;oy&yR9_*Fxh`aMPyeHu9SMO==N0?z=HL$Vy`M@cer*tOsvxi%0D zttGFZh{z)Yd8PoZwpxIEr{0d{F)u7pH-=632(?k3Qt) z=VzmrmHZWEE)J%m`9`)*2aOv2;{1K@S8#7}U(EfY`@Ug(P8!^7yyV=Of093uOmOkf zl!B%8Z^XT&8t|*xn3#V_ob_r!lrC0{$a|f++2Js2)UfC!PM;@4yWulJC&w9dG`|CU z7d1vl=$u1bP439|%>}pH-ur7Y7kC2L!RAL@IB3B={@e@$>U6iqGpc@u@KYCXsEv^{ zzAljs#yb~B={YB{aY8|nE&ek%n}S&Ah!Gw9W&veEEl&)*6z~pa?sDPZQW4Ny%}%Zi z6OGV`8(z^HhcL(D-JA_OvPLl)>@a$|6;uc=wKUXg02EPVj*%i3idtWO2U_OIclXwN zz>n4hn)sUUH7wqpQ~KdSFLaTnBW$*yy$d42(rv_P`cmPg<+`zOUKGxlUO7Kc!&SiD zh55~WA$cCFx948!(?xo#jH99t^WN&o$1RgrwW44Bwk#p~dhJ1Ri(SI1Rw^hu)Ykzk zeM!$|;ADvLnF#+y+bqSL4B6R}pRuvlO`imAAClSz{)#HRp$u9k( z%0!G=wHN#v6ZU^WkDIC91ZH#0khO_&DYbTQ44iPV4Ariug2ceV*9>Zn6t#bW7mn_+ z!Oq7!HloQjTJBgU`tqd2^N4TZUM|c{#xd}UhJ3Vf61OGNUu+-^#Jej=TFzstE~c4d zk5u+0x8nm5-&f_RR6?i4;&9k-0TUxxhkvh)O4-$kji1gCxYj7362KzJQw5|iVql(@dW-* zLLugQ{zBYJ()9or8ne0bNzoGggu3#Nc50F=i=h}R|0d*ePfIE+D#L-WSdDIzA zOwWY)OGA!^pqgC^pJb&Q$+UM@5+e0S6uNHd(@CRRCvHS2_^TK1Iur=AZ?}0- zVtiqfe(}s_ZM-6IdT^o^qev?@uLyTF(RCt;lt>E+{^l-n6`2f3|CM_n67Kj5AUmW; ztd^PoN|ASfx?_lc2H`sV45MTaB=yepsdLG&bXcztN>ZJhy(7{0=z@b!gntn^9C>Mk zj^>h+$PMjjzM&(^x2N1NA*p1r*N}CK@5RaVdRC*PzwwGBk-^sQ3`zeWODSr!U7L z+fvlx!>lJa354Rku#4Vx;U~n*5&2A-Q{ACM`0N2Or?8xG4#E~D3BqAMXsr8u zb(|(-@^p>j(xO8`fDUfk65_I8bObwHQn{A6Y&=QN{`z>n%E>#~*xgU|S;Q?a%P1L9 z5<9TCEU$K6l5gPRvW%1dG|eD!S^i>4bG}YomeEFM&iD#(S;iOYLB3pEmSv$MfWO zlJ{r&o#*{mIHjlbO7L&lOudlx-Jt3%@sviui_;JFl%_+qs!jy|-H4|&IFC?*C#N(j zX_BlHmZ{q*jf8R4*fZ^(Ezv6&f_+Avv_{) z^+m5#2HabH9w)00giKj1fJWyk+i^HM(3z0uLS`KA$Bg}`4ANK;vW3O!vm9+%>;I3k zH_5ei$GOD%F^W5au0tmdf;ycz zx;1)$IWMONnDcdd03t_Xd%VWrl(|cE@CmCUJt4}d$~?jtv2R)(lihHEmx&tZL_?I! zk@cLxha838Ltqg_w?=ZxIVdSb?0fjIT+``1WL_dTAZrKJ!>>#X0Fn@ zz)>@pkR7HOOp9p4-j9){W>S60Z2I|^exK&u?;-og-q?AZ^U35^g0EY=wYap;=fQ7= zl);(O8lo(Hj)OwVY`cQL3Djaj6)+K4G17}q46LdW^ zL*)kqPe>*iakcz6*j2LZA91wdR6JKXpGK~-W1(7t!u0c(Oe>G0eSG(Q8bsN<1}UByUacNG12RanWO%g^w*Mm(c`2CT z)kEFBizS)FEE!%ki7aML+GKb+y2z*u7RN&u8CP}Y=_w8!Shmz_=`%rB9Hn_`28UzF zy`y}KUMt84o|4|fZ+w#ZTFfh}WjwZOk5`n68bvb8<~$jDBcTK~SF|=|s6S`PR0D5-QeC(Io^x`ucTVH?Ughc|alP>#XoEiFD@vtw@6JzSbpY;wW@rLF{pbw*Ez zO=_ZMAGY-hHldP&Hki5=So{R-RkVAoyN;o2`8~(5v+0a{-$v0Jf&Q2*V+P94c!@WT zz^5wj*=*8f+TS3H+neZcXtiTUNjiKnxt8DGZP>h0@;lFOIhg1BxT8X39~57z{M?bP zEt1k3C_u74xWn*xlLI2sNbQbDx+|l;PSg z_t)$rxJTX;$Hpfj4>|hm2)r(-cxvtx*~30jrF@Ip8~2x-UbJ1-7~7_G&g2!Q;g~OF zgQ@P~2!s@zDbE`()G!1O$`OT-tG?Tt2EC#M;lKvC5EmOW&cNZ1+B z>)EQ5V-P7x$&XnIY=`V6lZ|o=8vj~)zD8A1#&XNZFtkt-T6Ee}0zq9#*+Z6ZVlQUS zIxzAZXf8cOU165wtsHjF zZV&RdVtkep{ymrX`?_?+2tAb8Cb(NN>J0v8&$RTNk5s#zY21;I>nhZ6XR7|j!hUAD zLy(PGUR1{4uNdM8D@)=6?*ppE=Z;?7ugu`62asp~f`6<_vzi0i3di)kk{0(_%?5{^ zezqq0du0)q`Y+Kjw@GGTQU4JX&XS_U?$eMjwn*&SBL6B`%|op|NKWgp5&Nv>jcQi& zVgiz7X1<=Z2IoC;;XGnMeI&jRRD9Q~`$sd9OBc%r)%eb+-N@)>u}U*Br^W|$S-X(Su3rK<0uAusXZ=rN->HehE>i;ImoG8Tqr zO~*mq+w7Ljnznmuv!*w$S<|p@Og{>YSEc8Z**BWz8N)@bF-?Uw4b7Uy3#r*~iz_m| z8#-g{5+jc4!_?p$>awOU?z5&1j`_jX&aCO$B{CrGLKL+rwQG3mF_X? z3RjC{Hs?;OCRYgl;Zc`0eQ}>PZEyr!_Nb%48Kd#>>xwg{DbJC| zOENnSOv#$=Ozjju@8)yXV$dfKYz)lPSlFKT9%f8*Up_9L%hHVx>8{L>R+<2 z@16Y(_gUEnha?G0yyG6!Y9RVLUPB!yRiP`xnW!;kW;YowcC{ zjluF>%wr(-QnRu*r^c*9k$IR`puueRf%>d$8c!cQRS#>9-f#fsOl?s_jO6+ijwM~23}uDu@b*U+`cj;-!t?#h*m zeTKg05#y>9@og%)S{NN8{Bbri-Vmf=8i!PY-8s*Eum~EpG{pna>$25 zT*l_g=;zXe)2??nkpH5KMUMIotRYBkma}h^W1xzHa%J;1Y=o1$$B_%`q!ttf8T9sH z9=+=gL}l{G-cT}Y>WHlEv0enNhf!%<@u=nO26vp@;;7oxc+)TwEb@;;XSc*GsltTz zU8iN;5zVPWb&uAAO64g^1{Pk2kg~=<^r`&b)mIUeoZZ!95u9`hy{w1qM@f)cX}@KZ z8@y$IDB-~Kx1%CL(tG)9y_kG5n!ysg^n23Rfqk^FC+kSq@xt9fw80LD#(42Za@)9* zh%LTeQzSn9aomHft1_$B@S4D4Ggd0P!v#|n`u^`KTF{`NMAuF;W`C=rUjmdC|?Esu+{*?FQo*WvQ7 zB4c2D=(o(nU4$LB%)mnYTFYZmsBpF=@+HY!g@?3N%7h452_nftb&smG;Us62F^IRA zH{O*}h>v={uPUdYCP<5?^28E!gN(jTxM(h0%e86d^qO~4ZKG(n%kMQoegt37bJOpk z=*!JVr@$-`hPV~%WXt@>)TcxoU5VY+3b}H>uN89TerCp^FsakEm<76Su4o`|%So;d zyrgRgrB0ZAC4Y$(Woq0}Rh@oF)Cw645fUvXMXB=v?$I~h4~9f@45sNdjZKBX6{Oz4 zI@GEB0PV0jJEZ=|xr2)hWGrZ%58M*f`Q|z&trgPY{?k(klHG@!ox-jw(qhoHAm%6t z1!4h-y@UfF&KWf#3i9BTJlv2Pt!qKLUr6rKBHhr6M47?Ki1sZ+QR7fEW)>qK8||}i zbiIZj8NsK9h-MqFZzM+SJO0!bG58v?Gv+stDXL8iyAAsnYgNDvGsL15a+DY#RJN$I zPxa%i$EC2^3gs5>3w&#fY+oQ;Cb^cFaPD+j;F|58hsdf%cbl;9#lFuS!3bK5B+5y)piOeL z`T=A{)%4~N2^$p@TFUBm8oBRmcSJetS}94^NF2Mbcu8>XG6#PB-i~!nORBDd=2*6e zA~YDGcQuIEclOhuO^PU9z+E?!!Vll2)>K17N&2ztchJi-_13Jzm%1uY98y)U=lVmb za#GL{XxBe0b*L?AUGOD=cJqnnp#+`Ow?gaYkO>Hj8>h7QL&cNVh>7J1Z?` zYR`ys3ZrW7L5rzqb&;;IS*VUvvP$#_Mwsf~M1P0Sbd1oo4h~AJU?W*`q$J_x@K-L8 zM%IF=BV$NbE%V}8j}a}Mrg{U>JtuQd5i?LE!+K{OiSSbxh+Nnc)lif~*wz2;8qjrhK5!NcT84&f76=sOB$gJHUYB^g z(S;?wA&}g|d2JX9E>$8n4{dOh$jFRvtxEIjf@u9l#$wf={Cu%7sZ)f0j=k!=0e=Z{ zf8&l$MYpS&P8GVIbJJK9G?Jcwv}#6*nuoLYe`-aTRz?3r9z@u+GRL@iI0Nc=nZl!> zXsS=M)N<C(&>O?KYY1eI1Ymu)iwQWmp6)M3gCBr=5i}Ty^O<6C{t}(WRTE5DGfwbGp zA6nj0GO8|X1TOONGOJ(&+N8&R^J3U9;rg?D{NB>bOr+B&b#_oOiOLTvzpOvrJYx>6 zM}|DxdG1$?yBc1B*Sl%HFKjGxzu*UdUiO1qiOJ&I9!Qx4RWqDDkn&3!S|#h+11asm zd#I7kVh?0}H7Ll{@3ybTcF!!KS+hjjo39gUNNEM+Z4ab`)_W_DJ&+RJBL3j)fm&HE z1!oWRsFj#5?j>-GL)d3FnfxLYCGisv+%Y>pNWSZ(C~-^Ax*FE^4qYnqot0lNwwJ)K z&)e>*`}&-KjEu+&dtNe+wFnslr<=!_b=ca3Qi80omi(Yr0ylKyHJFP-ks}c)j^w)` z-*@I_D!y~lV0u~f*F~OPuj)|!C{W7a4O!ZBQky`Gc^Yj`EDKaX!85cbUKT49?j$TZ zQgd==2{i_k&aHLEwM%L#Kqf_%dyRxXehu1z$Sg8zl$yJN{mety+HYkUHfpY=35}pE zWf{lQTyiXe#-iGGMulFj#sQN7qPG92t{gXy>PhP5!h_e~=%yy$bKEr+BEtD(j07IK zb#|ECI1f0|%TWDZtWR(C(QB*n)k?2@aG-}|I@O5OhQ+E&no|UkZw9R{*OnN%Gms$0 zZ>g|pRS*^(&FUEip2oJ~#h`b${ zbisGoNL{FZJEhvPL0!$MZ?c=%>L8ytvAFw+bOuM#?WEUhR4NS`6V~fIvVRocCNiSX znsZAfaD+0#9@a-x3zbvWDt)Nl)WTG)0+~aa;eh^)D*Gb~Riq#|iU z2dWF<(bS4`h@L(Y#ge2bf1pWYMCCM*GiqqNPTd;0h@6qwQBQKMQd1oSxZ1{m!}%!s z9`I53_b_G%9>GGS>eRaEGt)zg9x6lP@ljnWxAfTQ4*EzS2j6zOTzB?^;BsG!@Gt&i zW2Mi4ro}Dpd9yg`KfV&&$?(uhaE&3<`};1ngTOTxd*fUJ3j@W2N2v)0yh;~R!LwGg zp#YPP4QeS=v>RsB-|QiK%LCH0k&OMIu0r0$eT6)OXQ)Ubh2Y-DsCCJgTp$)WcAp*F z(^SS(4*sQb-n0J1gfhl$xc!P0|*mxbiY`m5cw^TW5-`)#!mL9@w z5qPiuK=GWv75J{7-B;kdes*>tk~hqF>lcMq8uO*rULUlWqe*ELKLWjesUP}>q@k6y z*A!~nGJGe;hVhc2O1y`qCRs}*r!D&~*De*Kkrv4&s=WELORa3w3*eI|b@@gN>JvbU zMcG=)M`gYr)m~zr>S=`N9JjrEX||FH&(~gl^K!R{)_hg0=oj@B`@ZnVpIy&06p65S z>v?Y6c{&UpQM|AYtrXbNz*6SENRhsqL1j9&y<4fD9SEu`15Z0T0(cuz$zB>Mrj4(E zsrvnT-}mUFNkm%|ZFJj*mMoyv1bW%NOmJ2@8Ks^;jZJHMt{Xnp3%M>{R0=}at=6S# z6D@I6CtI>n*9;(3R>;B;p^`aIwWs8bJ{-~bwRwBi61~5hcda1%P<|1?OI@U(9Hhz^ z8*rW~$G_Fagj+$p1NffgzAG!Ymerfpq4Zpw{ecC~{eIu2hYIml(~)VJnoZ6E37|WP z8N6%>+Oa<9=6sJFeK_SKM8OG=Hxn^7kt2uDPZFqIJ^{3I_&^tDd!`tkh| zCd0d5Xc(1ZNCq$M;MwkPmr~cJI*re1XO;VSE^{n)jbG2RteK_L5^>xPP zIh>T+T50YXBKA&L*t z)jKu#jy+`szjY*!n5)jR!C2leM) z`+M>3(@fXo23FdEsf9wq9=HHMf2*5{?EaHlp8D+phc;ty7)zP60fhPYUN;CPu0r7ztvgoh{=$8`ekbS zOs%JiF!-rbW`t7p*Sf5YcHHq7+}CS0_@!G&Jwd9m8@fett}2#NN4Ml2DK}5IsE0j9 zeJIh^YqfMc*Nsc6F`KWUK83kG^Ng8hJ=FEDN$GaSOtX3_%*#V(&6sKY^}kUxUJXvTOC(QX+bQg2*%qlY=?ub?71NSAY44}t{4qD-WQMCH`my^ zM(e@C8=^a~euUZvw&2;e+K-tqu5QqcRda!FQ!c$N>XZJTzydGTjU&Yn35FtVc&NnG< z8ZSe6!SjZdAXTrvzHO(<_vmWh1D$JaJ**04sP(;Ghph7Fw~Dqu_xrrRZ>nLVOhPO{ z<&wELZjhI+E#*}A43VH!D($ipFA)4%U^wz3{8q^r- zTVZ=KX^H7r3T~RCe8Jt#JGkX&7Qg-v_~1u~J3mwXABfc&VvTbCkHw7;WT?KP?f-z* z1lOqFEnk89?m6m^#lCCXov#bxyLYwa?f;mS2b=diw*O1MzWpB- zwH(dh?uE4Yj-hnFcw#8s{7ej`Tg{1~bSpP@X;SWBcp(%G7EMLO8WV8o^q))nssj3C7sWKRPs(2`BwY&PyD>+@< zSF|@c<3)S3IAy+xOj(t`miXC(OU%V4ww_b6>ZlCCk!U)I@*RG7%A_UCU9%sR6o;Vj zsSJ@w;fByk;J~Gp6Y!j8*ph8(`DiatUIjd>@rKvhY+UhVDL>n-YBohD*1I`(7$=d%i|A zL`%ujEgwOR+qHCycXFJF(@nR0m13NpZY^r*_Iow;?{Aw{S!Ya`uNM8E2hWl)zgAyI zZBY~|_zUi;um4u9-Q(M4`&MzkI964qPtvsra|deMsI+dVbGsUeJ-4g9xGL^O*Kt+cO)N7i zGS8;@ly{VZVjA;mR}IwyBXa>q@9+GOJ2=CR;@_)5H})=mI=n#;BQ1jyk?hZ1$%!QJd90x*xlRK@IY(F>w7aT>Z-mQ{OEKt z^2j@)Pjosr52MrBSzn#buJCFMc0a7%Vd=!`9hR!hD24*fFIqlh=Vz$ftj2P%yYg@R z?w{RnVx*GipO+IF{76`sOR8sIRT{&*~>fWd zi5d}@8X`IUJR%yCN>sj^bwIqO^h&rZyi(57Z(-Y(-y3{nC8;N$X8AkrZv4hwO?8VO zSsA=)@%ddpS7l}JHF(*MjH(?4U!a5$qHwAoBQ2z`5NCX>y`@IoSP z?v1c~#@-+9?kvx3vmET+%y0Zo`(J=Z(m(g~zc9G#e_`;&!yC$=N*9B{Ol)+L=pD6j z*dL*E)+j^QiN&4XKLN3V_Cln(J@>3qifiaq%N_(8}OOfy}`; zFZo5GPs$vreauhl6=1jQYOWMvS8h?)|H9y||AoPEgIdw9G%DqwV}t~jSeXd@CD!E* zDsLSL3f(9sWUN0{+Ct}wO4rca|3XlIQ!dW)W$|!$1?(s|vBHYM@{1!EJ>S-)zMq{R zsi8__<6VuVoV)UG^mjV543-}I?96g-=!c1&a58U$^wE&)6Ybj($0*uC1X0_{PjS;I z{a`(75@|>WmUTX?Lc}f~tm^4@YNHPY9$YCyaMuz?9*W03_rj7!r0Z^*T}Bi zq9(4>;r0ew9GPE3X2Qf3#+@P&qQUbn^OMfyxC!eCRv7}TsX1#unW`O)xN_NT6zyb* z6@ODV`cUA(ReM{%Te=^GC&oxI`9G?AM+|Q5Hz*=o~HI{Pj$}MVd@HhGsJ$fQe z=wuw2&MX)AommWid{XE^P*O^}B98c^(A}W4zVow>PYS&d7HRHCQ1wZzk^ViAkLNWk z+nL4cX>+nmq}S@uzaeL-je0debbBaKv{|cr(naPn!u7bHZgYrC#vIa5L^3mzWcfh$k=QrGU#xeM@C|K@ytq+!0T3dzXtj4loxUjK;7@qBH zDTc$_ukUMcZ-jk2?ESGX&An~z6+1Wc>)Uqvks%4mMH2ky!H?e!b6GM*jN8gUzZ>R_ zB#&L=b^G10%E(j~>vuy}Ak>%gUdpG3(6yPYeeF3up3#J;wA!+_#^7WS9vJpK)HZ^X zYzUd&vrfD3*pfU>)l;#JIMmoiEdKUjI(S0pyu>id>n1f`7v5?Y|I=C!W?q!P2hujRK2lPLDCMfXtY*!9*C8rN>${^0ZxWv_lH8G2 zD7_Rs$a-uJw{S+4fs;jv_Lu%%h>BGXijLYB zS6*INy)S;lFUK`F@)wYTOx~ZTR>b0a*!7)2%B<4MK-FxSdxIF!G-jfswy$(>OG{jt zo)Ue}vqQJ&khYG~=Q#3p8%xLABg>7zOW3nkJvR3TFJ~*K8;jmH_HU~t`;0(`TDtv9 zZ@5r(jga_K6%LFV#3l7DX%xv9{@#1ee4ozuvu(Ubsrt(*jq<7@C{`;r2VQLU6hXD+ z5V~5WCTtF}hakROx9zQTzCk%M(ZpHJ-X49&#`g5|J2p1Rfm<3dqp*8W-?4_Z%6p2- zkxy5rh>BSp``4a>_+DZtz?I) z*Ai%XrL`Mc&T6cTh6@{ur0*4Z?V5(e+pq6yaDA!v?XbR7`_f!rs(qENFV&&ee);Wf z`;mTdmGMtcZkEG+Ke&q{*MW5l*!2b#benQSb~Ix@xT+O-P%Uf8Eh6utns!#M%FBWL zO>~KCG!*iT@s;+2!-=SFfNl%g%Pk_wTYAo;uUDgl-I*1?eaR?S)TF;q$aVV>QN>X^ z-hAm3_ak^nZP#4db&=SqW7NKVcv2UM9YfOW#YaNA{&7~1vi;+%9)05fO>}n8HP(aOy*yEZAxRIK7;X>hiOe+VOtW@ME$a>WmNEtkMo48l zt`Ui_>_L-Z$rg0|YtsI41D&!2g=nVpK*)FB_W4$k=**rnq4uuPo@+`?(J@YixQ=Sw zGDcswQnNQJ%3#VEh`-Zbc<3ruc1O69Mfbxw)~3DR0<2F`s{wZeF>KoLt83M!Mr}Aq zcQ6#p*QkOFZy?8DFEO!*Ba^qMsr|?n+mBjtsf8{b*I-`JT2nN9iM8u%U%4N_(b-4G zM*W#8ctw`5=tM}vi=F+;AXLn$y*W|VVtCZ|o4dI0H)n9Dqe#RN6b`RHl3h7jvkD#L zCJQ(*;$;a%LwmF}CMRkXVeUwZk?wR;a{=1FAuuVrMDL&xIqOzQ#s~Y*WvR$h(9)qu zS2)z=SMM154yB;|=4Lzj80%Q;={L8;ovGf3!Gsv1fyJ}M1I@~7>_Ugqms*9OG9QIw zBot2Bz2Y)URPtm-RG9!;*~r>&ZoQwoYuIApZS3=1dh5{mlAKYHQ92I}t#yGyW(W}@ z^`Sz8N+FY#h)AnaSVyIhiC%_0FXFLod1Kt3Cq53>@-An4FuczC!`ZW|=BKt4`+GIXBxw}sci$ycTig9bhW7G9 z=cPr1b8CV%2JOcblkP1kA~)$(hIU*kX$IUy;q&bJm3Q|$lr)uIYNL!h?072gM-&MWXOnUf39Q5J(s$HjL`OsET!-?Y zs$bSIx^^}V_0UQ0E$J9&Ksl4r;6y&6bgrxGlOSjrK{~9$ofeK4dudf+znz(lK@|^E z-#t_(h`VmkuI8M15peDB<+D_Z(i(%L@jj;I9{9g2`BD0pK_5uzgr7p&}KHKLrv+KJs;I7F?D z!qdzx@47s((^k@1=zC~{#nNeqcBEaLwyG{xS>Q<}koD3`tpYWgSe)R(qY_R!NmF|6 zc2+^pyB5_%&p4&-64HqnnIcVfQMELBV;z0eZhlDlQfmQdAnsd+-Wav+5zNMEHA1(ZJz`VMm@0fYwLfA{LrPF&`Mq$6a zT6VSN^%9gMJ!Y3qH*`jd`Lg?X{ZqLsK7D-dg4X3BRzakp#(LUg1Pb(<45Lf+8alIBj0-sA1?+UwK^F|Mem`Au|fOua1CRsJe_$o=d#6oS*ngGxgi;P@R0d&XWLToz{ z2zrTSkyr9yYyi+jRYUuCElH#22erKq4eoj$8k|4VqISYXU8>!;u+FUKH8nqiJ?nE- zi7&9;;KtJ#8+N8vwaR7*Q)vfykQW15PDQg(=~$a)*QgV?tyO-l&^Wh5T2emJ?)GSP zI{v4~#!JBMT3cp`{35xdjD*H0;%xfu+Pp|>duUqBAfg9^l(z;yZ(1yNmB6e(f%(^fx}NHysiGGi#jnR9_gY>&>cFlXra@#iidCag?V1 z*?1VcZcP~KCF$6pCK@72-7+?nGDR+VQ(r>4NWPuxHBVaYcn#rk1SKbESiM--q9l5K=uR+C6Jpa?FXfVH zcb72JY1iPp-i&lGuJUf3dNV{3FOY=8?0PfOQ+M4m&2N=@GkR*hX>Nejn-RHlh>g?K zo8c?GIKt+DQg4PR%-*V1Nxd1q%xfOfZT8li5$efe-97bYM2c?GG2+yl;fsyz4n=pp z8NrKKg-pE}#oNvd-}PqXg;$;Y)SHnPKJ?-;XMJa069ex$^O_iA-l^7ht(ZN^q(^S& zd7w3*cII6~T=aFy+aCoS{X`XC3u{Sxb8A(8q!bhwu7zv#JT=QbYlkfIn0n7Fvr#hn zR7z4?6}cZ}M3p}v+-vQj#>O@O{Ofzi-XFnp);`Fvk_O2vk-oZ(IoT+fpJUW+yKAah zJ3#lz3`vK&PQDlSoqR8jY9QLX9$KHiL#XUj<;4a=oHD;iO(f3ENm2Gq{h}j^^?kD9 z(e5L${i42;@AZrNPQKSKiY))$Q}v?^7SW8_*(ieB3LP)yXlgb6dSW&pk@m4W{-83} zl)JDfH)&0)bx&u~&ef`Lvs+X1Bim5qD^6mlZl82dM z^M;QOzdwREt@%z*&k(7W5`EKJH3ObB&d_iv)Qv%PR*(A5zh8LW&+a!Kw2Io>Z+zp< zvTJZq3b>r;B1`*eyE_E>)yZroLZM})>p4jIa!09Z9cg8#b?-pX+$rF!i>7!)7gxm* zJ0|^96Z>GX|1JBacK%~Ato8}KA2dy1EN>(^<8d!zP&bhnFqatIGJ%I|W)x`qSA^DpK z3Kp9OEQsqhkFu|ejDo%+qfzh0%dixusTne93>gDw(`(um#O>dltd}x;^zzeF{>11% zJ?KviG-zt3H`1To$cLQdssFO6boF{`;TE|Ni%X{-1ySFaO*B@qhmLzyAB8BUUwKRLN4X5a78;?LQCBj^t_=U?(GX zG5#Ijjb;67yB;KA=QqI5UMoC$2=-y^2Rf3^aVh0qFvA&A7tDK9ddza5MRr&eW26aDgfY(M>xHfhothsxA=E)S)Vq10#}*TZ-K>!>(P)3O zE41j|4;^FUZvW6z0wHsM!!5m9{GKM7QFT^+vnP)Zk*FsA(g{i@NjRcJmq8HZ{q z5J5pb?KtU6uPHHyWIkWRVO)03#TgmrTX&{|nFoUTr8};jwYk}5gU6j`a+ z@&9<-aRNA!Vr(Cr17_QfcB!1@dK%Q<*U(1~j+f_q7a)w)>LG6t&I1KG*Iw9u(seWe7{Xw#O_`nle>ZC7tWxEuj>tW)3LnEaG zb05*zHL5=ISY<9S!iPAu15THSMxaX@kxtYZG6L!_TRJ)DKSDpS^p@vqDVsH(k?CIz z>0=(~J2S#NYjzo}{aK+yWj{EE!QK8b0bFh@Di|z{=EWVoTKt|S>JQA(qd$3=vJ)dA zJe?r3sJ6|OH&r1ABxKR4l9dxvfBJYew4T~IcY0PQMJEaL{p(rWw2#|NG%1oVkSK&k65p>Ht<;XDS zYpR(w?~wy_rVK{}E6d3|QoFKBV>FD+8XkY5uswvm!c2>jMIU+R;7+djO%8H6l!fs{ zKKczemv4Y;9-og--)$%P35W4 zDN)SHVjt;4o)T2$DfEHN{%hpc4`!Om$n>@Xg_)|n^MEWgKW3ez;$`zIw5XG(F7D-~ z-{dTZ-}CYE*KfF!%TUz&^LGvY$ZH3`_f7g>hUxDcdP8K!Bl`P>?hulNVecFIf+v;v z?hW$t+P!a(*W!`E&ezavd)!)w-nSH4>HOULec!;dGNUi-eX|_y-nVajQsgysKnr=l zLxzJI90_!h9x{~RUZRldXAOm01zq~&R4I6|q9JcEI%A}`cPfX`bWhOhqCZlp+bKbH zw_{bg#7W0X+#Pj~+?%4KBXCCNk<^QtXptUs&$`Q=I4K<+6<^uiawdyElxFZH%GUUW zVh!#n-{D)U`3vrD(9wb{XY$$+9MGxRAWum;;@Vkzt#z-^qLwom{MD2V ze)rqI5C(TIjl)q4@Z+34?p~~eJ4wLc_r4+TbcnQN?i(@dZwp^nWlIA4zGW0~ z7ryW9Ax09o_bp2R%yT<45|3Hu%-*+*aL$s>JobHK-uO(--#2f?+4s$&P7*Nq|ExtR zFsUkik?elMjf80U-f#FlH`RC%M(u12|M9pR$>Udc^cwQTy@u@YrFzWv4R3kxTw`~> zAJM8pYWp2Qgzx;Trd??S$R{UTJ)6+L*Rve+9>2u4@f>oQ?*Pm|GrxSi#%^$@u{%6T zEttRIPQ%~$tKlE~N-z!9x*Ug>c9tgJMoaybjTXKh^}Mwpm82Sj41<%#UJd`sXiJxG z?P%@iYrbpg$Y{vYpcdX6Ju#d|9r0@CXn?b49cig=@a4^{{1FbJ_@Ix5zj3GG|EBrF zst_6Ng!_s>kg_#=NV%FLpF*qurKMm-cJ}xke=G}IHJFADB@b_o%Z*QHJswHo#}mpg`%jHNvC4)V8$4N;8-t9&+80 zuui$}RkjlLP9Yyr_asQPva`duow_m_Ty2t%|JZ}zOMaw3qzBAa=PRRCRy5t7g)O6b zyVRD^ENVHE!Cy)hBF!b;p_&-&*y{pp2s;KsqT!I1_iIG zp68OY!g+zJN)9N2K2rUH`xi@bF9{g@-Z#9pfnAa8eQR**x6t1EhFXYiY)N3>H`cQg zzVAH%MiRL94RS*eJ=A_((aC8Rf8QE{-STYjeOvfaap&;&&HF9(eY2>O1TOwR>qBue zw3qCD!_7D-<)$yt62GffQd7yasoeuaW_&<3s@8$%k|VKp#BHAMN8FMxqLJGff6Tx8 zm>hoR?Tsf{F{^0Fpw5ND z+La^G^zSjN^v?p09*c@+HV$5BhRh^mAk7z9%pMJJp)<18;YiMBa?sDe(5Vc1^`hqA z@iWeg-(Ss=e_i|xVLce=Bl4^#qiAlDG4ARUQej}3a61K?$G|IVP>cb)OhNFJ{~Adm zd4GLIM!I<`?VU$LFEpglx>+Y=Fh?2VuF#@(Ukx6Se=N};`cno94^**j z@HWhfxW9!nB_}B_S+WXl-+I2;Jz9B2Vl9k?W;7vZl||ml!oIl+N6X`|9$&hs; z^o)#tDNLR=^UxcKqU_Vo`$u2Zo;h!`$_8i4P^#IFL)RGN?%%x!*r$s6ESa~@-kQk_(f5XZxsQ5N z3w)V@b3t*BAl*1Y4=s_q4rwyQQ$PS^fEGX$ai7go$w5ga(DFlgzJ{eBETPzsOdS

VXA^3ij`1|Mjql!AN>TS62x_g;16 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

Gf9DrI=Cn*;V)*U6U$YX_C6#PzG;npgTs&ZLCo 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 literal 0 HcmV?d00001 diff --git a/examples/01-led/led.png b/examples/01-led/led.png deleted file mode 100644 index fa78286253b9635522302bacd44177e7a22685be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 69321 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 a&%tC5)KeDjYH#2VAaxb(dxdu_pZyQMTloC| diff --git a/examples/01-led/led.rb b/examples/01-led/led.rb index 18bf732a..4326141b 100644 --- a/examples/01-led/led.rb +++ b/examples/01-led/led.rb @@ -12,8 +12,10 @@ # # Create an object for the LED, giving the board, and the pin that the positive -# leg of your LED is connected to. The longer leg is usually positive. -# See led.png in this folder for a hook-up diagram. +# leg of the LED is connected to. The longer leg is usually positive. +# +# See led.pdf in this folder for a hook-up diagram. +# Use a current limiting resistor with external LEDs to protect them. # # The on-board LED (marked "L") is internally connected to pin 13 on most Arduinos, # and can be used without connecting anything. diff --git a/examples/02-button/button.fzz b/examples/02-button/button.fzz new file mode 100644 index 0000000000000000000000000000000000000000..1e630fb9757d8cd7b6421b3543b581f9a910d94d GIT binary patch literal 4005 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 L003AI00000@;aay literal 0 HcmV?d00001 diff --git a/examples/02-button/button.pdf b/examples/02-button/button.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ef2231f0c475f964d343bc2592a8e47ed535fc1a GIT binary patch literal 446386 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

tqih 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

vQOHFBKQ!uj}MvX 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�z^A5WxvATJgnx+VD_Bza=dmy#OStcIFV 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

c+{SCT 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 S%){^BboA%&QIw{B9{LXW-IIfdUmZnH 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 MA$E3){1zks08eujH~;_u literal 0 HcmV?d00001 diff --git a/examples/02-button/button.png b/examples/02-button/button.png deleted file mode 100644 index 636bf134cdbc3140d165ed8c3546801ba2a06953..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55959 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 S_yG+dayON3WLz`)<9`8l8#6xu diff --git a/examples/02-button/button.rb b/examples/02-button/button.rb index 06b3816f..e5e75ddd 100644 --- a/examples/02-button/button.rb +++ b/examples/02-button/button.rb @@ -13,9 +13,10 @@ # # `pullup: true` tells the board to keep the input pin high (logical 1) when the # button is not pressed. Without this (or an external pullup resistor), the pin -# might float between 0 and 1, giving incorrect readings. +# might float between 0 and 1, giving incorrect readings. When the button is +# pressed, it pulls the input pin down to ground (0). # -# See button.png in this folder for a hook-up diagram. +# See button.pdf in this folder for a hook-up diagram. # button = Dino::Components::Button.new(board: board, pin: 7, pullup: true) diff --git a/examples/03-potentiometer/potentiometer.fzz b/examples/03-potentiometer/potentiometer.fzz new file mode 100644 index 0000000000000000000000000000000000000000..14d52e451a776bf4a80e21faaef83295f9b0d12b GIT binary patch literal 4457 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 literal 0 HcmV?d00001 diff --git a/examples/03-potentiometer/potentiometer.pdf b/examples/03-potentiometer/potentiometer.pdf new file mode 100644 index 0000000000000000000000000000000000000000..91c8396543ce7da241ea860071aa822f6643ec28 GIT binary patch literal 457715 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

(bh1c?6E)PxYy?y!d)-o>Axtr3=zw)s5|( zA3BcH_V5dauUnG^!O0jb!ojZ7$7a&;d4kb0%AO(ff&AO2T$Ul1O0JJ4WEtJbfTYrd z7k(P}&W@Bp)tRWsHm!ti_6nPE>G@U+ zbFcL= zoBTiA@9`fG_W$rUcy%%Y^c%wACy|eAUg63w_Ex*uWDfhJid9IyD(?GsufeKAMNspk zZ#MChtCI2avZ*F=St9n@6g5E-`k2JAf*67oV~8!AVldLsY$^HzEr{H&|way}ACa0XvKHs-CcG8;;e$RsG10cnsbrlu#q zU`f8&-TeA3_h=jcoB9St0iuG`Ts$pf;G?!hHlKoK(F8a#t6D4z=hnzwi?*3`xVkjF zX$IBqo$_NAJlOWQ^Vk~`nUA;bjNJ}@b+G9Ho2fGQA2?_K?$Eyv%-K7ehJ0d7d{*?W_-7gn@Y4`Iyqrck8{XP!AT==EkuV)_pF@EwVPoFv| zds^n_^U3^u;qP`b%*-gGcS}6@{Tbnx%kIG9c(n_7TefU7v`u+tchkH(r){YO4g8#qZbl2(rwR;v;;PWn8 zyV_b=tKYZq`0IQm8S-oc$0c2|z5CGm{!yt*cP$=T|LcS#_veeFs4KB9N2sw)79ep#XmE0U2QimX6fL1O?-bN%+9fsw(T>E2}f{AMqd8y{Gyr28{HnK0 ztSsQ`&vmOP;QN%jy_7pX*Pp$VyCLurd0_7?FMhuFUw?FGec!^6W@;~4SFX>W+sNhF z`ds5~U)}T(S*){LruW*~c(D6hU{$NOfHs^_9bq9|^F4|0B6|9K#zHEKstr9hd-Yxs zqxPNK7c=tKv+0cWhT`mU&}%tGgQ_udHX~+T!WUBN!`g0NsEILMl&>Q^J{*Kjjwe!JsUNlS*DZ*ytPHx4-VSt&P6zw0-D;1K>`u}7mS zdsSS6daWkgM6CML?cD4$@APKu+OD{;7kj2;n|FmW;O37ut*A8el3aEbj9fAi#8^X# zt=y(OyEJ~?`%iYxzR5HVV#ZmzF=Q{x)|?3qV~jw@v(`m9+v_Q^)(5* z8;li|U4FDH92I8?daRUi##muYAQigl5nB@XEj~%b@4` znsD5SGc^4s8Qg`Z>|?Wya$>Dk685IU{OKHh2JIiQCuX*1oc`|Mz0uFUT;aK$q#vG9 zapoVrpXy{M-jVc=H*a`f(_zk;gd-jYXBf-wy_?r~jH$!-X{&&2 z^u6r+SDCb@?Nv6+b@{c+PE@LOTtXbt!N`TJMf+ zG?gFlb(}In$@dw5nRmV*@zu!dy1Lz?#v3g$*Y%;3M)+{PD*Bl9x3^TmQUzlpqqrMi zB&E_!`o1?>cH%j&;+7UIJNs?!<$gPFsZd@ya>G=qKF=s|-sHA?mx0vm9agrFe7m&D zF1ayJ={a!yeHX&!hu?RGd?1D%%BNd;7W@VA>{EH*@{^E0mm`q|BbLnZ&*d9al)4s9 z@bW}(SKJvM^?$(0E>#%*`09CU|M`!dT4e$*&i7&+=ZC7L{xJ{!lZlvje(##+9;g2Zy_lim9Wc0t!^CxUVd4Vd_*=vj{S9sP53YH9~&p?Pz(lH>1Hk``D&Yd zHul%#wK@cbZ zOA%SyzSwbr?8x4<^Iz3ZrHnJ_WLy&M`lK8|Z#e!)C00Ag6ZPV*Sn*<#Y6z`!eyQ?p zlp4SEiHql3yO|!`T#NhqI;zpb^vwR-*VGt7ZH1Ame%4}W6VK#Q@7KKqYH{~r*M9?_ zJ*Puh z4V~<0X@-W74r9YJHqjZQ?wLlVJ;$msYsmIHTdaaw;&hzqz2 z*a8jQF>q!BHvTb9pynqDmYzdxp@LQF9pcIC1|JqHM*YG&7o0mF9)b0s#(%aB@#M_8 zvQjKxXuh`i$tGvFH}&$GnRogkWmz3H5<^+)+0{g#lI*H;)fezA1ilrR|AqwL>?F!} zs-)ji<92lK#q#1xHBGD7!*7nMC2@>$`*oiwKK4*By|O$Dlyt>H%MkMdp z#(j3&ks-=bHWrJ@!m{gVs&Y(dd;)=p?A}#Abg;V z>tBT@)c@MKi#QMXWUy8G)p4*@diahGDZT-0F!@I|V1|*vX&8$9Ogchc{zSqTP+KFz zMDv0+Mx10OA*Ce{v!GW7v?T4r5M1SdpcNVaRp$$8i4cAma3(~`uUlnh?UCmNZBU$K zTFau-BseWX0#rP0V&J)}k4bc(7FNW0av9I&fLSlv^i`4k4{FT)zi+jH+<;FM{HyeM z?2iqo#s2-V82FEWU;O{dcVR35aTJQds?{8TQGACTRyk>VMm;!lMc&Wc-KoLNxy8%E z-bUFXdi6WQ+)R?jm%~%1ug5(;Da&+}Xe<~L(<+Z0$~>am0DpMr!mz2Em)Q25$Mt((xklu+FD3SP~#0$HSue%Upc0UOmc+xvgNFdP=ipXWr zuD|Rm2j*tpLHdJ7nLI;67fzzO4r7)dX}bqh0vdkRiPWd#k021i_M)f>^a(wLAu)h) zdzzDo6~)s-*P5m96W2h|P#|=B1ypCEHeZl(VcU2ds!qk=KTKefUu^?5VI`D=V;6MJ z)AH9%|3pu#(k5!DtIysR_Ci{2&odtx^qPvI>7Nrd|@kb(0uP@yeF#elnO6t9Kj&9zRdJJ-UJb< z>~AL4scS>kbLcT=tKIt>q<=es?j2946xVpq606ds>U|Ajk)?JgGEnY z%XaHF8Q300e}(8swYcus8ERXNr8F$(JcCbepUTO3 zeOs{GX2qms5uK5$x?U9>`olo0JdOhq7_QZqoxS5KoEx6b%P>IVhBhPCxts_GKo17x zaw8TcrWREeZ^e_uKGc0Xov^+s_bi_MVw*0l%KoV^?Vz0>~y_eqwalErfP^2wJ4Ok_u24xGlGrOgIV)HgI;cE&jk4#1t|hI zlq9cCP_9Jnuv!9n!wazOq2cNb`}o3?2V$N9SS?ks)aAgBz#`NSg*CK~q{huisXv;0 zCr0xagV6H-TgK z#siK>BM#2O>D*jv&!i&8R6v?Ba9|YGeH!H8>wuY?WR$g{c((hWqpQr`4k-bnE>+uBf0%={)|QCqRI5d4%8qY zDUG=n8eT%t6sw^AOr(Gqb#BGMIZ)MbeakDdQEXC8O+t|%ryJ=*{xcaD)1bKVp2#wp3@tqje$7r9_$Koza>_0RlHyvKa&;?7|S%>EeWksCw`= zT+UtTFPqd$_8Qc^E$*77f1m6fYk}4=&dF7Sk}F)rGEv39{khkx?xuk!4N1C<2{+BT z*-saFAK)@q*=Dj=!+Qfo`tC8QxEu0CXAYk24;w=D<9a3I7HWm&?ufl}PG{&lc*&d- zyI-1Zm{#SXk*@J!i?Bnn)TTyT)x%vXLW{-%9RYW&AKP*6TM#5q(MQYK4p#2pbMZ{L zS&E#WKWg3wElwY+YMfOga9o&_@*9}b3pA&q=B+Wa4$Zm(QHy%Z0F?NmEKfQ{bD+y&1?cCl7hy6@+J(RBq_nm0MZcR| zMq)?btC~lB*>=!)zwrh2#IVAqF7Y}NMxCmbS#PBXWmVI49~tGwFPwfeFR*{(*}H{3 zMP2h%QNeG+_XzohH9zMbp7woekbL^n`(j$MWS7#5zRdCbn*Mannm+VP{j(BCxj(o~wu>iU2Vu4C+w8v<;r`?F16|IUD~v7fF>wRvOvq zyf5h-G+unY)lGQ|;{?yL)>Ih&Z6L1=vtS@Fi1uZFit);@D;zuG5FNkYrpb8!kPH1m zbm{}(0Zx+KsW;*)jc0x1flU+#-ZKRjktD^eBm+ZR0gA8(WhUxnYADgO{(V!d20mY1 zjTswp^1#l$1g%d!s|i$jBtsWTnvlDT>j53rxb#6MPxL7h{oMF#s|Ymcj(6uO)PItO z{llMSnG^$z1UwcBkuIUAVf5=^S}#jN=&X(>!ltW;wT_L@AO0w|k7OOdS%u<6x#&|p z2G(Thzod#A6$nO6ZjN40D2{Nu9K8qq_<6xiWsO_A2Qge1pU_3{Bg^n6wxL&GMBjCW zZF_KGvUq+8Irw$HRk%+jUHHkd;%E)`J@yV3_bwc7R|q$4ILkSI8}0hxQS8YZ8Ac3w z?6gf+PxrJ94tIIC*?6@#wTbHJ@>@(f4&O$Xph)fK${IcANg zR3$erjy5_pA60`%Lz@LQ_08)qqL(E={UF}f!HJD3RbjC-Fo(e>5;jn^T-z!H}# z&>xwBu>)g_YN#8x;xVsjwu+D?%*=fS7J#{z#*5E~`jh*RrA{Oq#rd0yrt+XG`q=wo zGWT$+j+0u1gr~DZ$$b$L=-e}Sm7YlfCEU?$o~ww94Fu+%TzvfC5~i$=7PVr0%hF0U zQa0^WDSyf*Q@t28kAuG;L#Y5u7x zCk#cB&ukdS3&&|;zoOcvGvV9mz*7Q|7ypJD$b%0+%}2=k@Oot=sSC`=A&ftxrhr-w zq0;g&HM@@g<_9`7rB)mm=H=v530&pcWThx0pRMXoYOD5L+rY@24 z4gi=M{Fizr$L+1Qy?x(7*$#FX|B`~fSNW|WPUz`HFLOq^3~;dBLC~;g z3u=mjWNy!+b)sJc0(2MEbbJ*M5un9O#4-E8+aCz?KL+-vv2L^yX3Tp$j3Bs}Cb(s1`Jn)I@{c76qs9 z(L-q3HWXQZL92JCs~w+BLJw$IY6T*%JOXotANuWEflFc3I}?5*J$PoUGZ>`>0Pga% zMH?~)4!yjJ*r0UAWyWBi)E;0AlkqcXIbs_+XPFVj6l8uvEjkku5+QE?BwzLDG5vpt|Piu9M14zm_7W~h-UU zmV^$8T`>SgX9i;q20Mene%KvW&dHKrm8{bl&H)nF`mXYJ8;F4sz)nNgEG``XC@x;8 zJb&JG6(Lp!?}Z)9s-mvfN(uz*nC7=o1~E3S0!Y;%U$A`ZuR-LqP~{j2Vsa-70Xz5< z$uPrcH81wo2PCxoVex8_q#_1#6`|u8OdvUOfZ}zuF@^(73`L&9>}ei+QfIq4_^g47 z_>_5l!wPYd6RfuKG%=lcC&lD*mz-N&cJp})h7zs{Pe+yt0C3!ZhGnr-CsYV}5#BRX|bSl+%718@+lc=-%@1{%_#g@zcVr!`+ z{CmO$0!@VzpaGsU z(7D_1rSr#&(t}?z%i{j>BhLiR# z@H9e5#%A!52lz`=plR$&RQwyNSmkJ6RsI}TMvdj9d6lZS2v>63n8UUB94jxa{h2$C znE51j7tdn%y(nvdMQX@C@Vyduh7e0$>QMtdXhCS)8l);KQh@;M#Ph^bwC3;wgt6hk zMPFBeSu|-@4opir&<*vs0GWw20c|!0S+kOkelLKz4}hthJMr%VAnsKJN_;t`>I(ik zz(F1u8z6*k-9UkW%wES*eFSUz=-XxpBqyL$b$dqw?o3hLX*6V+sMY`vP51_ZzVZMl zjsVRSoezrulUJWNI+b2EHw)K+Se_S4v+;1&kK-o z)EL_&{SeCaQ#~shpxWF7bOZ6D-LjZPGK9f>?4^hH-CTB*X3 zPN4*P`_9>PSxwr?esgz8P23hCx64|1@5yZW?Kieo?YEB?$DArVdwc%;Ro3(}{M<1B zS)e>dLJznEnWOn$fY}NP`h;wTdSX@)dS#eQv>9WK4iaD(ByDtTA?mYVLK<)-#5>f! z=N&*s2So4x+Ca==9tDzRkoXeaA_TXv1thScnYxc;3q~<`q+hIE%J5ObbC7>m30^Y3 z(?`-rBD$sy^<6A0X?{C*$yiz9yE_!%;o-XHbkstHC*P1py!ZC?>^x)ORsrNzMu*vI zHft(Aw_=u@CyOwIY?E9^U!t!RChLYt^bU*7dYi=U;u?OahM%w=YL9)15vOVl7_apW z>~NxM$GWeJjd&J2vZ9>y#*p7+_WGBOHS0$uBO9Yn8DXS^+fOMSEF1X@x_oB_sxWv` z2fAEOq`KAjL7=qz8*s1LGxq{KNdB$U5N9rbt)e5);65FnC4AI}PalR8S9Ge1eJJ`N z(+A|S@4M?ZqrRU;rTU$FQ`V7Eus+WPG+nj0?*md$hu2B#wG^72Y4k@Z7)joMcm~aH z#_tled4@dfi5szMPMg{D;^at!@YAV%dSHUVB+peCxG3kjH4|t~lrt(!d;8a{z^dm| z&zi3M_%v%jyjIu5>8{!2%y3HyP?sw@Xr?@Mg%RPNWIoHtszYZNH83rf)R;vnnEM+u%0*iPZlmL|OOJAj zTVTHK3LjU;y%vgBEo`q^)te02@7r4CvFpOe2?c9~F>WldTi9M1Z5b-=^+DIKmIWPy zBDYQkGrBz(&i8<-Y@iAnQq6!d$ZgitaahKC3}!N|(ckV>zb3yVpEtP7e#GQN-Eim8 zio3!OAj+d~lhEPuUb)GJNWp5tSDO3upnz7*}LHAhS; zDrNj`HjNeZD}?QR!ySPQDYk4c^-1YE*b~!x>@BLZSCQ^eUJcBhV*@Imfo^^je%DU| zfZQJJw@vg{rJC*NQj>YDokGjjtn^sU8&)VTlS&O(mW9)q&WUg12bOzE=xIxDpCZX7 zvF^9yHJvU}%65I^_`K#j^#*FJi=d9`FDGQDF_fC7?NJYIeAJ=h22^lwk1WhA|w`=b}Gx(`_gShpR@4L=lP2SozyJ-E%RU~=ip8SLXZ&nMO{da@TZk2ziZh|hU zJz;VtvXRkZ;?#+2iLbQC>Rw~%#qq7;)x_Wy<~`h+K@+}4-)7?#Cm0_8(PKW)XM_boW<~yXZw=^<}oUJ6%5oH)b1Z>v!|F=G9&f!p9o8 zzrk(*h2`3^x0H)O6{q0+6LjUYj_FysnLGpW1zRJcO5CAkpRM+vP?`(%)GvbG{Ob^F z0UY^5K2%^O*mw_#U$`$CML&U3t$UfRycKq&;qbV8*>o?S4_INBqNGFJ2I0r`v^~&I zaHQy~sD)JoxErXO;k@Kdc~0FT6&kq7H)Bis%RQ8y@W-4kJyS4InS^B=1Ymzr0R&FB zq*(5$kO#F)SY{Gxu~Y+PByAN@1_192tPGr0 zl_~JJpLk#djs?Unrh`D`GVtCAV}O*jQPG z?l7iW2?NUQ(D(uHA=Y6_gje4h@Ey)VRFcf_PN_Lqd2;Kap|rSh4tIqz0xaU{1>9dM z0p8TKiV(qzi-bMPI9Hw&8D`0481W4ffE^prq{J!I;x_PDqRkZk8A1#J9IRpcgrq}Je=@)k@^%`H4rBE6_^q@z))kDH z)vDl5kQJrBO`g8?YU%4_%B|{J3lD|`EfQ`8ZWR~0ikKTnSvfTZ2Z9wO1ebvMFIbSc z;Rin;nXyPxDiRI?cM^zNk)}0WG|lc_vics0?H3kq+H9L!U0*qdAT%ECD$iTgpJrZU zk4tEY=CcHyr;`iQIO^`tom!54w#^PkwtNUYb*qo8S;M z%h55h-|b~)%5JXhTQ}?qqq!0G)VHPQuHc^8Axnb*W zWK0Ey=AOtHfaEU$5GkVKR}s1zgGiX)2gkUcvx+d2fz%m>a8DZJ4YTqE0p#fHjk`hy z9q&U|k--#zcDHDHfk`=oNeaEAT|>6Tal-d|XC*J+aN1yRFTty~LBe7NiX(hS*Jo%- zna-R=UrY8AeaJXS+RZI^N%*bUn2|WXj_)J^QwKk$qjsOqE0+-273+;|7iLR=vn*-`bi(jlF*eh z2vy*_><+hJkYr?CGJnfUQeIirxp8<`Np^vg(f6)+`5LkGOBpGuU-&mymQFjaJ&`$@ zW+pWO;LYUo>g?d?I|b=0Aoh}jNF*c{wP+m((mVqfUxQ#~5;f3|5{1U|836QGR)Iie zsI6)h!3&lG*m%2T#T4e1R0fuWdVONvxC?}#{Z#iAF5gYuHwQ&an2E|rEu}K;-fe}1 zwvjMJab0#pVChZ8AB758-ud|C8*a=R7G)!k!4KSP`WX9_k+7Dn4Ss4F-cQ7X(F&Bg zGvAhELS^xCvag)diWBwr&mW&Q)X3RtxYa-+#zxhhr%#J45YRW96}Vi`)0pXTbcFd$ zc|je8g@nE4y7Y%op%|aRo7i0E0S`RRXZpYnDg<7wcCz7=otosiCSq%HD2h!hR+YPaOHK<^?8e;lf)C zYM{Z8bFrgg_+(4dH9iSMUvXjR`y!;a;={vCGb)yhJVw>+cQvj~7XRkDn8bJdZj+an z;zbjG1c$kUgmgwwV`Ij%=rs%^^96ip5}E4`(w$pvC3cvv?BB$Gt*Xe3 zjpiZYM7VMw$747VF$f~5-XE@#K79gEYQhanW5aj`ve; zoR5p!?w39?qsRF(vaQJU-*ZrT{)|lYqjvR2P3=Kd)kD7()&31h=PwkUzmkG}dgCWD z%HNQ54*iw0@(T&+r#F83+|MMPf3Ar4(81qGEJ`YeHp%^GhXy|im#GtqW-`JS)1(|FNB@sV;pi5 z5*V`R4g5+d2_^1L_UGK<4t?Dlyv3+T{l&c}QetW%p&P3lLOb;^TjC?UIvngceLCA) z=wTxi-#a0?&DX!zN8B|uycbpf%4ak=yR%Cu>A>i=v-yeL&lD6?@Htmp%f9G2Pu+-n zlhxLF*%jJ$i}HNC_My9CKYq7C_wL}QaxH!jnPZ4v=M9J1`#jp;tktS z6SJFt%dO=^;Wnx@qvBYrEN(!;T}%u5wunpYLGC=w#wO z7t@+EPC7M|Dx~35P4>1)qMWgZR#|a&hz9fLC!6K@_$mA z{VQ8aK~?QHA^MpK_3v%!KgUe^QJMW?>lIjX^iyFri&OQ}cK<(Gn7yV4@$7#Kv;Ti9 z%>LEe{6}Bk4h`U^mj;c)4k2CM2luvrnBC~*v-|jn{Vv{TYwOa-gs;C*j0zi9Mt2Zv zx&rRT=*VIN#O!#FT&vXo#1=H3S!Zi`P0RNgkJO8|zIUpY%LTy5Mds07L&I+U&i$v< zOd0pZ*|&gUun(^${jTHXuV*E{z6$iRsw8<{P(#TlS~#PGJDtYe--=_9Y9^1^E7*B$ z9v)P!pB+^~?@ALd(p`MU^NO`X^MtN~ zfg838B>pbI?FR**yN9nwo9ec_o?}U*TW-giSAI za+|5OUdz@k=Z5Ur`}x9?KM*T_X~6Pp=KPPZ7Ow(73H20P)IL!ax9n8b@XJcwzft-* zSCOs%Y^c6kKkoH{`m{D;ia?5F*prs(V#oaAFA?2l3Kwoa@V@5n{zYSGr`Izc*69xd zjGN;fkES}K%Ud0<^af}T8afFnoKO@Okhshi9@%fv8%svl*=$AgFfNk$`IKDdZOcCH zH4wJfz8ZOWSmsv10x=75{l;I8p?+Q3pI-%@4`}(oTOa|aL|iP0?i_7uyhdibM_sOU zd)^5j+EMX_zL#xrZCDd2xdf!A^zYsHY+OH&%5@bho?mvGUw-CV^x=zQc)$5Qgupy{ z#{Gq1D#ocaSuY~Sv>{RQ$*wj0mhW%#sQ!zN1IR0wHAHAYjH)~^g_Zdw-1CE`@4W@dcZN_y^V4p)*)6@7kqoiBQ( z>V={9?Pphyr0`muH*}da=(RF4KciYJ&*zvtga=Kq=0OjTWASEGO~;6ov#oNMiXO@D zdjGKbqVH6(ndZ=~z`JZC>i>WRultMNd3u>l0BYI-Ltt1^+`XXer{*`Ut6S`(Ncpyp#~jsjUaI z&30(reg;MW`~{@`S7u|0r<2nL{zz?DM~EGTu38y6mw0QwStC1bhg-6&L*CSS7x)hU z0R9I8jL-eiXNT?Y3$U5`l(sq$aA=6<{$LOWmBS(*S?^`%~on!ZTwz=do=a|3S z28EkvBT%FV)XbCL7 zhvG+Jgjk9{O!E!`ir+bJ02E(+`iq9+&L#YVsi_|uK%^>z4HyA=l<%Lx4q$qszz$%S z3hPilS|o(lQ9$D0;ba!xFmU{;#C=@p5~pFPYm$LR!-rw-bMK`i_PbwHl8HAm{OS-)p89`a&03Jz>q{uuO;d@EvDo)Esl zh(Cc?0Y^+=nioe~Zu+8@`DpzlN_z5(MbPCQVY_tcD)C+YcUr?%2FsyPl8Pw`^i1 z%y(VB{dDuNxee$(u}0UFL@5vqy}>DRVqMg(ZwH2oA}%A;GhbiWym3dxw9$ERm-@^% zH_}0z8PWBqRsX%l__JiTLXmMX%TGgTjp1rz*rPI2VKf}2yCp58j&3xi|G+u|^)X_Z zC!s~(Vf)mFD6Jxj(wf1=zvCuYE+ZhFBxC)c8p%Ej3E=bCGqY!~DMVAKGmZF6z(t zk`n2+xc-*5#ot?2@H@5%`gwOf;hA;G{I8T^J#tIQ1(P^VED7Og;1CN37jQW9AD*%Ob|O3 z6IIA1as4H@3p2Y(k2>WZb5ymKbb#jvp^?}(fv#<%I1l!Hg|f?6I?rD2=ZaP*A4YOE z|NV)NYbJ5oqXpYy$#;*4eQZkaG<4l^dV~2o`v%Wemy@bZ5&`;cv1HuzTvp8h*kWyD zvVByv;T@N?h*J|LFHAq}Tz}0t-+vLG^A~#?V0{9o)8^*#Hf8?pn-SkVMiu$$(QgzK zHgC{U%~<+|acN0whGl&tg&#ADkf#%`sjxkY3cv5WWNtdC*reR>+qkk{Q$#<%u1~wY zR9LD!WQ07EblF#FrNiyA`#3pdiPfS@tH#Z zFa){yAK}Cfs#HeB8;m$J^TQeMX!|=zfpqczxB^p-7q6cok+$o~1HRkh;sk zPvwR?9D_V^BO>jc5M%iIN#7ydY3CL&>4uG2lqGo+&Gkj>VTEu{?fg0ofyc+p48}KZ zs@}X}{2Y(Li!*Z%NET6zuaavdLfzAAB;I5kkH8dJc=~fUrbv~n>Aj_G{SQqnwj;(Y z$Q^OD-Efav))J5Qv97?gzRhRH()Oq<5A|5&T6_Vms(Ulp)$#YI6HnTl*JUTaYF!W$ zG*_Fbh|;1_lovr4)C#!OJGz&_O^n|DGNDcEVaC2o0uB)gh12|7jW29`JO2)rvND87 z-PH$=@W(W1CwT7ks7+1QlRvl#F&ypt;Tccx_&>t4*bbuE9b;`VM^yqrWIuVLr*TRDvS9*og{$9;JAMhC#z8zS=ujb9>P{nq>6lAM9L? z4?f<}9X1W@WCM1RTIBKJGk9UPpqHXYIKqV@i;ct0qdPH{WN9P*hb32iwyyJ%K9L(< z-$Ri;!cn-3sPcDJN*KwbY^j7oj{6jx@iWi&zgN1T-O|0~vbo*u=xb{xJ_j_jaBOW% zM#Wnvb7KB`zx;wI2~v4WN%JjxRaNm_k)@z@H4rat9cD(BWIlByvoy*;=)A<%V~sC2 z3Vzbz_=w~!?fb=xLaP1M_4izpDzcQ= zE^$3lXLC}WcyjPNco*DSzKx`n&TFfm)vN9n$+=_OvGI=YyJ%>r)d|fWdw$XElZ13Q zLh%E_`^{hH!~SfT|8IW(r|I=yyE-X@o?5>qMiu{!mQ4yjWxK)m|5UbH@tyQh)+*xt*(_LFwm6gNx1zt=14 zSkZ&~Va0lrFK&h%OO|w9Yq&>tzfT%@(^Ye)E`R_LAhqi*cg9+IZCy6 z6c&c@4T_vO+qzhzA3Nw5ZE)QAQRs`9#%&EUuNnn<>a%JdJ6N7vCwRZMEkafH{Z*Nk zIlTS1*GJEIhm~miz95*vd{9LOj#nAXHK(2`m+{r8N=C<1kkcaSV^%AAKD1=Vr>>e= zg+?Wq&p%qS_7H24ROQwo)ZIC+gi_%lxi!93C$b9^EhWFj3z?^-7guku+%5l!-L1v# zn4IT3#+%RECFE*a$)@+D&V9A)`0jJYWPJlVjCI!zR>d+f*BUj$IkDY|GNx6c0=ODM z*=z;L!?@GTCBIB#NTF4YUFZx!N$91 zThfed2-OWQ%4GWn%467fTX7_WSom4WGwvf(&DymEJ8h1Rel5$bxE5xadwFF{D)}k0 zib(FVl^Eg6obGyR;T>Cc!xiRuJwKU_)~O0q2lMncFkh7;dwi|yV8Q0nbI*Ea-QQ1c z8#ETvix^yZ4iUI2Bp&Y1j*U{;`i4iD0n%m_=TQBR{Rs>{<003iu&b!#?P%`8Y1T^_t-8fMbD*1Mz5q%@npu4t8f^ps#>~p zvv%Sm+Pgd}Q;TAQ8CA;FWp8R+QX*NtP}f|}*bg#C#dhs|K`1ef2L&^~G+$-91D1_R z>6({UQM}s9zOS5(=#+@G33an1V&c%J{F3NN>-!Y8cXKy2?4mh&b|FEoZu%Lh zwWD7z#EamUbO7Bl${Wc5rBf{?q*!g0Nsy27zxzX7{`mMB#A$tokOvs{y9|%aG3zZx zwG{v*A{_K&fau;FZZA9K)SB2W9Q~7a2r%HUYE?Sz5;tX>V zosOSg<0lzyAX1!D0!Gm~5Bslx*%mXah<#V7*+0h7D_Rokyx?b~^G^$JBEYP>csPo0 zARAR1pUJ2M+w)5Phd?p!17SLa8Z8G>0ZNFAPB47%9ayT;5bQk1Z0K8ggm1(E23|A@ z$oMr-n>@X_r-V6(LVdzz3rzI!poWb>Ro`t;2iPGBqq(aohC;Dgdgz&DH+AIt8m zi*J}mJwZ|B3K*%GgLO}=&uF&o@LyH>h${j#AOsp-}z%Q`@o@3Gnh!}>Z zyGd0ILJ=qn+C6>~^r;K1?4-a7IMZAI6#5(ho)uJNUyr5Eq478pxb*$p6?a~YXUpaC zAG-T>c4j)!UQm#?k~pzHUDQWG+;~&YntO_R8`s675`62nQBm9DnV=T7^_>PF4=vvZ zIjuc`^%iLuO(2Happ7`-66N_t`1@2f(fGh5k$#9SY(aYh=-u6QFjBbx3J3p~4XA1A>GghcIKN`j~1?v9uiY z%RZ7b#CXY8i=-Iip~aC`7L~qFRuK~U8VsHVeREDKc?sp8*;+0NeZ)Bet{A96?<%bY zmSl_(N`XiyCU+*Igp7i9&jLOhV7Rq71oT=nEGFnFSAjHEVqHeHWk8Ge5*{W74+pi4 zwT+_$fNt1?z~vUe)?8hfgm06^;A72zc!%XU2rK^cBBA)RNK{_>qe#$=fJhX8u|9`2 zFRmgk)0e>jb}Rcp7vi5R1SuKJgZXqI5t zxilCLKM5s0#tj*_!54uJwm$yFEXF_;S>WmqdA38H+CU3i;}XzMl>dB1FjAp}c|$t_ zcuFO&3j{3EbUEX9``hUJ`|T$*5ct3rni<38a?CxB-$w{Ffw+txR&g)oi`^iB@!%cM zgnD2-4%i0R-XWu5-41AK1X|pN*T#<{H$%flNb+%#C(n=~4Vamr6{y~-d_TmRnsBmR znfyH<6c|bh=%$m00UWAA6rAwJc?dAkOjV^|gB}j1Zz_=bLChX0YU>j87?4O7M}Oq0 z|3sKaKL`HVT5KG2XGC|*kx-vSEaiskzJdN#zc>90LVwu(`MGM)OGdyH?A`;=94BGf z4~#BIn~cZ+UVM{Q&SO?dwAeIiX`Vpmw7^+BV*u#}UW-mcGuLG{WzspSa5O%B#u?3> zRn*3RL9M_1UadF&uUh}d)%qCK`q~23J~q;PM<=h{vk&g0M$ltX$rjIgJ4uQIZa|Wb z^NK4mqlriYa~_mD(1`i{-hK`Iy@g}44Ky)AQ{a7~n=djPphM8#y0d0HRc*PdIFKdi z++Sr~-nh)jdSTftzxOq8$q4s0%E=9Etv}pQ7`8oyt5ZWv_Y40TS_66#8A6Rk2fyPx z(Vn^vaEWU47qtZih0UWESi0Y_bAVOj``=Q6BUBTX822sB_t7^ISz-)oOQXEq=`?rd zI%h^5Sr9%32!MbhJ*zM`goy-mO7LStz5j--1C(%e9r(-|VAEEl+R^mu-=-EE2nK}y z2_D*U4&e8vap$0k22=d!L}0fy6@YWIW`oz!as~kFIsP?J`ejGJt^IHMwXcEv(8m%L zCZ+%m%47MaO{ig;Cvdqna7|XL2ziDs%RUookieyvQ2WHwfNiHymm~i*BS*4;H$MdC z?3OPI{1p`lS$T^<@-u#1UY`J%K^Ih*O!BOmlLK(+^KI1_SL~MY@ zWmp!PlmhE^5~fB7ivsxhJO(COk>%h7D zJGu_N`@}eojPM)r_o+tF_(1yzBwz+UCe8&4nR(WqhvKfI@1iGt&xPfoqo_bZIF3WW z69W@}ncl$i#7Vz+Vv*e}NCG+<#7xOC)vkI0NHVb;jGi~V0DT}F!W>ee&MyH(TRuJV z1J?pHya6~LfQEtV^}sR}(H3=Hz(w*(0T)Ta!9z$u0>wWba;Hgo0Q&489)1#4E=>Kp zgic`Y`GYt&W&Upa;d4}A*~OfoFch%vKIC$JW1v+F-?LnB+FxAopREKv zbRZshBqT}hGj3x9tXXyxXjX|D_zF(=f#(Tg2SCQE96WlD9q^LGwqLv?JMa#`OG*JR zSqCTJGe=dsfro^czL~&74!Z}#N?I19kU{M41$$v~5qS?eFZ&tX^8tWOvY@y?F#ysM z(Sz90fLYh(Nj#0w84tV6_;(AuNuh6Mu2Tf3hC%3txK{+T>txE;v@&L0GxdK*eUifUn>v zD;w1hnwZ7m?*bqW$WE=wOTVDswGcqR;L&I0S@3s~7I}z;eSvJ*N`ru*NO*t{!DB|l zwb=XfRAOm@4!~&xaQ4-_l>R%>M~~p{6Xy5CQ&VU-pLj@k_{O4%E`$;#Klt4SPxSqM z|Ix)%0OG_la7$!xwS$@`8>pGBOOBxJ#h(Z>z}J7f7|`qF9nhx(w?WGtMkncU-%!Ih zaMG}n^N4lcATw=D-DM17q5%}M7Pa^};Fpl-_xf!YK^RT~t4<6f(B1T??n63lfsTs_ zzgJ~#{O|2K&2dnU5t_7DMlRSJ1 zu=FDH+`p#LlTv@x=so|d(fUZ-M(guh5SxbptL+71^NIL7(e8Ih zt^^uw6_MtE(Y^?i<>H5ffYoa~1-Rt@3FY_3ZGc9_0kss^A^*XtTq=-qdE|=YDk6tS zgX(beVgR$d0TuSp9%5tofKD?*-q4c(O&>WK$hP)j+~m1euMf6~OWK|B-{qUPX-FhI*=ig)Rm; zn8msZ0!^F*Edxt_s*#GRV-BL_K7pVOowoDH5BLUx`#+dB>g=C-fQ6l|z;e!_;8HVS z8n3HVK|-RL3#ga%GupsjHR{74a-CkPAv%#lK>H#}6DaS+&x7U-s9{|+s~J?wU_dPg zmHewfME!dL0YYYyH;VC9ZsjqwEQpR=`t$9^-ht-z0oI$9f|C+RH0(-sIXW76biyfB zu;F)IV#|DIZVEE^2mmFL6yf^Kz+9%u_|Gp8Xo8ne6)2`; z;u$J2ooQk5(-XdxEdkwiSRG9Ap%pDC_9@sjG2j#^b8tN0ED2SIZJO6O0j~QL`+T2o z3`lx{n3@s;lt+0ub}CSUg?2%wowAo;qmBF|1KS4NSF#|o+(+^PDokpKz!1TLFqsn` zMKWuMLuIV|W5+6D;V@zLB5=(%ff_`*Uw9=pE^cH}RD%_4jGu<{bx z>!HpgUr~!A?zS4XHGot)#{!8ME|4#~^9-7(?=3Bz1EDjPWDFushezeglaN{4?-cyO z+tvXd&^l-a2RETd96^#MjKD1Uq3oqR#dWD5(Si;thM{o|wg_k{wiO8~CX3AM;) zYkFsL4CH|ap@~l1#>cCOQo|7%cubka zq5D6aSqGs%=|?8wHjZ-o1`kusni$!9zHnNl{b1X{n8B!AH|r}RxHF2xp|R+>4rQ?k+4 zGFmTUNA`Y0arc9tIS;PO{C;DKrc^?GE2bK~jY~~cyG{hFGwsZ%9)v~lXM&~nzAd`C zXONL92o+DwYIk$m=$T~XYXygW_zH`4T+M!+(Ei0+!sQ{O)HA80ZY&bAE(ko>GM%z{ zxqW(L-E4OwDtef>po|HTPqFSe`&`zoC#$c8XxwIl)E8Rp8a-paW61T$jXo}$9M?T( z<@X*4j~l|CUwcX`vq8qLrdKw;df8GqB{v+NMSu;6!x~9es z7cFcx5RZ~5OdNO-wJA8{lcbJY)5j(Fv9If+wgz9vjd$N>Neb>sp7?Ugo91areQK|( z-onz_^8V2uXq;7YQV68^GyuC6r9evAd#H)+%bb3LJon&Tg%-p`_G`9MAJ7HoW_O<% zIlEW-xIxrWwKLg09E__Ei9 zkK_I7>hIr~pW&F_XPdPm9oXF`fVza@z1oGV1f6VceXYi93Jh(IZ0qGLxtXiib8jF? z;(qbgZnWFd*5hl|^dM_XkCUQ9c#Ws18>KCi3kdsH5jD=aL<5Q0TxW-}ouXPT%Z^|) z6TSEafxmnkc#3x0gDsjUrNBc}FvyA8o;%HPG z?MHoLwomh+o32ZaWyWkrIgj@dO&3&#co;$xB)gh8tHxe#ay%A;%;-D6)!SkTegt%O%rGF=0GLaGlAye`i%o#LOx@0fGTPi#{e zYs6<2lzY9ORJ5JUnMXy^WfoUpP7r&rLoF!#nN@_4_V!L@NQL^BXK`K2Av0i7#K2TS z0e{i07mvce=&#{&GrJUbJX-T*`cBkiW#-MQorZUsu2UUU1!o)dlF8A%a-Rd5yi&9S z^LCuUNr2!6|4iXmu*7;r858=6_)gq$&@;V#3rF=I$IlW#`! z(aeUo_4{0>r1tseOe#?KevK%e^nwwjW9qRP=-@3L*s;c1J3ob-7b?c)Qw8QR%BN|KQHKEeV&#%-`S+^zHYT zY(2&7GY^Oq4R@-LaGVm|_KvuPv2tSp30uAH-s5r0vrjZc{I;#@pw?P6_oS=)K8x)= zk*J7M*F3tP4|S9A=icfJ4sU8VKa;?(uCe#j^agfZ6&p+!SlYP;H|S(HaQo@g^e>ZN zWkqJJ(R2I1OD% z#rg|)^@LYR>u?S8`r(IjhEyuQ>-4+V@(56+%oD#1nUMl>{9f2LT$!fKe|FWV64yIt z7Pjr^5&JWG3Tp-lZ;N3kaNJ&EE7Ho40|zrU)FRhcmYoyrEj>;|cY!xDn>1S|ygiNf zZ9>RNyf+XpF!yngh&F;Crl_KNRHMf^pUy}8jF|98kSG|fp zVWpB1ku_UISwpsuxxXvnw=bQ4KDo2XQXw}rP=uf-?gh_tilFS%3 z)Q{iFp#qouy5zjs@@1lDAw<=~xw2D*H)&sV-ON6bHfuoiB;&(7irA66gNrN^FVv!+^5q?o+ne5J;+`OXO%nvK%fZfX8JmUcd7A zX1TUT8$@!6)FgCfD_|J;rUjB|u<7c#&8tRIz)Si?MzN$7qznyGmJFXASEWAjU{Q8b z>AYW`rlkaPE?wBS*2qWa3ldrQN^^&y6S|oJN@orm#^Jw$Nz;awRCo@O6T z`6PbS>hyN=Gp$_QEO}o1k=G!kl_@MjHzchuk)(#_GMmZZGF~jWSJb z?_HfyZ-wW(g{W=UQ#Fk2wvSkj^kp^)1a--7=J>4>pbzHldMM@L(H@$s=>=60B|U>W zk{ffeV#l-8qdE5@grT^ru7K^n8~S0ij)7Y>8{y(GF#E&>Q{Yvmj5>X96Cu47tfs_649?2P5r_B1CcuB2WT4jQP0V}Sp+oIv3cM1*?KHP*Rf40o>Hvin4Ep>jSPp7>O4XX zRbWqI+uHOer%Se-?=#1U`TbdepPc=Fme z&LswV!c#pwB4p9i`OFU1u(Md(XL%9re8zNXc|7V;gNVD3@VDkm2d=5?JL+cDFw8=b zc3t;taWa~1$-kC@(o{9H67W&*D}zdOXC&xAbOBlG0(v5@-j#-Q1V@rDiKu{$qiN<~ zY=<3KmP<0LGX`5EAhMyhuVS=g)Nj0=dSnw$ZIx_=yJ|&{qxodD?+&^`38y1MIYnq6 z8Mh8Jo~MP5a~0BxJUvH`<|@&9QeQ)Rw4l8}1pB^z^pPaYQe}+QASfsMmQ=ds=&hkB zc-E?G*DHbU__AD0h>FP^{2VBwhk}@T=tpdLg8#_ z7?19|Y-DsOi)!Xw3dSp4i}`hCmzH=~f%fI-{?i?NtH=V(Fl4BJ;w(!ke!bR{)s&m(@>ES_Md`6C z

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

MM=_g()nt!OH!Hm7v3o(8CO3979fYHbN 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 literal 0 HcmV?d00001 diff --git a/examples/03-potentiometer/potentiometer.rb b/examples/03-potentiometer/potentiometer.rb index 3db77b09..3a6c98d7 100644 --- a/examples/03-potentiometer/potentiometer.rb +++ b/examples/03-potentiometer/potentiometer.rb @@ -12,7 +12,7 @@ # The center is the wiper, and should be connected to one of the board's analog # pins. On most boards these pins start with 'A' and can be given as strings. # -# See potentiometer.png in this folder for a hook-up diagram. +# See potentiometer.pdf in this folder for a hook-up diagram. # potentiometer = Dino::Components::Potentiometer.new(pin: 'A0', board: board) diff --git a/examples/04-pwm_led/pwm_led.fzz b/examples/04-pwm_led/pwm_led.fzz new file mode 100644 index 0000000000000000000000000000000000000000..be5b874cea8da5ae9574e1d2550d9526efec93f8 GIT binary patch literal 4458 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 literal 0 HcmV?d00001 diff --git a/examples/04-pwm_led/pwm_led.pdf b/examples/04-pwm_led/pwm_led.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2c81478a0a1681d4e3b4eab4e20cbb8059f74a10 GIT binary patch literal 457670 zcmeFY1yo#JvnbdE3+@ntySqCChu|J8I0R_YIKc_hcz^%_f)gM}2=4A0+_iCUAUHIy z^Zoz#-#c^X&ANAH)_ZHsx`*D)Icrz#l3lxY)jrh>nsN#pJe>UK4BdI%`RF{<+|(|X z@6g4?(YbUT+?{NwnOV@eWL<46++AG%y4Q4dvG%aCfj_{W09pWd05w1WzzN{_>zns) zUHe1(%Fb~1(YXv9tljOYMTAArxs+@i?CjmC`MBZt zuN>UnG;LgEU7X*;$phV~h2bo^IJvm$ytlA|E8!nB6+!BE(HfC zI9t?Q3Qll|7ustw)X$-ip!#=)a3G zuvyzZS=p#X=<)B}R9iW{It2Dmh=P$-VuW|Twuuy&+K5)sO3!E%TM@jOA9uF~JpV3# zYs321QhBN~t#DI)cAmVk*WC&5+l*#nxNR_I*H)l@N0xz4Pa^_S|~Y zFW4!srzwH%4m-eCSQSKW>ZEfT(Gi@zUxXw?+%Dw0lkuI7nN=!61g33SW!pM;x#?vC(1ps%`& zjo8McBGjD`IhL$QMBMZG+#U}qK$-?92NHyurppYU`qHl2PdOM|YG$OY=dNJAr)--? zAE9%_b$YCsd|Np8Yj$7a2*2bX7cqsd!7{jb@D|wWnA=K>#>at4<=--DMi-l%O>y|o znY`O8ITWqqIc`EQRs%wv28-uj_h zh9mmxbx!)1yJ?ZQFSaLJ&lj=7J}8K_bcWvBSX&XDbGVp_B-zAibF!C4!RGz#h(TzZ z1AS!RzV;Eh=dUM)WaXD~aq=kEUUWTH*UhehXFYW>u$_buk$WJL@!xug-08_mabua_ zz?iN@N70kdCi5t$eRh3{%9CG9yQX~|WcqGE9F6ns5X@F=<;Y+_PiRIM(y(qO;IOO7 zIfU0H=S^kr_z1Q?X1C)_{$Sx&8*w6a7|B*_-*)3j=?wC+Q3n-CEuWk&&2|g5s**j8?~Gj} z$zd8}sY1$g?KZq2#KPK2D3535b!Z1}xqWFL?Mpe4fvnJLC+&7G+*qT;_~fnQcV7%m zK

*oucRN&u@R@Wi-17sj@1QUwGjkJIB~1p4chWWqwt!DNo_|VV3KSSbyB1JV!@S ztTTOGr^Zi2$K6>3ta;R!?ymVJL18qno!G*bEIC0T1p&&KbykA0LOlk#+BBu=h;Y43 z4i%Pif8|l`-q$_vi*RXk9I3B0;d0;!=*ff%`JhGIhZ`?05ffKw@R2fsIc)|?jH?FF zm8e#ZX`Coc*zDd21@7b=pDJ3fP4>n&WGbiZn43!CGS4`a_}_QM7t$eXS%><+QL0q- zyswi?oXoT!pO<0b_?=bOD~x~b^Z9VTHpvycxM3%rJ>|2`q826Np*CW!BrWN3V-?D# z(sD4>cAqWNRS^;0bbaCWCy4>M;_c%!qCGzvXYQ;X|Hx&gYpE$v2y>N*_OFBjGL;^W z`q%IKx6M2HlNu1GY*6gV(=d%Xp2u!6P}puF+F&AQ{L~~jwGVA+ZNT>`e}q&H#?26d zIdXgIu=OU$~?&JJ$=Y+K*h!24q9gsACQk7pI6Ee{INo)@Bw3V*qqfj1>CXm^AKrB%kHz0cn75Y~l(DK91>^r%=iHI{>kV+=Bu zD!3^~!GcqJIu?o(0Tdrt7{XMpbT3gOg$ccKde+-B4czTbYLYAwF4#TPLru)MDN?~~ zwx2K@@6B9}x>UrY#Bhc8)s11dYVlc?qKt0=eU?5rQ=rK2t{1*+7fC!+H@9Iw7)%$y z8lc^AC~Ug?w!hOeK%z<@v5-hrJ@41a_d9cmyPnFUv<2Ma<7CWD}W zqNE_|Wedd{-zz`a6F>^Je@scl$CB~nn7%fkR4JP%Fv`r!bnBsF6#}j>##{VekSJ0^ zD$gjk#=xF_SWxOOdGv$)-mq1F1FHmNV?)R`) z!vJZh+dDQ2QjH%oH8yNq{8F+WmMEnH&dxaZ57myzn*0Rzsp(qW*bz)Zn~zq#*oX?^ zkL;ZdcwU$GI~nk}=AD4}y@@0EjbQm;iGtB%LiYK&y)7qMX$mi29hw<*`dQ>8A$)?W?XBwV#m_-#1t)ApXHI{)#M}N*PgfG6=ky+FP_oSoRyU8KOZ2sXL4wFJE5nGuj5yP= zroPSSzPc1IX8Fe9aE)7=Wa6o+P1x#qJQIU8-;(Id+fvEx{qdrKo>_xTzzkm&DvV!w(|QmR!wjpSd+!u zAe&__tINCnw_Ujvc&^{)kkfilC zVo%I^=CE63R8@BNLQz9S;#9p|1*t8!{;MJ$hvW#ZnDGP`=ueaFsMCeh*iC=b4u{w46jotkC&!iRpJ@B zJqve|B8A=JMc|Q;6GvNejGqD)X{uj(0L#8p>!qn{WA&NmX)vLO`Wlmn=Ic0qi0Jbl zo}NZ(;x!C0xc*pnngNz7T1VO&J3BDl%Xdb8y_7+>O@4Av5vg50W39`YXLWj8>4SFS z-!|Vuq}2TlV1u^tbK)7>jL>LBypfW$HHXS-7PU3LS6bVMx51V?5r;ugNV5AwJ!MC2J6@fZ*qyZ{%q3_e+vs-+miYp5dU$P7_;YU+Qje zy&6L#50miG+ccg`|8a(|z^*B~Vf+d0y6E2HO?+h7@o0f0yJF-zlIJ6}nEyiO;Od$( zlwdewthVMA_S{y?ZP=u39C)fnyFF9ZqwK2sGuL};w@6*3F8i68Uq=%nD_MoLeh|^9 z7aN>2IN^QovrrEZcNt3?GncJ8Uot#vW;f;* z6#D|W#oUfPPm^#?h21WdVI=AyX6{rg9(UlkTz19-T<5luyMCUxBRN+YpVo?D)tUX- zCncFRi#tP0Ta&*Tz#o2ufGKWIX@4-uYX3z%jbQl@r~t8)g@qiv+XJNx`6OarL5jrEs2pajo8%_wlC|@9LL{1X87z zd|J3%52=Ii){nx(lC8iTut^>zR1s{Hsb@%y27@*S2hic0J=fcQ&+;@fWAo~^fWgh z=C{VAq4sgXx@~0eD^fl#lf{r<=Ckk1qFL%|pJj3_M{(?nt z4dJXEIm9jB&bpnWJ>g#*DtUJ)A7^((YD{I65XMa>3Q%gWY+;@J6jDyAwbEQ} zsb>%STCwRwkbFbpm9upmIn>ik-iL9$E9 zW(T=nL?rPYmC7Oy^NCZMxCWZ4|Ajeb55;qSZ-;tsdix{aqzp7Zk_w3A6_1;pH?^ii z!!+YpLSQHZs`v8S_Pbp4JS}18mI( ziuP0yd`w$V)kgh)|ePZRx?73FPS8U znaS^B(^=FM)~2|O1G1~gx>_9eX*nCzXt}0+?4A2WeR=6`>9YFeSV!#0^mUKgdkKH$ zFZV4C#nJbvWTlObnfW)7un zln{ch7rC6j|NZxZ_6bAdkq~aZJb#bG%qS`B6-0iSBZxbI0*_2y$#yO9O%#O+odxEU?35#_vhYY`FbvUD5 zTGC%lx717Xo5Zb$_g(pUOG@^vX#YG5NEE9Mh!(pDt9nh>y>F_I_R-dE)oe&f$w<0N z&Qlt^)a3%QTip>kZ@|4?2+4oPXTmsjZM58ecGYy9bFEB(GP>MZrK_t>bMuwk8V+p- zqG}!T-8!Q_Q3#@z&178eXO%sQ zZjO6fbgyx=`vwmddC#?R;Qy(Tp{ORu+%#PAY-dUQ?#%0%%rbF6l&_jIM?!x~gFK63 z0Kti+-DYY-wb^Ikr_SO5)}hkns0|%KkAur)wHCN$;vr^RKLBAL4N0!QJKk>An0&GR z`cVU7Mk}0^fzzS)wE+^TNGOz(}(Tp*;e0!ImiWO0a6(5l8n-+z{Zqo{C@{lqfw z@!ykdG*ONbA9yeU1WOBjs5jM?yl6f*ajTN;WNTp=gns-EvJhqlI@KqOuiw}If$Kg+d(xp7BNNuZ;fH&Da&f6ZSq0) zebOp3t`^^bn6z(84a7Kxz+`jrzmoH?o~-*G$qa3eBa61YAQH+iU=v$;C7IqM5duVk z)QxkTP{~JJO-E{hFjUGZli8c;I*J<j(Rb8=h3_&tv}Jl7TbArQm3| zW0X0+lC?nQs`b1w<5-YctRA&)6408=wGEF_K36{EM7_SNLKYKrhzcp(h-h#jroa85 zS;PnLcwJ9BGzsrOfp>gtxPKm{-NYu@(N)~Qm1le__s_9p+Z%k(N`Hs<9eCL3F7Hm2 zzb0}2*x-Q(ni}UkVT+rh2=kGtmdb|!)$7k||7Hvb^0FlC$G9l54Ycj`CtexC2?65P z1F3;8(|cE7+$(BdynZuev%(eXI91M0{P%!tCxWPZW?g>km8&es`H&w9^;NI_W}~#> z7ZKt4*PZUg7A=rLfw2+O9?D;==js4QJ*WIbLB<0~(ORI?JmUq%zq_=Sv7QQ>pI>+= z`?sOPC!Bg^Na}A+?t1)fU#`f0xy?KGKH+9P>EJ0zMhr3g2b+sMroL<|GB0q##2dd5 z3Hc|`$c|(DrCZt5hEkWDC7jJ~pO{FhA-11y30nxq=}&Uq`Ecuzd&)nSlgjlID-R*J~mLFFk|IN^cXnA6n?^%|OD#jX-=+1Q=~Y{E-N3Euzj(g(Ya*2i268}b3%o-b=zVe-#d^a8=MGqQnHaa8 zNyV@4tF_P3j3!*;a4A$M&{-LH&RKye*T&ge8+;^xM7?^|g?gl%GK?@f6O;Lc=$ify zJ14_?tyRN=P0l|v`F8%2DR{m4t1_`R%U0U-Ue)29*;Hkgs|aH?B;X@ZK{KbB!>Yl0 zxJbapDP|ZIQU@0T`qCv)ndBU^j=wvBv3k3n%tYgO>O_0ah-Wwq=*bg8T%JNFO#kC7KY^Wn*{Cw@UQeD|OR z&c$qMR1weyZzpnp@{Ddgd$~_N5_J!1N@Kch4f&mvmpguqufZ^fF**EdISd|*sQK>q zT^+E!Cy~J;x@Lxv54Wgy^`&8uwziAw@uI^`_=1e?;rqkN_LVPDbmXg4S)^?f>XYXt z;N~r{qXhq@2zNSpo#pGZBEzdd1}FEWUq{)i>YSKKkdvXCxL+2Is%HI0BLQ3(liiY? zM-B)e{jNmV4D;j;!lJ}6CCk`HdpL94Dc8*PRtaP=Zl%V z^OIy3SuFVqdsaSy*a$ldqefQO+{9qh36t9j^?oU7^S+xlk&t8Pg0 z584k|QUKmHU}VT?gt#Qh5)b_ezAAlJ_Q;W>=J>_0ja>#sSYWTCt@7&@KR5>hASN`| zu& zituXG@cOYvnWgb{R;&K+=eTHaJu(72X;r?+yrG+;1ukSXY3|)Di`}rn7-p50&WA)d z?!rhx1}VM|rel7U3=P+3&)_KZCk5?WeO|s*^C{wQdPar6D#2f_w2<%$m3}!_qMGnX z!@IUh+~Dgj9t!9_C4x(>ygW87XcQPAVwHTssVB^&H%<%ekPNoLF`iDzQ5{Ckp5J z1R+Yt8iS7vBq6V=?KeepjV<`SnJ&JBSvd(0bX{|uF~KSHu(f*S)V<(g$cS%9<|6UO z4#3T?P!_zxlJ6?ooLdh<$DWbF1re1U+2Tw2)~v@7IoQJIBo2xxcPL(OiUIrbF0gIx zyq8UU%vr`QjD2$?r@R`}z@gjYtQue4=4zocGiseNeZs0-18#m@ct9@_ZxNZ`--Gc; zJFr;YGe4hw{-eUV}g8i17aEC z>(^#>FW!{BQyTcJbWwLd^B9su$k=0=dL;Kc7k?OBCqd39;d4K|4BxY)ND-zYv!(|6 z(&RQVV-pVXuBS?ptTDPj<>*$38f`&LQLFScCptnKDroP%7Ay0cDZkkwqH0vX1*)LH zH7dVsefDDM`!`9sO_jf}k=@zIU)Uw~-g6Ney>*jrb7}b$XvEl~Hak`;Axxi0=HF-( z=NpaB)sQP3m7$fpfrU(9Ex7=v&j(hR`AjMPVS+ADaa84f^#QvDxu0;}JH?)ALDX#g z@{pu#ojpN$<0X};=HpK{ok<*S?^9q#VKCil9pDmYW>a{l30y zrxq@(aUZA{s|+IaLcP;`K52^giHmFKQ>^u@*y^h&))=yPGXl-%M=dn&qT|N{%agZMxR8eBR5*&i~ z62=L!1xWSwF<)CY>NQt8U^x(W3*8n7eVS{3gdpmc4^O8H_h#NZFv>?55raYWsaD>v z<~n^>x@c|^pGMtlI>m3oQDQ6~MG0&2tJ#ThB@~llw`5MkUeak4wBv4D-{mCr6f=(I z@vgk40%-_%CPnO(BsdO?-jK)#E{f872k*>BXm=23Bl=)WFlCA_YiBISu#a_7l5_Z{ zC%vWEgu>+)dX#q&lu~0ryUj>eUrtS>J6+ImS5TL_Gm&3VC$P0qdTIPZf5=|y;ysx~ z_Cv}D>u7u7)OX@Q+_+xqckMv!&H1dO=~R1ijT)O&i+S*tN+X{weRL^yt`MOrNJ^0` z(uS4zgytUK5#*BSe0>n}^)Bj8gCsokdoZm}m`zyH>Mx3BT5}X3rc)jL$R7>_qY$gE zTzjR%6dHr_Qt?}zW;$2007qB5p_eQlFH>0K7p5l>z|EgLYZ5*wKeq!)J<6!i=oHFeq-w;V+x4rLM zbHA#Y$@#iFj@!c8&pF&lnK7s#S98b2z1qtNDsjOhiz>u*3r!l|Q=YD;j*MCAFtTqy z8}7%5m;PuV3u7xd1?bExa89&UzcZfm8%BFtv2w)Q34ZsAr{2(#-rh&ZI+Z7@=5opK zlPklS;5}XRZ_Sdmph6p9z)sS5(2j5QdmL>&hniMmx7ZRG(Lcax$%Pv>gU1E}GbMETEN2x`Lqf(ZW z-k&pG(LF@$WqksZiuZy7D8Hh)iht1I>^prAZdh&ufOOm1ijORg&?br9iqGNoVuT3Tow;Dn6WS303e{GY!Qt-WP;R;QaXx#eKZ}W-e*HrXFrn5@tC>?a9 zAy#xgzpEq|$`S^^Ux%f&SNaKgI};TZzkFTas?OCq%aVTGvrtPu&vMl*xMW-TJ8DZ{>AZ?8_e!v;~9H_xzf3;$6wmhxy5~}Z)v=n!z4Z1TQPmn zd?2hVTBVWFau1ePU=ipzM`6M%DGbE{nHE*=)O~AM6VW$ z^;Adtdt_O#IM3@-6f<+?;eoTyZcl=uGRrG$xbn+a|gzj38PkE}C zeK;gkC#jtCH^?DZk$pN88mmS+8DHX9QAGAT+bO2pOxu|sPWE$;nD3QSHSLE17S7rn z(#vgHUL)ZHG%MVFD?A*U=Y216#cau!3k#!limB4e zfF71BBpT$sGHYZ2cc*t#gXy!>!M8r3K`G-CnZAbJW{&19FO!Zm?vRXlk8S06}^3Q0J674Z?jLC;%YIpl!IA z!u}Qsz?}GXq=gA5Op=>|9MB<9{=F9mqhZP29RNuE>~nymxRm>$a1#;GkT>~J%x4n2 zoST9gAe~mQn2McASF#Pwna2iDzd^n-}!oC()*(3c)xGecXsJj z69LF0YjMaiTnPyHi@+h$52vqkJqx)|0l47_KFdU?OBro10YSYPcQucvw}o6N0L|xg zB9$0*0)qY=fS~7XGvAIGX4K5l0n~{`=`|ANHT!G`0Nj=$`Nai|x2Na;c}xWsDFN&& zL4Q&JKrGdM{OUlVu;LkDo!TMQ&bghcH)I_Ea8U61d}mdK9e}H%psWBuKtKQ} z!T$hHO8{BGGX#V`pMPG6$cTR~6eJ`>WK5;_JPpcWU-J|f~XIJ3w|NN{L>cs~FM4;i0^M+SvJ%L0`aM93SQ zko%nORb?lU_5_rk&(bvn4V{?e1?fu$MkZz!R(=6NAz=|wSvh$HMI~hw9bG+r14E-X zR@OGQcJ>bM+}u4py}W%sd<+c>j{rwTB_@4NPD%ZemY$bi@U5_@_Q<<<4g?cE=_5CDk(5bIx({h#E* zgUj^{J_kssf8;`V<^^wvcu2@JJSg}wTBsHv0$SeS=Y+2kaw|L0==ijuM3$}-=*0B= z8w_WEMEgs!|D0eU|Bod5mtg-U*CGHD5dl7VhGw=mc29s1)|*|( zNie9ydrjXw{HkgZ&3(y%T_HEnjqQbz+<2rF@!6LwOI49%>rlE^h-rj~kOQ{gc}uY8 zq~Ey0Tr4smz?{3Km8I5ii}v7@pT?nIG{`@ZMi0_88-MQ zLh`XZYqv8U7P)C}8HQGu99UKHExBNEDC!8Btjl_zWzB^7Y>RUp2fSA*+86J&acp*| zG0g#Vayhc~_04bur?ZrAE3oeEbr9LSl|iz`mDkUC7RCJkq8&@3!N%Hz9`pTP-Pwis z2^Ak+b8r6g)@5k@p|{|-@U=_}h}X7q)Eu>LeP(WdqX>IWQD$}%fF^Jn`2mP!3WO9K%>RL{< z@#Xg0SRg%ik{pt&I)i(>15e*x%y|{5`F8xG7Yr@)O!Z0^>a@x@=Tzfw&`n&mw6uJX zu68#{QB$4vimhvd^VtqNsb2-l8$-yPZB#pN~g zwbv?1t@z#u(>G*NNIMErs|?744t<)IXDt<&3rHE7&E@8uIf z_nwn8;;}Y%67O+V`QBij67Mmz4a=LU8@iujj@d#YP}^u(5%{gQ*o0!l@Faa*ynIl2 zxgQbm7Ql=M&5C+_ghm`~Krc3~!89{9I0)W+vNO)flVmGWa&>hLU{QV`v7B@VFCRc7OLxx9GqSKsK=wT`lD!QJ%qohWpS?i7mB%=W1(Gpt1~T9YeS-Q zpR!t4@n@CZw3d#}Yl_a`opwp7^};6rRP_)i$c#K2LeU(YqDc)wX8MCQhBVqmt`Wg#A!X3dA0O<#O3H!bALxk$Z$G4aktjM3? z7=(=o)AtrE3>+I8kc6wR*Z_pIsJv4tvHUrtL|UX`PATNv8H z9qMR|;n7U|wB!l@Q_UbhV92~0SaEOY2b}aH zEl|CZsspB9J(jQ9*DP6+CfZ@#(1x>?(y zODCi$PaUg?dY{asw#&%pF|^* z@P@XpL6i_XfJ?$p-uD0my;pf9ag$Lu_b_ytCjhd7RFy__9@Hz$-CxM`OlOf{d!=`~ z?@*1)H#R|B>u16@`)iDM%gixxou>b!ul;l#1He1WOMU08YTW!P^n7dK}R{xDXL z`ZrUG7q~-1O4Kp9N&>fQ_u_$_9zd6t*Zfa_C`zI!jxw&3b)`#xjuR4ZoC=5~Wb#$Q_1J7vCt|jzF@#TUOj(^g=3gMX6M{g-m9D3WNTO6n`M2 z*C@zBpm^SKl!0NgBxhP!R3VL_Fp%DV)Un3~2xbmruwOs`Xd@E68ck9P$&Ua4H z9tc}wz;n&!OF0jc5-I+lgo zqT9e;lxQ_KB7LyAr2bmB2Bql(`%V1D6M$e|))C~tF{-kpTNkx$l$)r;V5DclqR*-l zB>fij6>m03D)-H;EDVDST5oWZeE)LTt)TlV`$p~(EHFHuOtI}~9p!Cpjb3!(15w}z zGu%vwynP=Kb0d3Zpm?(Z?Uy|R2J>Vn&mH5RV2y+;RY}?x%#Y;By&X7saqMo9#g-4> zp(NY&+Sengs%Pt=_3OXD59Z}E9!2pKqwf|y3ZO`K)xq>UD~QcvHbU5hycNUv4eV7c z6?wIR^Z6T0U`d)~?~*6L##-p9N9lkWStQfcq!JKgz{bJsnRv>VM*0!W0hzudkXi0* z5c)$L+;o<1nl;Xtj#FLi>_k#FyI~YHKif=W+Fp;+U(>isW8Ig6GuRR?tVED-X1R#H zv-&$=1T!9^+O~C*zg1Z-$reIgm=ch9Y7*O~k>jTIg*7$E(I4<1^8y6lQPtS>o#K1X z>1Z(Snz!)LlO?weqX&KJZq$?Y0kJzY+jCTdw$cHRztjK6gU9&5tw~AZC%}Ae!4qJg zP+)InSE};#AziU+?VgZ7`;j)h^<@w`%e1v+>)+d-!|G}e+?$%{%sn?m^CMxWLC=XLlxoa3tcqQsF zq|BVcPY8P2i^a$x)w75a2@!j4OTx4CE``VJRbrS8Hom+KCear=(r&!vanyCH?(7KB z-QIR`hc+_kYKB|s;fIzk3{@q-3f`ZB zy}zxW>^q^iU)^~%C3=GHS^Y_{_ys!Pv=}k{<`*QBt)m^a#i;ynk@`$9B7bi#buli^ z&Jd;4nGpMY$tRw?B+HVGaVJh%$T}aP<_RE~e76GM3*sM($^wW2#=Iy|tEN4@i~7lE zxQ$o%4Tzh({ij}U$UH7*V3HXYe^TWXLh`5>eEDC`cK#>J{=a&H^k@RN3BskNila1$ zi2g4P)uJK&W#3t>SKw)Ct`a zpop#HjyDMXrW%I6fA4wl1gNh2SmaI6d9Bdhv&vNMTe>2TQFdpVVD<6~wqT@F4S&8|fuHB5CoDFfvrg!iy(B*tavXCjh$Y8cam>34j%L<^qdK?QwZvaS6QH_8BEa z$cMlKpH+#u-I>}I9j)0~)$%s~?Z@HeWwqK@ocrV{!g_k7;>d_OWe5NF`4mph6X31O z(xJ&?^n=rcO8v|B;98%L?-o@WR> zjtLqF<~XU?j2LU2{2>J@UI5T>wGnTX)D(xyt>9UOc~*2TG9%t`|KQqmIVM_u=p|)!FF2Z|k=$;VTD`pxV-;iBFa)@3hM~i?gv>u3=$K0t-ok^D5BKl@nJT05 z6ZG=cN^Y8uCP=n=v1DYlm>pH8mg>Fn+b{o39XP8M^Vcvnx(%1dsFxbB1X;6KGq-!n zC%|BVAIB4*Qdap1Fv;vVQ4D~)qV{~aSK0&QMKS+p8{#F?l{DM}XC0mZBp!qO6)|Wz z-a3X2f@h14FP$^qIMh!P#j(7EFtDdoiDwkr#wa@FDWa$Bzhv_--{5-TKq(1*Y^%t9 zm+o2r&Gh7)%ZXEaXVtk(t8O5))FS!wI#f3r++#B7_}V^!dKp(NttCznestP4m5`My`SRWtPX zTW9c*wQ`ooW@ENhc}qf#Hk+K@H=Lg@x)Qt3?jfjsTqJ%*_e>8AW-Mn^k?qyjkE!3((jW8x(Nh1fxrSzqg}L>V{CkB5AapInz5X2NabJn?E6k;;D`${@ z$>_6CD!$Ua(6c4MIHeqH&gK=8E4`ashy>-F=2RQm%@wTcjU^ot{O#Qv#!`nXxWgTSEgb)#St&MYI*IMD8LIj@ zu*EHbFrXLos?26G5y)9U;#LsH0$!hf=*}1d}aZ8%wQJCR6T@cFOJO^@0Yo`yiM1M6>cZ zWFcjUdE5gV$r_r>h_`EbZOL%^{|f8%46HouQ)Prc0TwkrJ{V!9ez4S_ZC+3%3l=DL z?p?9MAz*7jD$8{}ird{O`D?#egfexz;Q49KY)w7zQQ4R zbuhM@2HKi0WOT1pX>=KLA>RNG{Iw^Clo>Mg8Y`p54)v3llf*0g#)!a+CE?2Gw6s3) z^`PJA0GP++Un;7d341IbjeU&y^(UbrlNV?K_Zm^Wcve>SoH{1tdCBG7Fx($~eN6Y0 z+I#{mEFMDSdsq50AyNqPn1Wpx%-g1BWYNRkYAxr)2C-HbX1W$FC>I37k7tQeLlRVq z&1cvZjz#-d^+I#kQw*)OwJmDwbNJ23a&nrgd^`XEuky1XHvQDl7K+lFCqR$jox(@B zv8l~Jm4l^O?kYgTu6ShT*!x5 zpLo#O1$&2c$x52Yb!6nd2)l-kG*##tv_G3fYA?l4;R(?7?)nK3J1266xPBVlM(~&f z!>_>bj_)>KB!dk9bFK4Ij=VMfg0?JEA|AV0C2xrF=KwiTK_<>+nptH zqgwW$2+c}z%)hDV)>JJ%;4$nb#68sS9I3~D&71#A-htsl!L+LicdNhpyd(&nO|grT za9Z34GRh@8={&kxTDH<_We4z(<)3U&nsURS zcniX&siShs%9h!@y_bRD#j#~Fao%0O3CMMHvkJ80TUuT+k(*F+VdXJU zQ~;E+J$p_^9m00U^urC3-Nl9J2ZY8fl0>^p2$@e7F8f6f$lj`J=y=brI!&OFoeN{4vNy!cW8HCFu72}eA z{bOIgE#MR9U}|E?WHWfpT|Q1lAb|S8^v0<40q-XB$Bn9K#NdoOMt^no^Mk}=DfY@! zJuC9)+KH&xfdkM-Y4YtqXNevGNH;i}@lkJd>sMbu?V=tOZIB9ARb#o78d{=60GOIJ z_R>zgUkSc)UslYV&Wpj&TA}yr5#90VH`zK9Omlu-fklf4sY0Tc5lNiUZtU;JW+&cM zH}%Y(|LFY^%tvJVI3CC#Sp+vpmm8~!==R?#uz#5({BR^(^rjhZy%qmvz4reLK@Wb^ z-?RB?LdW7giVw`O9(>`xkcatR98do5t638$$njf6ZkIBz0I+QlD}m!ffc-CaSqh=j&`AnG~|z&Nr=Gaos&D#7$P>W0t*&u;IpJBDn$c4&I*6JXOlu$$rmp6lcp zol!jj&`Q@}LguZyn($;Zy505(0M5qwjdtnm+hN8(!QZT%=Q#X`zq7c?F+${}v`1(I zMr2Bq{=o^4Z*NOs=xFd*(rtE*MX>EE z<>^>|_5uC6{l@**@iT?M4Zss%XeR?6aueNAz)6=pX0Qi53m6h*ERe6cJB=%c%vJoz zM~F{mMX3wok$3)%!NR%}l%gC}sZaE~77!>YMHqm0lf9lD%`pC8ock_-YQ_?|C3*nU zRu_A4M`6uVX_zjU7C*LTJ$={l+WczwF+24t?=huCdE;4n*vyvfy`w^Hee1pGV98Ra z=wiyUoFc7{C8PQ?6XlQyY$;6sOvXd=8Rs%5=O0`y6*fv7$LleJ?B_h_#ZkOQk<9G`zdu89gUc;GqhZNFn^|96H#`Ctq; z4C6n^pv4Zio}|WbYBWP zBMG8!<(Bo#h&@S(tsWwYBBaI~u`<$3wv}m+vyR0X{{8Qc!#x5~0#LoHuR_nNyuCV` z7-pMGTe#j^fJ@l5IE9qynHi;)c zoN&rGGJgF=zUzHDUXM}L#y;OoF9Bhj)-(iUfEt*w{vxi=a9 znvzlmCHF5!*(9}-;-~npu;)|_?vO6MPd6&SFyey=L+|YFyR`_eA2cL9#s?|93&ZsC zX>w8jgS|I_hPwU#hewet*_RMg_OfNK$Y>FgEMY=SNJ2|7+ZL_ubw7{h#yy{hsrj^E|)j=^TgoG}p}Mb1m=p>-~Da-q%$mDx@V4 z_9!fup}A0ON6MNZLxYmpkMRORoYaHCYK;41j>zR!5YA?r5>!@%8VfZNc5cu+>`Oan zWqd$(O6rbdG%75 z`ODQ-wumFsmeNjfohQT8%So+JtacZx339X&^`!RL+{wuk}W7T zxD&e(zZ`Xgw^%v!xCioFoz|NB7tv_l}~i3&q%c^i4vyO0$vlhm5E`<_g|e z?4A8PhhEm+B_?Aga!@oRmARJ1ozcuk@>O?~M{*gN%+&2U_Z@BKiRV8K_x3liw!RtB zETpUL=|oWAtk>B&fnY#kGXPkbj-{CEGje)TZS~3>->|t;=AZMu@^c)(={0XFv_NQ9 zcxi;6B61?7Dtr4NH&K^So_BwR zfDqSM_;Y}XEW8E5^3K4{0ds*)xya z_@2tsJ*i+K@uZmJR;cssRJAX9HnEGN#lG3x)`XhH$r1?sC8?#sZP6ZE!gp@PHD(u* zQ3y`~sjL>vs_QO<4z=LagIX`+h5!^6BDy~sN1WijHdX#Uk{gG(5I`4h-aXwRc|u@t7pn$Nbt=+tuy>;V)tRc)i~hS1;iwfYQqv(|Mm1g+)R_>ef$a%H)m^WK zt-vQEGC@o`nb_x;Uz-2c&T~CjBI#@y^DxgtA!`+jQMLd>!$CpaH-ZcpG#ka-c;Pa% z6a$EEo^>=)A^_u=yWLgr88HMU(NSRWQ;d(+&BUe-}$D!`!uGACMxBKr1t6#xSg$&QQpVbe-i&u~NL$m@#$rj8%!Ax?W2;v7kQThQrA z1oWdH3DqII3whT<8XTBC3t``p2L^thf09(_eJab4ht?7i6fmdvUA2r~?bqUKV4G^h z?2WELFz+P_LLZuX*?+xb(ymvCrY+x$_|V1V&C26=(b|QYKJ;mO#o_&10U_9Ne<)U8 z$`8Z&Fy?{n^eE@0BvFg_sR6TRwhvT$j;om89*&lIdci|{{vcGE{?!H=JBXlx&1HL^ z2Ie*%#MlwK)aiPLB|etDjV5YuUSy7%BCaFOCvXfXtmeJ_a&exEmsgWM?-iMoVKySi zHxn*S!^M-Xk8%LP;jM{16t@vuK-E-DI#v>KT@cvzz7Mo-j{=&5-&bi~tP?f)ww49e zt&e*Es91@FUC5Di5)a)Zl*ld>2*c?Qp;p=;>vm+{>Q@YUzfQFpwF|koOjH;i7{`=g z2<$DBPIZU4SD_4cM&nG%T2A76LIH`gbmw_5BywW~&Knv`1bqyz_CJPn)%g&47+0nsh**94z&9Q8qv z`DAP);FBBBgyVNHxEYA2JUd9-|K7!VdvA>S@4m6_&Mu@BUJtUBcAJ$*^oqcH_@Ar> zCP+dkX$pQbD=75XPc@0z;$D`O9S73!RMxs^tgaLkgpj>me3+rnrTEi%_8h6aJ(8t| z&d(1w*Ee&%tEhMvXGspyI+qqy4Gk%yuRZ7&#lGWQp&=FrjYhj3_K`O5#4ccu2f)fgQ3x87g5{ ze>~A_>I;`mxErPIlv3I9L~dK6j;tps$6FlClpt?zT;b%YQd1`=&h3&oyLHiklx45v z;w#ZNdeOJdM%)~vxZGRm*+&lep3V7ld87Zaylc8QLUovJIVuCtE)G5xKD0MXZ;Z4R zH8`_o-FoOEC{y(G95p4=oqZi3l7#xAOA?-dv<-yRt)lX&BpFp?Ya$i2!9ypi+#J&y zAeP7?_ocDe!hpU|a5`Gs*>G;WjFXUpX_CV8Y&a1mlB&P$rQTpK(19e1BHXnJ3D)}I zhY8ngN!+~?J^8mPtZh?81+G7>&`m2ed5`+?K(-TVdKIzNwUz}~^L@lf%tVU$ zKH}57p4GN-5(|{QY#_$0?`C-Bqv~{?{Gw?hpTejJB!T~Y-qOD5@jHJizh+gAdRt&4 z>rq9h`fGc3dl642kg_k>{6QdbxjB6KgFt%ofV?h6{<$ts_*~Pf__U!EDmLP=uq-}v zN5s$l&`KUF)Y%3d1I*V}kTjZAuukd4P4=_yV&9)R?G1dNB@!J~fX z3L4NO>6`GFO&IY-DU?>-`GHq@2fb%Rq?_|h@EZrg4?OiOih{x77oR4Fnbw~u3L$nC zJcdows8IRnOzc8@m9ID%U%U9(QLfH*CR#}GDb1DS;?+g`!NbFD9*?8SDfh3@?kU~? zi=_fsEVro|**z93`%e~2=Zx(QLEDNTz4)6l8vB)_RQba3$B-XXM2Us9!ms_)S~j)M z^xmzkEfBZPU(eD!CDH30Ug~%0pyat3L$iTRt<5{cSZ`LmFz$@DK;1B6NP9MZ$A7(t z%W>m4vqE587vbO&-NJ0Q6S);d%R@Sw_j`6B*04uOQhw_dEDM8T?H=9^>3(LHzFm?K z>|}b-$G;@re#H63?ZHEy5`V}djHDuJ1>M~yT_gF@ryNjf*d?@F{g9-K(p>&AkITge z8$P_}I1%Rjd{T449`+t2OPN=$8KkzEFeCGRngaKBuVQ|p}#c_^qWb6n1+0|Z#w?0RU?Pk3CRc!V}6Cr z9+|9Y8yd_aGiSI}v@B0a*cE7E35Ct|&yobS+)+Dj|Nf2*;(C|!g9pxHTJnu=no>pw zqd$Js6nq}7DR1%X$<@Rlb-Zv z#BK)DDg8ZS=jZn`P7H!jL~HDe(d=<7B^nJkHxEy3?ra+Pjg1}r7~LhnloOOL^eh@g z^)ZMyAfQ6wggY`9ieH_Z0-3%GPChH#Bo=m=qK3)2=N2!511?-`(}>nlT^0os z>kmPX39|jnl|KYM<77P#n@l@n;7gZdpn>6QYe7xX4}YOqU>3zDFu3De1or(TcFbmA zgHu3dFrN@r{zlFH&sR`ZoA&l@(ABk}uh4l9J^Ytvs;8xJV&eohhIwuMwMh{_c~ivO zgJI^!YfGZSIwUuZddqK88PdMi_YdV<4K|BTC%ON!YS{7p|4xEpjWEBD3_uNJH{*x- z-EGwHYyz(zQ;y5ng;*i;K|D|lfBuOq*rQ&z2SW*s-i6enQq=Uv0A&4&Sw_$50XZN0 z1g4Cdz^48lQAG9FWc*7c6E1s8(1amb*}-}L_W6tUFLid59cq@y zl7n1VI3QVSl9Ps+v62r2BIu*a$pB69c zPPTv7P_chTf0h61grf_uMnO^TESNRt0{~J%;&4xCsILl)kc1UVC~y^IttCPoRVPdk z_BO0^0;LHOXHi9LnHVcLQ~LIFj7vU9hyRg zkR&TV)gHD#lMtZHWrIgA(%l%b<8IH*^{Br><1pO@3Gz3KOa6!_-9TMK)!8e%xlKX` z3rp~K7E_^YKgv92n6YUSTQaXrBM;{~v?!gVlOqk_3?)Y*z5E1iw zUFnt2C0=MUrwC=z&bXl=l-eed74o?V^D)&VZG^^xnW~vN`6J>phzd|#1nA0mgZ__H z>J?%1R6dR5w_;Pg#AOdo&Zx_I@Z1nDOlN!OkOrhoJvE3;Rbl}_2YETVr(Il~09PdiPMdAw;*11$!?2l2E z$b$1d?-_RrT4lHp z%uyr^cu+S|vV;9*G=^grGLa8meF_8o^cSUFh;~^C#ahZw>9nUJ*}#i90R!Jm1O{Hq zqTqDFC9+g(kpx-}j+3QgcjYMX{a_waIanDNW0#^_vg|mG_VP7N9UYCiYziy<@osLG ze`afhF|wG6EEM)y!^#0|ok?_qk1DEOIczg&slr~=BJXA#Fqr!1%I3LMQ9fJnEn``q z?|2KK9`n{G>pqNtZ$6%jHTugd{Ut-&i;aN^?=S{W2P2KcXU~#-8DO!$<$*^7kLQ%N zf87%J9L7%6KWP=<8adZUwu;1N*k&{4^j}hv{ad-4z-Py3;7oxWH2}~juXG2};m=8c zKCy)Y_Cy!3Cn4PHb|AH-wD}gi^ED0(g_Th@Fs%0nmcx)|1Pk%j&1>nli!28}nJ(~Y_ZB|;@NubLAsFKS zfL#0q$7Rlb>!HqK1Hl?e*y({oneaerCIwvqS ztxrG)`k_&Ey0FO`s5=A&oPE|Vq)C0v)ZTNZco*^kT5M_xo;)G-1=+(H5|^&Pp2hwq zzDI7538!CrjKE&-yTAv4XR;TXC?FF%B2IwVX(obR*Sfto;of4vggcHJ9M464zuP}6 zN9L$0h}?gBDT8v%%C;1KcDv620$(f0wHq)Yhngw$&G0%7Y62R@ql4zuNA;cGsKfu{bkx}N9X*}vjQMmV(qx0CgcBe+mf6CI$J ziV8yQ7o~8=;2@+6!Yt@kfH^&K0zxz=b8u5#ILQJ+l1?CO!LbbdhuARTWkLV=zc2n( zio@6gpq^9623;4e_mxjy-$`VAAMo_qsODii_$@_}1wI+rJ_8|GCf|lt2JLl%x*gXf}B#B7YKw${s9nG9uC-BO=QHfQVjQgHPoD zOIFUl17-*1{#{5CnDqBhK>hV68~2OF(WRbiBEzodpk4*hwf$;*3($2V+=N*&82^>W z-2+^EplccaZvb6z`rlIAJ_!8Z2)baLo4`QKgMIy83nHVz{v6cz8zmI)fAJ1rnIMzF z#FQi(7!1}IWka}(i+T<8DPX2nd@3=G-Gw}byHuty5~?4H1`^?80w3Q8?bE@5&m*u> zlh{A?)(UxR_F|^ew;dmzWto9}Wgc~OWZQ+bcb$(yZz#(mxAsA6?vRzh1ySHjG5~dG zuQOz?kzCKmoH~Y|l3zbs6=!ULUB6mkY)ySE)B0!&Gv&=d=W@%-5HwaFjXN%suu%2# zhC8hOT7%aLkFGB(e_S+B&rkCkp99pfM+E;VdO=izrPj7I5UknNo?S7@eDk9+^+mqD zXaAS-oq!jpds~X|EtU$8+wpLruTi6kiG$IN(VD_(pIwr>n1P5R;DyTr6bdE#<;IsU zmwP&##R_fQTm_3|Su{liqqYA-UE9C#G?I4U-y@aAc!|7VvGD%@sRz=Lg9tLk&W2fV z>c^~?*{vn**&0&#Uo1?H={Sy%KwKltpMZz~x_PetSA(>L#tAEiuVVto%iS8Ph-L+! zd!{xW6l852V_G*ut8DWf>WrdK3aq`3^jZ>P^*W)^Cg3#UNnrooH_PMuRjItUK;&rv zBBp{t&MA5Y*8$E4f<_ODE^tM~pIXlU8ueknk{)|vq-u`l`vbL43QiFV*%TKdS2UOa zxP78-q`V6mcn-j(#2(nRMb*&)b4*YJ3&y{T6h8!SD&0&VFy)f2{u@xP*bq43ByEuf z^roUI>O1=)QV%}AMc_0*H4Eb0fF2wy!y2Fm|I-lX&F!2W4|4I^i~edcwZG?}JE}Ujv*g&*q70VkGj=7@EoF8Lp-2fx26E9*b;#tX?+La6G-< zu*_=l83^5F15N`t>@r{q_Eu^qng+E5R(Ba#DquA~uKl6X{hk6l8U_p@d@Dosc z5CBST^R?jx&oL7q$XbmY2afd%0B={}hU*RxcCxdfx&v_BBit^8G&#QGPzs#Q9*tds zYEvbk{v@$KQFlK4Ga_VPcOtN&Hg#88nwKNZSy#-10UB$Pnq*`#Hk z`SJ>(=Yt_IL}k6=)@@<|1){{~$b$${H=xeF$IlxFT;(8B^oV|8mM{JZX%gYRr_t@azV3pl#nO!ulP zoC$|KlmmBnAI0ZQBTWRl|A5w-GIou)3_u^qxST)`BIwV-i)VP=Id-z|p@fm2sr zE~feB;a#%}u>)+hmFofL=M1qF+o5Uz<2LxV5ZTcLz|P~}RKP+&@0{1+!Z~T-fuPgs zrTgpaX-UQoP7VmIq)UbePE9IhT(UXM6*Xj32vFjR|gg>m`Hv|J7@P~O5b(Gym{J)09f#RV;|sLsr+x`c87-0w?P`&yoX|e6qfi0|YKW-$_P{!Wgv; zfV5_2fqlxqpo?p9_CfjVITd&343IOi>|9!O|M}irRKp`iB0KPjPXJ7G`9wXd#=Zln zN(M^Ux{*4G6-pdXC!Av1g>)N3aaT}VW)Sbwco?aRBtm?R6uFMmsdt6l?R&E(Sae#< z$?D)y<+W4Px9p>0{4`JGa19xd(g<{*Go=Z_IG>5CWF0P(8wAzQ%R(U+va}yOc-?X< zd-nFJW95me#*|UZG9~$yl%iJj^JDFJe!x$25F={2)L*zGg)93c8}I+%uAZan%)OTz z-U7WZB=p32-+8ek@va`w{;d6-$(yP;jZRIRF?L#THj;h_z@3-5%_?9`)bZ9b5loX- zx717eybtBxNKR~+TibuTw|!0EY{{c2%kXVxRSQsTazY_)rwA~Oyu@tGEd&>`g{1gc zZN(wKSYlnpCnEjUvPAffuVT<>+|{qQY1`WJWHNcoxT>2m-n$Un$e|w>h*>I!?D?1m zj+R;1`X@CnUB8l_G5W;fP!FWdO+euums|EN5&O3=e<*A9eiv-+6gCW*;8Q_Km8~RKJX^e%TQFT5{rLckXo^#v z6mQ65N=ZyZ*(|e?K4Msxt3{^Tp+$BI=<1N`t@d6xnq!hj?;pU8P*X$vxUP%kicaNj zDQylC!yjTs;FCP=_l?&bpp8bWd6mKyS!%LPuiDHMmrn*Hv_^5-`)i(5co(4_PK7)S zyfgw#HWqJ(40rhE$J7R0#i`}SG2W`)hjmVl(anw=8j{JKx1+8&Z*%lc#@!Mkln$~d zUEJPRwLIiBeGy&Uwa7Th$R(H{UH4*Muhm^*GQu{a|NUKy^(Ew{V=$bupG`!KktUbweWYjmUJ@!pbD$u-1F-$UuVe z%kMi+;GZMysn$&DhXZ0NHD1XKU!Ci662@7!3oGWt-dm|khamTLBVLmiryq^Gm+og~ zg8T}nagW>z6C*<=sObOLb8F^FR5e6WA-sx)lJ(u}e%K}OUP|M-++md$E0@2`*9l*$ z2pDf@r|fAw>|_x2=7<3M2YNkUL6-b5CdN*nWO2V>v@#ncv7W-H)#s98wD8uivw3Jq zQg}Ec`R*N1ysCNX2t>z@Xahd>E~-w!0cM@T59M^Pq_bZR8u;`ygm#ZfZ@9opACMRu zC73Qi`I5J43~`+p53`62L>E!zH{h;mS0r4o30&CnY))51`#5txyP^J+9Z1b6F9z|n zhn0QPwIBSW)TdyN$;djePlzq2?Eklm8cVzUQZPeHJcv)7i`TMJrk$$pitl;S{FqVuP48!&8q^#0zA7r< zj%!JSD#FAhB-b1_AN8VNRrad`-v`IxVa2$Igleov zixU??1{W+6`cR7XhlyLTq_FIldFIj6NgGLqag8&I*T0P0^bf`3{ew#&_YxY z%3dnKoVOb54ISna!Df7|HGDkUbT`3ufP&+$oUhcgd^T}KjnKDRThG!8I-F>b^(ZcH zdmv+#>6(lTKUPsOTDZJk@euD^s>R!Tl|^v!s^ZDk_gsSeDDceq!vyb2VkYV~iq3tE z)I6fyCm-}$E7!||uCb^=`Khe%lHv8{8^;B+ZYnA$LUlph9Yvx9$}Kr$Cm$jqs_ z3dbkn8*87rEtwC!I)!D&q=RqWOVl0NH!C6xYGGmfVRk9pT@C0`>c()8t|}{h`*!7O zrjvt%(_tWqj$SgnsCEM+7a(Bo461vEbLi6b%`HSJx{1p{N{)Exvks?S$DvXGFYx$s z4JRQn!D|;gjugJFgI^-=xZ-!nWAq;^#D8KS@DE4_9aeInRHRQTN3mwzB@t3Q-+##G zmgtwPnhkA7i(hVYJ#kp5(pB=b^ig`6;R$F?EP&_82f(8KBgt302nKwSaJ_g%q*>C>mJ-(B-PX*zl0FFZ}G290eA=VPYe z3-Z{d9j|b1(_D$OC5aYqD>O~I9fYHY1ylAHUy!}Prq32*&mJd8D-b2S6o&&}O4&rL zeL;Mx?BMef7bPizg@p(QzsU%H6}4{L$If}2NeC1W2+`JBi95#$HVdEz4?)2_azxo@ z*=`vPeCGU=(Nx+|ohz=$d?r9$UH^g@sP(#cj#8H@>Cft9m@;gl8g+m_MgyzfsO7#G zedjfbVO&W|B;`y|P19S08HE>GH60DN4WK8XQr|X^Pf>O5B-zF!xStB2Y5UzZExA;F zOjwgh3T;kJO(7hge4}Oz9{*7Tse5U;V+R5hucU(psA(2u5Di=TbdjQr;WLNGpF)SZ zW5&|bfE2`8d7Z6^FL%i*t9VT#e5_Rd4$JA6Y9ry@5|t{79M(}my8KqE(3fn#r1{8y zgPa>sVGW1s5@Rs8l}5cT@+hn2nyaOMzg9vgXB6|HK;X*!N9uMq{kRVn-#onH5?s9c zJ*XGk@#0N*1gYyx!jm2^Qwht{>3wouRWTwl_HKNtl1yK7n^zk(8A~qT5Rd`2FU=B& z&8N8FDaOOLD*Tw0I{Z9jkPRXRtXH!~ep+PI*np>OkaT5^a8 z2g7|GIl_%ubi-B|m#k8R{<~IKEr{Xvi&ha!G96fyTZuCda zH3^IbA}o8VI2{_GB9FgWh39-Gm#v+dh`GI7GTpkm)|HHDA6au8=|H{&lT#XF*^U{+ zPjpNUG?f#CRm75%YP4@grQCg~#?PZ)7ov2@+{i|i!Zz{#JUBb)`(^Uh3VtK*0++Fw zi-}a5&nx{#amfvzlj2`HO0rxZp1hUXf8|7Bb_9%nm_AqT$-Ag>D(j2H`von_IPtB> zk!D!g1}W*Y-w28MEPwlD7LVlGKHfvKiD8E@D_g}!h#zO4Ll|Sp@uS#(N(oHlBgav7 ziYj8m-e$xQ0+riciI>TjdSlL4mWO<|vG)*v@9XgllCH_Eu23(-Vf{5s8nSglG;jH^ zd$|sl@rjF>o6$SGaNJAd4wM%xzQ^O_#Q;sHonME){=KxA2nsn0oHjWHGD0AyKrrMK z$WU&l{R>>x3itFhV^bt6u5!Lbr)R19TCUmrn(=iSwT$#HS5`~(JPHI;O%hI2jgCO9 zpM6dB{6jUAe%wYY+vIxZ*QfECwSr=Yyl#0QgrW`&1#j5G-8^Tp>_GzF0rWKo9zr+45^G>0Rf3%VG zFthMEA@8be1`6tX*PicV{IIdnLSWWuaYZr{O9pf*BWAv>T=nKg2b$k(;xjoY^XLu19b%e;)D2pH*RlEov#_d*9}7#a zS)SDMUQBFz3KLZ3#Zwb*uM7mG^(@qKxp&efKb?W<_6qjq2Q=C!Wxiu>45Yos74YrY zC1YRMn_bA;mW4vjg-7_awzX_tq4uMe#;^KZLaR`+trb-nCvMi>ce=LchyRKvj%l&H%QNHWfEAx z9yRVNJcnSD1&!gVjE4wiJACE$0Q+_DEXVL#2rKNb#9o$Z;2L*0c;YHcT>uYXyH63J_R}jsMIN`Q&(7)2B6;RKBmJi_Lo7eG+%C&g`erU0owB03`LytaAWH=;jqV$R7OdIdWGS^eHgqbr z0INRb>OVC~>jVyhkYZV-ugcLXA#`gN#)UanMfIKh647u<9$$>5-t@&{7D3e6-jvFM z!QW1Hd-gR#m1U~=CWX6*>eW`bfPf&AVQi61Az^=k4u}@)3%E#tNYIt zQ#>QdRDvrPvoW9&#PjmHaYypP_$@-}`GDCv97_!z$x29f>aE#oXs! zV{aL3e`hf=M)PoAY2V4~i<+!=qZ)X&i}HfM-BeO-jTKZp0XtpGPf+_zl5ii_Ul9qG z7fV5y_J5Nd9ql}4%LDe-egCIm#n2GEI3V#eP5<-vLl`Ym9G5WR6bWvU4R^t`PxYt-9au)ufp~QQCX@6Fz-> z?cjPRZ?Yue&Z=p>8?q4OUL%_ye?T+5Ptxd+du)3e?04C@0EBLQ1EHaCVw*yZY>F89Ef6z~kP5N2)DQ?+P!LE&?+A`> z1H8N?OknHlsi1n?bA;#Wp8zMc;F-ks%f+8qPqM1^71GskN8orn}k5{krQYFr(z2q-6!*| z<}vPGZ|8fDw-i5pIV3z8Y;$|<{KXX*6}ziC-@XU$qU`}S97tj$KgmPXmJkq}s6qjU z4R98yCw&Gg6!btUA{GVaH#L#P3J7S_cOmzIegaGlkQZSjK-V=RN$gN!t2zOOMRg0q za0dUikHMqy?8w0+k;N43^B+6jMmy#O)=H-Hl_#t4SB9;eoJ5P+&bJHNaa|X^v8<~Z zUbrAnAbNA*rl-1wel)jENgep%X2S92X-!3v5Zu`Eu49D87a5HT+7UAi`kaP1N@SEb z;3fSganPnR1qBpSiH}yjXR)=URNhO)N<=%8@QveZ)8j{7n1g19X>VVMKS8m@%f5yt zoJDUUcOfr!K_*7jGhr7ZFhBtWU3B)?s`v4coR1gfBYEu((Q2`_(Aj=I7UpkqS}^hA zi?~b6@f_DGE={;OKz3L+-w-oOoQ4!9UT079n#0O?%-#-u|9<9zWXAnj8Ws+MaS*+c zoGa~!E4m(a*u7YHWjWMEn+*3rDS^2bxs-scBCd`sRn?2Pyq89i3VE}O+aKW47 zUbb9Yd6Vh%P%bE=tk*$8g1@xvZGdK~Wk?o9R1w~LNUKf%R8&g59No8ve*ml+Bp5@G zND6^jqM6KvGB5-|KK6cDp{2mHtpTzMU&L25ORKX_={6o`cuY@oeMp%DOmo9-%tRG} zZnj4Pp^Gh#bZ{DDvw!BWxF`~E{Brnww0u3M?hr-DaesX(W?7+(1}sl))>8-T~YlwiVo+yP;J{pz8j8MhZGbC1{)!^wDkXg)azqk*2aS zL8i7=E6TF9!NhY;#}BnWyqCAn(dlU1mqGZjBGL@ZYJKD=25TVUwfO;C8#6hocnMu+ z&h2+hIR9p4=E&9JheEFEZcI&OY&v3h1r?W>Ux^QeTW$lD#vVoVCUBCo(LVC=I6Fal z>W3*W%%BommT#ZiV1pD=D0gy_xm>66uKAsw7u~O@vL;E3TiG9Gi>pNo|8YfX+kKrA8g%mITOrY`R z_pr9LaJbxlFUGDp`hBgv?6E`fkJu~%uCl$Q9+D&PleCE%!jnKx6JE?C2$AM`D{CaW z;0e^h5rnZftlI^~X?Hn8VtMJ)plw=-U|yQy@>iPUs@<`n_u!48tA&uw0-R{Vq8sOe zKOXjFULn7#N#&S~Z4#TUIKPICbfKcPU?*;3`}*WzPWPy?U`58pkoj5}Z^sq*BGt5q zxB4{i?b#cjg@TW+Wf+88g;Tc$b5mV3*R0Q?N_@83nEJPLO9|nJC_zMk8d@gJY@A4A zW&EQDD$48rk4Hu=%-|jV4Qi6z0*hn$C1r5xq#ev8oWN}9VQQJy-K|LhYn;b# zdRnkoa`yRMzK4jN}2CyGyXiT?l$Z6SF!8q~MpyuO5?vfk~@R z|7qm$_!b1#e4N>iTiJFJ9cUsMl|}PddH8jhpL&j`LhShll}H zl9&<3Dd?hJPGYsi6i;&1;h8mul6NrY%weV;_T#jKjM&R_f<8U$M>$UwNP~L-yugHS z3KA5V>`Bma*KcFU2}R$>=up@eHPDC56Ay!cYA?x{q5_0sd%A0}b<3ZZSpQIBi|z+L z38;zwNB@Y;VTRI4G)^ms&E!vx@?J`GjI?*!@01(CYXxi6}DG`WWDJ${^|mc|%r*BtX|)P5(wG(5btX>nsA zo2sUoIZ-I}(lJBkOIp%`V542(+9e!-h?E>Fj<3qm*f>y6S}A)>rzreHo5L0Dl##9N zfbrWWpEVOll9gP;$4yBeFj#Fe%dOT?R$xul5~OUwCamyzV}JaU>N`br?&__y(fN

(bh1c?6E)PxYy?y!d)-o>Axtr3=zw)s5|( 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 z

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 literal 0 HcmV?d00001 diff --git a/examples/04-pwm_led/pwm_led.rb b/examples/04-pwm_led/pwm_led.rb index 70792863..9a72e598 100644 --- a/examples/04-pwm_led/pwm_led.rb +++ b/examples/04-pwm_led/pwm_led.rb @@ -22,7 +22,7 @@ # Note: If you have pins labeled "DAC", do not use them here. DACs generate steady # analog voltages, not waves, but we access both DACs and PWM with #analog_write. # -# Set up LED on a PWM pin. See pwm_led.png in this folder for hook-up diagram. +# Set up LED on a PWM pin. See pwm_led.pdf in this folder for hook-up diagram. # led = Dino::Components::Led.new(board: board, pin: 11) From de8b22af1163b08aa06aca7139793cc450b6cfdc Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 26 Feb 2023 13:10:19 -0400 Subject: [PATCH 292/296] More consistent pin numbers for some examples --- README.md | 4 ++-- examples/dht/dht.rb | 2 +- examples/i2c/ds3231.rb | 2 +- examples/ir_emitter/ir_emitter.rb | 2 +- examples/one_wire/ds18b20.rb | 2 +- examples/register/shift_in.rb | 2 +- examples/register/shift_out.rb | 2 +- examples/register/shift_ssd.rb | 2 +- examples/stepper/stepper.rb | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index d79fa7fd..ec6eb8e2 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,9 @@ button.down do end ```` -Dino doesn't run Ruby on the microcontroller board either, like [mruby](https://github.com/mruby/mruby). The board runs a C++ firmware that exposes as much low-level I/O as possible, so we can use it in Ruby. It becomes a peripheral for your computer. +Dino doesn't run Ruby on the microcontroller either, like [mruby-esp32](https://github.com/mruby-esp32/mruby-esp32). The board runs a C++ firmware that exposes as much low-level I/O as possible, so we can use it in Ruby. It becomes a peripheral for your computer. -High-level abstraction in Ruby makes hardware classes easy to implement, with interfaces we expect. They "multitask" a single core microcontroller, with thread-safe state and callbacks for inputs, but no "task" priority. If you need more, integration is seamless. Simply connect another board and instantiate. +High-level abstraction in Ruby makes hardware classes easy to implement, with interfaces we expect. They multitask a single core microcontroller, with thread-safe state and callbacks for inputs, but no "task" priority. If you need more I/O, integration is seamless. Connect another board and instantiate. Each physical component you connect to your board(s) maps to a Ruby object you can use directly. You get to think about your hardware and appplication logic, not all the stuff in between. See supported hardware [here](HARDWARE.md). diff --git a/examples/dht/dht.rb b/examples/dht/dht.rb index f2f07ed5..6366fbe2 100644 --- a/examples/dht/dht.rb +++ b/examples/dht/dht.rb @@ -5,7 +5,7 @@ require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) -dht = Dino::Components::DHT.new(pin: 4, board: board) +dht = Dino::Components::DHT.new(pin: 5, board: board) # The DHT class pre-processes raw data from the board. When it reaches callbacks # it's already hash of :temperature and :humidity keys, both with Float values. diff --git a/examples/i2c/ds3231.rb b/examples/i2c/ds3231.rb index 676486a1..45d3949c 100644 --- a/examples/i2c/ds3231.rb +++ b/examples/i2c/ds3231.rb @@ -18,7 +18,7 @@ # On the ESP8266, 'D2' and 'D1' also map to SDA and SCL respectively. # This is for convenience when working with common development boards. # -bus = Dino::Components::I2C::Bus.new(board: board, pin: 4) +bus = Dino::Components::I2C::Bus.new(board: board, pin: 'A4') # The bus auto searches for devices on intiailization. puts "No I2C devices connected!" if bus.found_devices.empty? diff --git a/examples/ir_emitter/ir_emitter.rb b/examples/ir_emitter/ir_emitter.rb index 92344e27..f3678c7e 100644 --- a/examples/ir_emitter/ir_emitter.rb +++ b/examples/ir_emitter/ir_emitter.rb @@ -37,7 +37,7 @@ # The IR emitter can be set up on most pins for most boards, but there might be conflicts # with other hardware or libraries. Try different pins if one doesn't work. # -ir = Dino::Components::IREmitter.new(board: board, pin: 4) +ir = Dino::Components::IREmitter.new(board: board, pin: 3) # NEC Raw-Data=0xF708FB04. LSBFIRST, so the binary for each hex digit below is backward. code = [ 9000, 4500, # Start bit diff --git a/examples/one_wire/ds18b20.rb b/examples/one_wire/ds18b20.rb index 49ce8f43..4ba58256 100644 --- a/examples/one_wire/ds18b20.rb +++ b/examples/one_wire/ds18b20.rb @@ -5,7 +5,7 @@ require 'dino' board = Dino::Board.new(Dino::TxRx::Serial.new) -bus = Dino::Components::OneWire::Bus.new(pin:16, board: board) +bus = Dino::Components::OneWire::Bus.new(pin:4, board: board) # The bus detects parasite power automatically when initialized. # It can tell that parasite power is in use, but not by WHICH devices. diff --git a/examples/register/shift_in.rb b/examples/register/shift_in.rb index b179e9d0..2c193d2b 100644 --- a/examples/register/shift_in.rb +++ b/examples/register/shift_in.rb @@ -15,7 +15,7 @@ board = Dino::Board.new(Dino::TxRx::Serial.new) shift_register = Dino::Components::Register::ShiftIn.new board: board, - pins: {latch: 10, data: 12, clock: 13}, + pins: {latch: 8, data: 11, clock: 12}, rising_clock: true # bit_order: :msbfirst # bytes: 1 diff --git a/examples/register/shift_out.rb b/examples/register/shift_out.rb index 9439232b..f8b87587 100644 --- a/examples/register/shift_out.rb +++ b/examples/register/shift_out.rb @@ -10,7 +10,7 @@ board = Dino::Board.new(Dino::TxRx::Serial.new) register = Dino::Components::Register::ShiftOut.new board: board, - pins: {latch: 9, data: 11, clock: 13} + pins: {latch: 9, data: 11, clock: 12} # bit_order: :msbfirst # bytes: 1 # buffer_writes: true diff --git a/examples/register/shift_ssd.rb b/examples/register/shift_ssd.rb index 31a77fa6..689887b7 100644 --- a/examples/register/shift_ssd.rb +++ b/examples/register/shift_ssd.rb @@ -14,7 +14,7 @@ board = Dino::Board.new(Dino::TxRx::Serial.new) shift_register = Dino::Components::Register::ShiftOut.new board: board, - pins: {data: 11, clock: 13, latch: 9} + pins: {latch: 9, data: 11, clock: 12} # bit_order: :msbfirst # bytes: 1 # buffer_writes: true diff --git a/examples/stepper/stepper.rb b/examples/stepper/stepper.rb index 6e03e21b..916a8eb9 100644 --- a/examples/stepper/stepper.rb +++ b/examples/stepper/stepper.rb @@ -6,7 +6,7 @@ board = Dino::Board.new(Dino::TxRx::Serial.new) stepper = Dino::Components::Stepper.new board: board, - pins: { slp: 6, enable: 7, direction: 8, step: 9, ms1: 10, ms2: 11 } + pins: { slp: 6, enable: 7, direction: 8, step: 10, ms1: 11, ms2: 12 } # Default is 8 microsteps. Set to 2 so we can move faster. stepper.microsteps = 2 From 3a3361ab2c1169e8b3ca6d80190ca274a314c8da Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 26 Feb 2023 13:26:07 -0400 Subject: [PATCH 293/296] Update readme and hardware.md --- HARDWARE.md | 22 +++++++++++++++------- README.md | 6 +++++- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/HARDWARE.md b/HARDWARE.md index 7da24340..4ae043e6 100644 --- a/HARDWARE.md +++ b/HARDWARE.md @@ -21,23 +21,31 @@ | Chip | Status | Version| Product | Notes | | :-------- | :------: | :----- | :--------------- |------ | -| Wiznet W5100/5500 | :green_heart: | 0.11.1 | Ethernet Shield | Wired Ethernet for Uno/Mega pin-compatible boards +| Wiznet W5100/5500 | :green_heart: | 0.11.1 | Ethernet Shield | Wired Ethernet for Uno/Mega pin-compatibles | HDG204 + AT32UC3 | :man_shrugging: | 0.12.0 | WiFi Shield | WiFi for Uno. No hardware to test, but compiles -| ATWINC1500 | :man_shrugging: | 0.12.0 | WiFi Shield 101 | As above, high memory use, Mega only +| ATWINC1500 | :man_shrugging: | 0.12.0 | WiFi Shield 101 | Same as above, high memory use, Mega only ### Espressif Chips with Built-In WiFi | Chip | Status | Version| Boards | Notes | | :-------- | :------: | :----- | :--------------- |------ | | ESP8266 | :yellow_heart: | 0.12.0 | NodeMCU | SoftwareSerial and LCD don't work yet -| ESP32 | :yellow_heart: | 0.12.0 | DOIT ESP32 DevKit V1 | No LCD or SoftSerial. SPI input bug. See changelog -| ESP32-S2 | :test_tube: | 0.12.0 | LOLIN S2 Pico | Might work, not tested yet -| ESP32-S3 | :test_tube: | 0.12.0 | LOLIN S3 V1.0.0 | Might work, not tested yet +| ESP32 | :yellow_heart: | 0.12.0 | DOIT ESP32 DevKit V1 | No LCD or SoftSerial. SPI mode bug (see changelog). +| ESP32-S2 | :test_tube: | 0.12.0 | LOLIN S2 Pico | Should work, hardware limits will be wrong +| ESP32-S3 | :test_tube: | 0.12.0 | LOLIN S3 V1.0.0 | Should work, hardware limits will be wrong **Note:** There are too many boards using these chips to be comprehensive. Most should work. These are the exact ones used for testing, chosen based on popularity. **Note:** For these boards, "pin" numbers are always based on GPIO numbers defined by the CHIP, not the pin numbers labeled on the BOARD. They might coincide, but not always. Keep a mapping reference handy for your particular board. +### Raspberry Pi Microcontrollers + +| Chip | Status | Version| Boards | Notes | +| :-------- | :------: | :----- | :--------------- |------ | +| RP2040 | :heart: | - | Raspberry Pi Pico (W) | + +**Note:** There are many boards built around the RP2040. Check your board's GPIO pin map, and use GPIO numbers in dino. + # Supported Components :green_heart: Full support :yellow_heart: Partial support :heart: Planned. No support yet @@ -66,7 +74,7 @@ | Hardware Serial | :heart: | Hardware | - | - | For boards with native USB and UARTs | Maxim OneWire | :green_heart: | Software | 0.12.0 | `OneWire::Bus` | No overdrive support | Infrared Emitter | :green_heart: | Software | 0.12.0 | `IREmitter` | Library on Board -| Infrared Receiver| :red_heart: | Software | - | - | Doable with existing library +| Infrared Receiver| :heart: | Software | - | - | Doable with existing library ### Generic Components @@ -96,7 +104,7 @@ | :--------------- | :------: | :-------- | :----- | :--------------- |------ | | Servo | :green_heart: | PWM + Library | 0.11.2 | `Servo` | Maximum of 6 on ATmega168, 16 on ESP32 and 12 otherwise | L298N | :heart: | PWM Out | - | | 2ch DC motor driver -| A3967 | :green_heart: | Digital Out | 0.12.0 | `Stepper`| 1ch microstepper (EasyDriver)å +| A3967 | :green_heart: | Digital Out | 0.12.0 | `Stepper`| 1ch microstepper (EasyDriver) | PCA9685 | :heart: | I2C | - | - | 16ch 12-bit PWM for servo, DC motor, or LED ### Displays diff --git a/README.md b/README.md index ec6eb8e2..1167a503 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,11 @@ Dino doesn't run Ruby on the microcontroller either, like [mruby-esp32](https:// High-level abstraction in Ruby makes hardware classes easy to implement, with interfaces we expect. They multitask a single core microcontroller, with thread-safe state and callbacks for inputs, but no "task" priority. If you need more I/O, integration is seamless. Connect another board and instantiate. -Each physical component you connect to your board(s) maps to a Ruby object you can use directly. You get to think about your hardware and appplication logic, not all the stuff in between. See supported hardware [here](HARDWARE.md). +Each physical component you connect to your board(s) maps to a Ruby object you can use directly. You get to think about your hardware and appplication logic, not all the stuff in between. + +### Supported Hardware + +See a full list of supported mircocontroller platforms, interfaces, and components [here](HARDWARE.md). ## Getting Started #### 1) Install the Gem From 00c4224e898b1de5ebd65e99cc8cf326e6eef95e Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 26 Feb 2023 14:51:06 -0400 Subject: [PATCH 294/296] Move dependency installation out of readme --- DEP_INSTALL_CLI.md | 47 ++++++++++++++++++++++++++ DEP_INSTALL_IDE.md | 82 ++++++++++++++++++++++++++++++++++++++++++++++ README.md | 63 +++++------------------------------ 3 files changed, 138 insertions(+), 54 deletions(-) create mode 100644 DEP_INSTALL_CLI.md create mode 100644 DEP_INSTALL_IDE.md diff --git a/DEP_INSTALL_CLI.md b/DEP_INSTALL_CLI.md new file mode 100644 index 00000000..d9b5cd88 --- /dev/null +++ b/DEP_INSTALL_CLI.md @@ -0,0 +1,47 @@ +## Install Arduino Dependencies for Dino (CLI) + +Once `arduino-cli` is installed, you can copy and paste into your shell for easy installation. + +**Install Everything:** +````shell +arduino-cli config add board_manager.additional_urls https://arduino.esp8266.com/stable/package_esp8266com_index.json +arduino-cli config add board_manager.additional_urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json +arduino-cli core update-index +arduino-cli core install arduino:megaavr +arduino-cli core install esp8266:esp8266 +arduino-cli core install esp32:esp32 +arduino-cli lib install Servo +arduino-cli lib install LiquidCrystal +arduino-cli lib install WiFi +arduino-cli lib install IRremote@4.0.0 +arduino-cli core install esp8266:esp8266 +arduino-cli lib install IRremoteESP8266@2.8.4 +arduino-cli lib install ESP32Servo +```` + +**AVR-based Arduinos & Clones Only:** +````shell +arduino-cli core update-index +arduino-cli core install arduino:megaavr +arduino-cli lib install Servo +arduino-cli lib install LiquidCrystal +arduino-cli lib install WiFi +arduino-cli lib install IRremote@4.0.0 +```` + +**ESP8266 Only:** +````shell +arduino-cli config add board_manager.additional_urls https://arduino.esp8266.com/stable/package_esp8266com_index.json +arduino-cli core update-index +arduino-cli core install esp8266:esp8266 +arduino-cli lib install IRremoteESP8266@2.8.4 +```` + +**ESP32 Only:** +````shell +arduino-cli config add board_manager.additional_urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json +arduino-cli core update-index +arduino-cli core install esp32:esp32 +arduino-cli lib install ESP32Servo +arduino-cli lib install IRremoteESP8266@2.8.4 +```` diff --git a/DEP_INSTALL_IDE.md b/DEP_INSTALL_IDE.md new file mode 100644 index 00000000..75abcee6 --- /dev/null +++ b/DEP_INSTALL_IDE.md @@ -0,0 +1,82 @@ +### Install Arduino Dependencies for Dino (IDE) + +### Installing Cores + +Some microcontroller platforms require board manager cores that do not come with the IDE. To install a core: + * Open the Preferences window of the IDE, and find "Additional boards manager URLS:". Click the button next to it. + * In the editor that opens, paste the given URL on a new line at the end (if it doesn't already exist). + * Confirm and exit Preferences. Wait for the IDE to finish downloading indexes from the new URL. + * Click on Tools > Board > Board Manager. + * Search for the platform you are installing by name, and click Install, optionally selecting a version. + +### Installing Libraries + +All platforms will require libraries to be installed. To install a library do the following: + * Click on Tools > Manage Libraries. + * Search for the library you are installing by name, and click Install, optionally selecting a version. + +### Platforms: + +**Install Everything:** + * Board Manager URLs: + ````shell + https://arduino.esp8266.com/stable/package_esp8266com_index.json + https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json + ```` + * Boards (latest version unless specified): + ````shell + Arduino megaAVR Boards + ESP8266 Boards + ESP32 Boards + ```` + * Libraries (latest version unless specified): + ````shell + Servo by Michael Margolis, Arduino + Liquid Crystal by Arduino, Adafruit + WiFi by Arduino + IRremote @ v4.0.0 by shirriff, z3to, ArminJo + IRremoteESP82666 @ v2.8.4 by David Conran, Sebastien Warin + ESP32Servo by Kevin Harrington, John K. Bennett + ```` + +**AVR-based Arduinos & Clones Only:** + * Boards (latest version unless specified): + ````shell + Arduino megaAVR Boards (only for Atmega4809 / Nano Every) + ```` + * Libraries (latest version unless specified): + ````shell + Servo by Michael Margolis, Arduino + Liquid Crystal by Arduino, Adafruit + WiFi by Arduino + IRremote @ v4.0.0 by shirriff, z3to, ArminJoß + ```` + +**ESP8266 Only:** + * Board Manager URLs: + ````shell + https://arduino.esp8266.com/stable/package_esp8266com_index.json + ```` + * Boards (latest version unless specified): + ````shell + ESP8266 Boards + ```` + * Libraries (latest version unless specified): + ````shell + IRremoteESP82666 @ v2.8.4 by David Conran, Sebastien Warin + ```` + +**ESP32 Only:** + * Board Manager URLs: + ````shell + https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json + ```` + * Boards (latest version unless specified): + ````shell + ESP32 Boards + ```` + * Libraries (latest version unless specified): + ````shell + IRremoteESP82666 @ v2.8.4 by David Conran, Sebastien Warin + ESP32Servo by Kevin Harrington, John K. Bennett + ```` diff --git a/README.md b/README.md index 1167a503..07bcdb37 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,11 @@ button.down do end ```` -Dino doesn't run Ruby on the microcontroller either, like [mruby-esp32](https://github.com/mruby-esp32/mruby-esp32). The board runs a C++ firmware that exposes as much low-level I/O as possible, so we can use it in Ruby. It becomes a peripheral for your computer. +Dino doesn't run Ruby on the microcontroller (try [mruby-esp32](https://github.com/mruby-esp32/mruby-esp32) for that). It runs a C++ firmware that exposes as much low-level I/O as possible, so we can use it in Ruby. It becomes a peripheral for your computer. -High-level abstraction in Ruby makes hardware classes easy to implement, with interfaces we expect. They multitask a single core microcontroller, with thread-safe state and callbacks for inputs, but no "task" priority. If you need more I/O, integration is seamless. Connect another board and instantiate. +High-level abstraction in Ruby makes hardware classes easy to implement, with intuitive interfaces. They multitask a single core microcontroller, with thread-safe state, and callbacks for inputs, but no "task" priority. If you need more I/O, integration is seamless. Connect another board and instantiate it in Ruby. -Each physical component you connect to your board(s) maps to a Ruby object you can use directly. You get to think about your hardware and appplication logic, not all the stuff in between. +Each physical component connected to your board(s) maps to a Ruby object you can use directly. You get to think about your hardware and appplication logic, not everything in between. ### Supported Hardware @@ -30,7 +30,7 @@ See a full list of supported mircocontroller platforms, interfaces, and componen gem install dino ``` -Before using the microcontroller in Ruby, we need to flash it with the dino firmware (or "sketch" in Arduino slang). This is needed **only once** for each board, but future dino versions may need reflashing for firmware functions. +Before using the microcontroller in Ruby, we need to flash it with the dino firmware (or "sketch" in Arduino lingo). This is needed **only once** for each board, but future dino versions may need reflashing for firmware functions. #### 2) Install the Arduino IDE OR CLI @@ -43,55 +43,9 @@ brew install arduino-cli ```` #### 3) Install Arduino Dependencies -Dino uses Arduino "cores" which add microcontroller support, and a few libraries. Let's install them now. - -**IDE method for Arduino & Clones:** -* Go to: Tools > Manage Libraries. -* Find and install the following libraries, at the version numbers given: - * `Servo by Michael Margolis, Arduino` at latest version - * `Liquid Crystal by Arduino, Adafruit` at latest version - * `WiFi by Arduino` at latest version - * `IRremote by shirriff, z3to, ArminJo` at `version 4.0.0` - -**CLI method for Arduino & Clones:** -````shell -arduino-cli lib install Servo -arduino-cli lib install LiquidCrystal -arduino-cli lib install WiFi -arduino-cli lib install IRremote@4.0.0 -```` - -**IDE method for ESP8266:** -* Go to Tools > Board > Board Manager. -* Search for `ESP8266 Boards` and install the latest version. -* Go to: Tools > Manage Libraries. -* Find and install the following libraries, at the version numbers given: - * `IRremoteESP82666 by David Conran, Sebastien Warin` at `version 2.8.4` - -**CLI method for ESP8266:** -````shell -arduino-cli config add board_manager.additional_urls https://arduino.esp8266.com/stable/package_esp8266com_index.json -arduino-cli core update-index -arduino-cli core install esp8266:esp8266 -arduino-cli lib install IRremoteESP8266@2.8.4 -```` - -**IDE method for ESP32:** -* Go to Tools > Board > Board Manager. -* Search for `esp32` and install the latest version. -* Go to: Tools > Manage Libraries. -* Find and install the following libraries, at the version numbers given: - * `ESP32Servo by Kevin Harrington, John K. Bennett` at latest version - * `IRremoteESP82666 by David Conran, Sebastien Warin` at `version 2.8.4` - -**CLI method for ESP32:** -````shell -arduino-cli config add board_manager.additional_urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json -arduino-cli core update-index -arduino-cli core install esp32 -arduino-cli lib install ESP32Servo -arduino-cli lib install IRremoteESP8266@2.8.4 -```` +Dino uses Arduino cores, which add support for microcontrollers, and a few libraries. Install only the ones for your microcontroller, or install everything. They are no conflcits. Instructions for all supported microcontrollers are here: + * [Install Dependencies in IDE](DEP_INSTALL_IDE.md) + * [Install Dependencies in CLI](DEP_INSTALL_CLI.md) #### 4) Generate the Arduino Sketch The `dino` command is included with the gem. It will make the Arduino sketch folder for you, and configure it. @@ -106,8 +60,9 @@ dino sketch serial dino sketch serial --target esp8266 ```` -**For ESP32 over WiFi (2.4Ghz and DHCP Only):** +**For ESP8266 or ESP32 over WiFi (2.4Ghz and DHCP Only):** ```shell +dino sketch wifi --target esp8266 --ssid YOUR_SSID --password YOUR_PASSWORD dino sketch wifi --target esp32 --ssid YOUR_SSID --password YOUR_PASSWORD ```` **Note:** [This example](examples/tcp.rb) shows how to use a board over a TCP connection, but the WiFi sketches fall back to the serial interface if no TCP client is connected. You should be able to run the examples over serial while still connected. From 6302f36233ee1a7741d3aa794b8b754527de98ff Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 26 Feb 2023 18:04:22 -0400 Subject: [PATCH 295/296] New RGBLed example and diagram --- examples/04-pwm_led/pwm_led.rb | 8 +-- examples/05-rgb_led/rgb_led.fzz | Bin 0 -> 6094 bytes examples/05-rgb_led/rgb_led.pdf | Bin 0 -> 489852 bytes examples/05-rgb_led/rgb_led.rb | 71 +++++++++++++++++---------- examples/05-rgb_led/rgb_mapping.rb | 76 +++++++++++++++++++++++++++++ 5 files changed, 126 insertions(+), 29 deletions(-) create mode 100644 examples/05-rgb_led/rgb_led.fzz create mode 100644 examples/05-rgb_led/rgb_led.pdf create mode 100644 examples/05-rgb_led/rgb_mapping.rb diff --git a/examples/04-pwm_led/pwm_led.rb b/examples/04-pwm_led/pwm_led.rb index 9a72e598..8a5f8ce2 100644 --- a/examples/04-pwm_led/pwm_led.rb +++ b/examples/04-pwm_led/pwm_led.rb @@ -34,7 +34,7 @@ end puts -# Now let's add the potentiometer from the previous examp to control it. +# Now let's add the potentiometer from the previous example to control it. potentiometer = Dino::Components::Potentiometer.new(pin: 'A0', board: board) # Helper method to calculate brightness. @@ -44,10 +44,10 @@ def map_pot_value(value) # Linearization hack for audio taper potentiometers. # Adjust k for different tapers. This was an A500K. - k = 5 - linearized = (fraction * (k + 1)) / ((k * fraction) + 1) + # k = 5 + # linearized = (fraction * (k + 1)) / ((k * fraction) + 1) # Use this for linear potentiometers instead. - # linearized = fraction + linearized = fraction # Map to the linearized 0-1 range to a 0-255 range for 8-bit PWM. (linearized * 255).floor diff --git a/examples/05-rgb_led/rgb_led.fzz b/examples/05-rgb_led/rgb_led.fzz new file mode 100644 index 0000000000000000000000000000000000000000..8ed8cf45eab3ac5a1954179a42ef2aacd1008a19 GIT binary patch literal 6094 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 literal 0 HcmV?d00001 diff --git a/examples/05-rgb_led/rgb_led.pdf b/examples/05-rgb_led/rgb_led.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2750a7144b52b10c1c1cdfd0eadceb4c9af504ec GIT binary patch literal 489852 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 z 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

Zomza${2L8ZKx!AzOAK*REb+! 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≪~cCj~^(-u2%?A0<)J)hu` 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 OUT_MAX + value = OUT_MIN if value < OUT_MIN + value +end + +def map_red(pot_value) + # Red full on at low end. + return OUT_MAX if pot_value < 171 + + # Red fades out from 171-341 + if (171..341).include? pot_value + return map_value(pot_value - 171, 170, true) + end + + # Red full off in the middle third. + return OUT_MIN if (342..682).include? pot_value + + # Red fades in from 683-853 + if (683..853).include? pot_value + return map_value(pot_value - 683, 170, false) + end + + # Red full on at high end. + return OUT_MAX if pot_value > 853 +end + +def map_green(pot_value) + # Green fades in from 0-171 + if (0..170).include? pot_value + return map_value(pot_value, 170, false) + end + + # Green full on from 1/6 to 1/2. + return OUT_MAX if (171..511).include? pot_value + + # Green fades out from 512-682 + if (512..682).include? pot_value + return map_value(pot_value - 512, 170, true) + end + + # Geen full off above 2/3. + return OUT_MIN if pot_value > 682 +end + +def map_blue(pot_value) + # Blue full off until 1/3 + return OUT_MIN if pot_value < 342 + + # Blue fades in from 342-512 (170 steps) + if (342..542).include? pot_value + return map_value(pot_value - 342, 170, false) + end + + # Blue full on from 513 to 852 + return OUT_MAX if (513..852).include? pot_value + + # Blue fades out from 853-1023 (170 steps) + if (853..1023).include? pot_value + return map_value(pot_value - 853, 170, true) + end +end From e421bc50ba7346b315175f11ff34f264b1b04ed5 Mon Sep 17 00:00:00 2001 From: vickash Date: Sun, 26 Feb 2023 21:48:01 -0400 Subject: [PATCH 296/296] Potentiometer test --- lib/dino/components/potentiometer.rb | 7 +-- test/components/potentiometer_test.rb | 61 +++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 test/components/potentiometer_test.rb diff --git a/lib/dino/components/potentiometer.rb b/lib/dino/components/potentiometer.rb index 272a85b8..51d3b271 100644 --- a/lib/dino/components/potentiometer.rb +++ b/lib/dino/components/potentiometer.rb @@ -7,15 +7,12 @@ class Potentiometer < Basic::AnalogInput def after_initialize(options={}) super(options) - # Read 2x as often than regular AnalogInput. - @divider = 8 - # Keep values to smooth with moving average by default. self.smoothing = true @moving_set = [] - # Start listening immediately. - listen + # Start listening immediately. Read 2x as often than regular AnalogInput. + listen(@divider = 8) end def smoothing_on diff --git a/test/components/potentiometer_test.rb b/test/components/potentiometer_test.rb new file mode 100644 index 00000000..f9dc617a --- /dev/null +++ b/test/components/potentiometer_test.rb @@ -0,0 +1,61 @@ +require 'test_helper' + +class PotentiometerTest < MiniTest::Test + def board + @board ||= BoardMock.new + end + + def part + @part ||= Dino::Components::Potentiometer.new(board: board, pin:14) + end + + def test_setup + mock = MiniTest::Mock.new.expect(:call, nil, [14,8]) + board.stub(:analog_listen, mock) do + part + end + assert_equal part.divider, 8 + assert_equal part.smoothing, true + assert_equal part.instance_variable_get(:@moving_set), [] + end + + def test_smoothing_on + 7.times do + part.update(10) + end + part.update(50) + + # 120/8 = 15 + assert_equal part.state, 15 + end + + def test_smoothing_off + part.smoothing_off + 7.times do + part.update(10) + end + part.update(50) + + # Give latest value. + assert_equal part.state, 50 + end + + def test_on_change + mock = Minitest::Mock.new.expect(:call, nil) + + # Turn off smoothing and set an initial value + part.smoothing_off + part.update(100) + + # Add the callback + part.on_change { mock.call } + + # Send a few updates + part.update(100) + part.update(100) + part.update(101) + + # Should have only been called once. + mock.verify + end +end

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