farmbot-serial 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 76fb34947d7e8701118ad070a874e9ccf7318b84
4
- data.tar.gz: 97a79c5b63f4fe02d4532f5efd24193fd9b2902f
3
+ metadata.gz: 968bffc9904f9b3d733c91107ec83caeb632353a
4
+ data.tar.gz: 0eb0a17a2e6923e3e0ef2ec8228c5ebf2d99cd09
5
5
  SHA512:
6
- metadata.gz: 47c21fdafd0323365b48d24037af53414a7ec65953a66195be9e9a7e2c73c4d366a112ade1d0e86a5c2c0663bb00b81d6c54e8d5d6bb573944e2a4e79cb958c0
7
- data.tar.gz: c48d82dd75b5be870756af8d2b8c4906416305fe9f27a50aa15c79948a99e34fe191de80504f308198bb4fb21c26a5e1c52b4cf974e2d43ecf9b79321002f3fc
6
+ metadata.gz: fd10320b1219e6001c2e5bb73ffc3cada2e4bd5e9efcaa35ba72476a2667c8969cf3b18eb3c493937c57c37605b5855b52606fd4c38bed5b2395eae0a68a7cd4
7
+ data.tar.gz: fe5fc6bead0a8fb4d39ab7a1235f3436f3730b2505d2b01e94f9792258136b6ba8bd29b5a884e1c2d09938eefc2de34a286c1a86b7f37b1e58ffe82771c40387
data/README.md CHANGED
@@ -4,17 +4,27 @@ A ruby gem for controlling Farmbot via serial line with EventMachine.
4
4
 
5
5
  ## Usage
6
6
 
7
+ ```
8
+ gem install farmbot-serial, '0.0.5'
9
+ ```
10
+
7
11
  ```ruby
8
- bot = FB::Arduino.new # Defaults to '/dev/ttyACM0'
12
+ bot = FB::Arduino.new # Defaults to '/dev/ttyACM0', can be configured.
9
13
 
10
14
  EM.run do
11
- # Register bot with event loop.
12
15
  FB::ArduinoEventMachine.connect(bot)
13
- # Make the bot flinch every 5 seconds...
14
- FB::ArduinoEventMachine.poll(5) { bot.commands.move_relative(1, 1, 1) }
15
- # Immediate handling of incoming messages.
16
- bot.onmessage { |data| puts "Serial message in: #{data}" }
17
- # Stop event loop if connection closes or serial cable is disconnected
18
- bot.onclose { EM.stop }
16
+
17
+ # Example 1: Writing to the serial line the "correct way" every 1.5 seconds.
18
+ command = FB::Gcode.new('G01 X01 Y01 Z01')
19
+ EventMachine::PeriodicTimer.new(1.5) { bot.commands.move_relative(command) }
20
+
21
+ # Example 2: Writing raw strings to serial every 2.5
22
+ EventMachine::PeriodicTimer.new(2.5) { bot.write("F31 P8") }
23
+
24
+ # This will execute after status has been updated / internal code.
25
+ bot.onmessage { |gcode| puts "SERIAL IN: #{gcode.name}" }
26
+
27
+ # Try pulling the USB cable out to test this one.
28
+ bot.onclose { EM.stop }
19
29
  end
20
30
  ```
data/example.rb ADDED
@@ -0,0 +1,21 @@
1
+ require_relative 'lib/farmbot-serial'
2
+ require 'pry'
3
+
4
+ bot = FB::Arduino.new # Defaults to '/dev/ttyACM0', can be configured.
5
+
6
+ EM.run do
7
+ FB::ArduinoEventMachine.connect(bot)
8
+
9
+ # Example 1: Writing to the serial line the "correct way" every 1.5 seconds.
10
+ command = FB::Gcode.new('G01 X01 Y01 Z01')
11
+ EventMachine::PeriodicTimer.new(1.5) { bot.commands.move_relative(command) }
12
+
13
+ # Example 2: Writing raw strings to serial every 2.5
14
+ EventMachine::PeriodicTimer.new(2.5) { bot.write("F31 P8") }
15
+
16
+ # This will execute after status has been updated / internal code.
17
+ bot.onmessage { |gcode| puts "SERIAL IN: #{gcode.name}" }
18
+
19
+ # Try pulling the USB cable out to test this one.
20
+ bot.onclose { EM.stop }
21
+ end
data/lib/arduino.rb CHANGED
@@ -1,16 +1,21 @@
1
+ require 'serialport'
2
+ require_relative 'default_serial_port'
3
+ require_relative 'arduino/command_set'
4
+ require_relative 'arduino/event_machine'
5
+ require_relative 'arduino/status'
1
6
  # Communicate with the arduino using a serial interface
2
7
  module FB
3
8
  class Arduino
4
9
  class EmergencyStop < StandardError; end # Not yet used.
5
10
 
6
- attr_reader :serial_port, :logger, :commands, :queue
11
+ attr_reader :serial_port, :logger, :commands, :queue, :status
7
12
 
8
13
  # Initial and provide a serial object, as well as an IO object to send
9
14
  # log messages to. Default SerialPort is DefaultSerialPort. Default logger
10
15
  # is STDOUT
11
16
  def initialize(serial_port = DefaultSerialPort.new, logger = STDOUT)
12
- @serial_port, @logger, @queue = serial_port, logger, EM::Queue.new
13
- @commands = FB::ArduinoCommandSet.new(self)
17
+ @serial_port, @logger, @queue = serial_port, logger, EM::Channel.new
18
+ @commands, @status = FB::ArduinoCommandSet.new(self), FB::Status.new(self)
14
19
  end
15
20
 
16
21
  # Log to screen/file/IO stream
@@ -18,12 +23,20 @@ module FB
18
23
  logger.puts(message)
19
24
  end
20
25
 
26
+ # Highest priority message when processing incoming Gcode. Use for system
27
+ # level status changes.
28
+ def parse_incoming(gcode)
29
+ commands.execute(gcode)
30
+ end
31
+
21
32
  # Handle incoming text from arduino into pi
22
33
  def onmessage(&blk)
23
34
  raise 'read() requires a block' unless block_given?
24
- @queue.pop do |string|
25
- log "RECEIVED #{string}"
26
- blk.call(string)
35
+ @queue.subscribe do |gcodes|
36
+ gcodes.each do |gcode|
37
+ parse_incoming(gcode)
38
+ blk.call(gcode)
39
+ end
27
40
  end
28
41
  end
29
42
 
@@ -34,7 +47,6 @@ module FB
34
47
  # Send outgoing test to arduino from pi
35
48
  def write(string)
36
49
  serial_port.puts string
37
- log "SENT #{string}"
38
50
  end
39
51
 
40
52
  # Handle loss of serial connection
@@ -42,8 +54,5 @@ module FB
42
54
  log "Connection to device lost"
43
55
  @onclose.call if @onclose
44
56
  end
45
-
46
- def reconnect
47
- end
48
57
  end
49
58
  end
@@ -0,0 +1,39 @@
1
+ module FB
2
+ # Composes all logic related to controlling a bot into a single object.
3
+ # Responsible for writing to the serial line.
4
+ class ArduinoCommandSet
5
+ attr_reader :bot
6
+
7
+ def initialize(bot)
8
+ @bot = bot
9
+ end
10
+
11
+ def execute(gcode)
12
+ puts "SERIAL OUT: #{gcode.name}"
13
+ self.send(gcode.name, gcode)
14
+ end
15
+
16
+ def emergency_stop(*)
17
+ bot.write("E")
18
+ end
19
+
20
+ def move_relative(gcode)
21
+ end
22
+
23
+ def received(gcode)
24
+ end
25
+
26
+ def reporting_end_stops(gcode)
27
+ end
28
+
29
+ def report_current_position(gcode)
30
+ end
31
+
32
+ def done(gcode)
33
+ end
34
+
35
+ def report_status_value(gcode)
36
+ end
37
+ end
38
+ end
39
+
@@ -0,0 +1,61 @@
1
+ require 'eventmachine'
2
+ module FB
3
+ # Class that is fed into event machine's event loop to handle incoming serial
4
+ # messages asynchronously via EM.attach(). See: EM.attach
5
+ class ArduinoEventMachine < EventMachine::Connection
6
+ class << self
7
+ attr_accessor :arduino
8
+ end
9
+
10
+ def self.poll(interval, &blk)
11
+ EventMachine::PeriodicTimer.new(interval.to_f, &blk)
12
+ end
13
+
14
+ def initialize
15
+ @q, @buffer = self.class.arduino.queue, ''
16
+ end
17
+
18
+ # Gets called when data arrives.
19
+ def receive_data(data)
20
+ split_into_chunks(data).each do |chunk|
21
+ if chunk.end_with?("\r\n")
22
+ add_to_buffer(chunk)
23
+ send_buffer
24
+ clear_buffer
25
+ else
26
+ add_to_buffer(chunk)
27
+ end
28
+ end
29
+ end
30
+
31
+ # This is a nasty hack that takes incoming strings from the serial line and
32
+ # splits the data on \r\n. Unlike Ruby's split() method, this method will
33
+ # preserve the \r\n.
34
+ def split_into_chunks(data)
35
+ data.gsub("\r\n", '!@').split('@').map{ |d| d.gsub('!', "\r\n") }
36
+ end
37
+
38
+ def clear_buffer
39
+ @buffer = ''
40
+ end
41
+
42
+ def add_to_buffer(d)
43
+ @buffer += d
44
+ end
45
+
46
+ def send_buffer
47
+ @q << Gcode.parse_lines(@buffer)
48
+ end
49
+
50
+ # Gets called when the connection breaks.
51
+ def unbind
52
+ self.class.arduino.disconnect
53
+ EM.stop
54
+ end
55
+
56
+ def self.connect(arduino)
57
+ @arduino = arduino
58
+ EM.attach arduino.serial_port, self
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,25 @@
1
+ module FB
2
+ class Status
3
+ # Map of informational status and default values for status within Arduino.
4
+ DEFAULT_INFO = {X: 0, Y: 0, Z: 0, S: 10, Q: 0, T: 0, C: '', P: 0, V: 0,
5
+ W: 0, L: 0, E: 0, M: 0, XA: 0, XB: 0, YA: 0, YB: 0, ZA: 0,
6
+ ZB: 0, busy: 1}
7
+ # Put it into a struct.
8
+ Info = Struct.new(*DEFAULT_INFO.keys)
9
+
10
+ attr_reader :bot
11
+
12
+ def initialize(bot)
13
+ @bot, @info = bot, Info.new(*DEFAULT_INFO.values)
14
+ end
15
+
16
+ def []=(register, value)
17
+ # Add event broadcasts here!!!
18
+ @info[value.upcase.to_sym] = value
19
+ end
20
+
21
+ def [](value)
22
+ @info[value.upcase.to_sym]
23
+ end
24
+ end
25
+ end
@@ -1,7 +1,8 @@
1
1
  require 'serialport'
2
2
 
3
- # This object creates a Serial IO with sane default, since most FarmBot setups
4
- # follow the same serial configuration setup.
3
+ # This object creates a Serial IO with sane defaults, since most FarmBot setups
4
+ # follow the same serial configuration setup. You can build your own SerialPort
5
+ # object also, if that's what you need.
5
6
  module FB
6
7
  class DefaultSerialPort < SerialPort
7
8
  COM_PORT = '/dev/ttyACM0'
@@ -1,5 +1,2 @@
1
- require 'serialport'
2
- require_relative 'default_serial_port'
3
- require_relative 'arduino_command_set'
4
- require_relative 'arduino_event_machine'
1
+ require_relative 'gcode'
5
2
  require_relative 'arduino'
data/lib/gcode.rb ADDED
@@ -0,0 +1,39 @@
1
+ require 'yaml'
2
+ module FB
3
+ class Gcode
4
+ GCODE_DICTIONARY = YAML.load_file(File.join(File.dirname(__FILE__), 'gcode.yml'))
5
+
6
+ attr_accessor :cmd, :params, :str
7
+
8
+ def initialize(str)
9
+ @str = str
10
+ @params = str.split(' ').map{|line| GcodeToken.new(line)}
11
+ @cmd = @params.shift
12
+ end
13
+
14
+ # Turns a string of many gcodes into an array of many gcodes. Used to parse
15
+ # incoming serial.
16
+ def self.parse_lines(string)
17
+ string.gsub("\r", '').split("\n").map { |s| self.new(s) }
18
+ end
19
+
20
+ # Returns a symbolized english version of the gcode's name.
21
+ def name
22
+ GCODE_DICTIONARY[cmd.to_sym] || :unknown
23
+ end
24
+
25
+ # A head/tail pair of a single node of GCode. Ex: R01 = [:R, '01']
26
+ class GcodeToken
27
+ attr_reader :head, :tail, :name
28
+
29
+ def initialize(str)
30
+ nodes = str.scan(/\d+|\D+/) # ["R", "01"]
31
+ @head, @tail = nodes.shift.to_sym, nodes.join(" ")
32
+ end
33
+
34
+ def to_sym
35
+ "#{head}#{tail}".to_sym
36
+ end
37
+ end
38
+ end
39
+ end
data/lib/gcode.yml ADDED
@@ -0,0 +1,39 @@
1
+ ---
2
+ :G0: :move_to_location_at_given_speed_for_axis
3
+ :G1: :move_to_location_on_a_straight_line
4
+ :G28: :move_home_all_axis
5
+ :F1: :dose_amount_of_water_using_time_in_millisecond
6
+ :F2: :dose_amount_of_water_using_flow_meter_that_measures_pulses
7
+ :F11: :home_x_axis
8
+ :F12: :home_y_axis
9
+ :F13: :home_z_axis
10
+ :F14: :calibrate_x_axis
11
+ :F15: :calibrate_y_axis
12
+ :F16: :calibrate_z_axis
13
+ :F21: :read_parameter
14
+ :F22: :write_parameter
15
+ :F23: :update_parameter_during_calibration
16
+ :F31: :read_status
17
+ :F32: :write_status
18
+ :F41: :set_a_value_on_an_arduino_pin
19
+ :F42: :read_a_value_from_an_arduino_pin
20
+ :F43: :set_the_mode_of_a_pin_in_arduino
21
+ :F44: :set_the_value_v_on_an_arduino_pin
22
+ :F51: :set_a_value_on_the_tool_mount_with_i2c
23
+ :F52: :read_value_from_the_tool_mount_with_i2c
24
+ :F61: :set_the_servo_on_the_pin_to_the_requested_angle
25
+ :F81: :report_end_stop
26
+ :F82: :report_current_position
27
+ :F83: :report_software_version
28
+ :E: :emergency_stop
29
+ :R01: :received
30
+ :R02: :done
31
+ :R03: :error
32
+ :R04: :busy
33
+ :R21: :report_parameter_value
34
+ :R31: :report_status_value
35
+ :R41: :report_pin_value
36
+ :R81: :reporting_end_stops
37
+ :R82: :report_current_position
38
+ :R83: :report_software_version
39
+ :R99: :debug_message
data/lib/param.yml ADDED
@@ -0,0 +1,21 @@
1
+ # ---
2
+ # X: :x_movement
3
+ # Y: :y_movement
4
+ # Z: :z_movement
5
+ # S: :speed
6
+ # Q: :quanity
7
+ # T: :time
8
+ # C: :comment
9
+ # P: :parameter
10
+ # V: :value_number
11
+ # W: :secondary_value
12
+ # L: :number
13
+ # E: :element
14
+ # M: :pin_mode
15
+ # M: :read_write_mode
16
+ # XA: :end_stop_1_x_axis
17
+ # XB: :end_stop_2_x_axis
18
+ # YA: :end_stop_1_y_axis
19
+ # YB: :end_stop_2_y_axis
20
+ # ZA: :end_stop_1_z_axis
21
+ # ZB: :end_stop_2_z_axis
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: farmbot-serial
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim Evers
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-03-25 00:00:00.000000000 Z
12
+ date: 2015-03-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -120,10 +120,16 @@ files:
120
120
  - ".rspec"
121
121
  - README.md
122
122
  - Rakefile
123
+ - example.rb
123
124
  - lib/arduino.rb
124
- - lib/arduino_event_machine.rb
125
+ - lib/arduino/command_set.rb
126
+ - lib/arduino/event_machine.rb
127
+ - lib/arduino/status.rb
125
128
  - lib/default_serial_port.rb
126
129
  - lib/farmbot-serial.rb
130
+ - lib/gcode.rb
131
+ - lib/gcode.yml
132
+ - lib/param.yml
127
133
  - spec/fixtures/stub_serial_port.rb
128
134
  - spec/lib/ramps_arduino_values_received_spec.rb
129
135
  - spec/spec_helper.rb
@@ -148,7 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
148
154
  version: '0'
149
155
  requirements: []
150
156
  rubyforge_project:
151
- rubygems_version: 2.4.5
157
+ rubygems_version: 2.4.6
152
158
  signing_key:
153
159
  specification_version: 4
154
160
  summary: Serial library for Farmbot
@@ -1,31 +0,0 @@
1
- require 'eventmachine'
2
- module FB
3
- # Class that is fed into event machine's event loop to handle incoming serial
4
- # messages asynchronously via EM.attach(). See: EM.attach
5
- class ArduinoEventMachine < EventMachine::Connection
6
- class << self
7
- attr_accessor :arduino
8
- end
9
-
10
- def self.poll(interval, &blk)
11
- raise 'You must pass a block' unless block_given?
12
- EventMachine::PeriodicTimer.new(interval.to_f, &blk)
13
- end
14
-
15
- # Gets called when data arrives.
16
- def receive_data(data)
17
- self.class.arduino.queue.push(data)
18
- end
19
-
20
- # Gets called when the connection breaks.
21
- def unbind
22
- self.class.arduino.disconnect
23
- EM.stop
24
- end
25
-
26
- def self.connect(arduino)
27
- @arduino = arduino
28
- EM.attach arduino.serial_port, self
29
- end
30
- end
31
- end