farmbot-serial 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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