merging with skynet

pull/2/head
TimEvWw 2014-02-13 22:04:39 +01:00
parent 15bdb00301
commit 16fe103451
27 changed files with 926 additions and 529 deletions

22
.gitignore vendored 100644
View File

@ -0,0 +1,22 @@
*.gem
*.rbc
/coverage/
/InstalledFiles
/pkg/
/spec/reports/
/test/tmp/
/test/version_tmp/
/tmp/
/.yardoc/
/_yardoc/
/doc/
/rdoc/
/.bundle/
/lib/bundler/man/
Gemfile.lock
.ruby-version
.ruby-gemset
snippets.rb
snippet.coffee
config.yml
credentials.yml

19
Gemfile 100644
View File

@ -0,0 +1,19 @@
source 'http://rubygems.org'
gem 'socket.io-client-simple'
gem 'firmata'
group :test do
gem 'simplecov'
gem 'rspec'
end
group :development do
gem 'pry'
end
# needed for schedule
gem 'bson'
gem 'mongo'
gem 'mongoid'

View File

@ -29,13 +29,9 @@ sudo apt-get install ruby
sudo apt-get install arduino
gem install firmata
gem install bson
gem install mongo
gem install mongoid
'bundle install --without test development' from the project directory
OR
'bundle install' (for developers)
Mongo
@ -76,14 +72,41 @@ use "git clone" to copy the code to the RPi
Usage
=====
Use "ruby sync.rb" to start the skybet communiation ans synchronisation with the farmbot back end
Use "ruby runtime.rb" to start the runtime part of the software. This will read the datbase and send commands to the hardware.
Use "ruby menu.rb" to start the interface. A menu will appear. Type the command needed and press enter. It is also possible to add a list of commands to the file 'testcommands.csv' and use the menu to execute the file.
Still to do
===========
Author
------
* A lot of the settings for pins, sleep times, timeouts and motor inversions have to be moved to configuration.
* Check if it works with the nema 17 motor from OpenBuilds
* Rick Carlino
* Tim Evers
License
-------
The MIT License
Copyright (c) 2014 Farmbot Project
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.

9
README_2.md 100644
View File

@ -0,0 +1,9 @@
gem install firmata
gem install bson
gem install mongo
gem install mongoid
gem install socket.io-client-simple

0
Rakefile 100644
View File

View File

@ -0,0 +1,35 @@
# data classes for the command queue
class Command
include Mongoid::Document
embeds_many :commandlines
field :plant_id
field :scheduled_time
field :executed_time
field :status
end
class Commandline
include Mongoid::Document
embedded_in :command
#belongs_to :command
field :action
field :coord_x
field :coord_y
field :coord_z
field :speed
field :amount
end
class Refresh
include Mongoid::Document
field :name
field :value
end

19
controller.rb 100644
View File

@ -0,0 +1,19 @@
# FarmBot Controller runtime control
# Get commands from the database and execute them on the hardware
puts '[FarmBot Controller]'
puts 'starting up'
puts 'connecting to hardware'
require_relative 'lib/controller'
require_relative "lib/hardware/ramps"
$bot_control = Controller.new
$bot_hardware = HardwareInterface.new
$shutdown = 0
puts 'starting farmbot'
$bot_control.runFarmBot

View File

@ -1,66 +1,111 @@
# FarmBot Controller
#require "Rubygems"
#require "FarmBotDbAccess"
#require "./hardware/ramps.rb"
# This module executes the schedule. It reades the next command and sends it to the hardware implementation
require './lib/controlcommand.rb'
require './lib/filehandler.rb'
require 'date'
# This module ties the different components for the FarmBot together
# It reads the next action from the database, executes it and reports back
# and it will initiate the synchronization with the web systems
require_relative 'database/dbaccess'
class Control
class Controller
def initialize
@inactiveCounter = 0
@new_command = nil
end
def setCommand( cmd )
@new_command = cmd
end
# read command from schedule, wait for execution time
def runCycle
def initialize
@bot_dbaccess = DbAccess.new
end
#if $command_queue.empty? == false
if @new_command != nil
def runFarmBot
#command = $command_queue.pop
#command = @new_command
#@new_command = nil
@new_command.commandlines.each do |command_line|
#command.lines.each do |command_line|
case command_line.action.upcase
when "MOVE ABSOLUTE"
$bot_hardware.moveAbsolute(command_line.coord_x, command_line.coord_y, command_line.coord_z)
when "MOVE RELATIVE"
$bot_hardware.moveRelative(command_line.coord_x, command_line.coord_y, command_line.coord_z)
when "HOME X"
$bot_hardware.moveHomeX
when "HOME Y"
$bot_hardware.moveHomeY
when "HOME Z"
$bot_hardware.moveHomeZ
when "SET SPEED"
$bot_hardware.setSpeed(command_line.speed)
when "SHUTDOWN"
puts "shutdown"
$shutdown = 1
end
check = @bot_dbaccess.checkRefresh
end
#$commandFinished << command if command.commandid != nil and command.commandid > 0
else
sleep 0.1
end
while $shutdown == 0 do
@new_command = nil
end
end
# keep checking the database for new data
#$bot_hardware = FarmBotControlInterface.new
#$command_queue = Queue.new
#$command_finished = Queue.new
puts 'checking schedule'
command = @bot_dbaccess.getCommandToExecute
@bot_dbaccess.saveRefresh
if command != nil
puts "command retrieved is scheduled for #{command.scheduled_time}"
puts "curent time is #{Time.now}"
#scheduled_time = command.scheduled_time
if command.scheduled_time <= Time.now or command.scheduled_time == nil
# execute the command now and set the status to done
puts 'execute command'
#process_command( command )
@bot_dbaccess.setCommandToExecuteStatus('FINISHED')
else
puts 'wait for scheduled time or refresh'
refresh_received = false
wait_start_time = Time.now
# wait until the scheduled time has arrived, or wait for a minute or until a refresh it set in the database as a sign new data has arrived
while Time.now < wait_start_time + 60 and command.scheduled_time > Time.now - 1 and refresh_received == false
sleep 1
refresh_received = @bot_dbaccess.checkRefresh
puts 'refresh received' if refresh_received != false
end
end
else
puts 'no command found, wait'
refresh_received = false
wait_start_time = Time.now
# wait for a minute or until a refresh it set in the database as a sign new data has arrived
while Time.now < wait_start_time + 60 and refresh_received == false
sleep 1
refresh_received = @bot_dbaccess.checkRefresh
puts 'refresh received' if refresh_received != false
end
end
end
end
def process_command( cmd )
if cmd != nil
cmd.commandlines.each do |command_line|
case command_line.action.upcase
when "MOVE ABSOLUTE"
$bot_hardware.moveAbsolute(command_line.coord_x, command_line.coord_y, command_line.coord_z)
when "MOVE RELATIVE"
$bot_hardware.moveRelative(command_line.coord_x, command_line.coord_y, command_line.coord_z)
when "HOME X"
$bot_hardware.moveHomeX
when "HOME Y"
$bot_hardware.moveHomeY
when "HOME Z"
$bot_hardware.moveHomeZ
when "SET SPEED"
$bot_hardware.setSpeed(command_line.speed)
end
end
else
sleep 0.1
end
end
end

View File

@ -0,0 +1,78 @@
# This class is dedicated to retrieving and inserting commands into the command queue for the farm bot
require 'bson'
require 'mongo'
require 'mongoid'
require './app/models/command.rb'
class CommandQueue
def initialize
Mongoid.load!("config/mongo.yml", :development)
@last_command_retrieved = nil
@refresh_value = 0
@refresh_value_new = 0
@new_command = nil
end
def create_new_command(scheduled_time)
@new_command = Command.new
@new_command.scheduled_time = scheduled_time
end
def add_command_line(action, x = 0, y = 0, z = 0, speed = 0, amount = 0)
if @new_command != nil
line = Commandline.new
line.action = action
line.coord_x = x
line.coord_y = y
line.coord_z = z
line.speed = speed
line.amount = amount
if @new_command.commandlines == nil
@new_command.commandlines = [ line ]
else
@new_command.commandlines << line
end
end
end
def save_new_command
if @new_command != nil
@new_command.status = 'test'
@new_command.save
end
increment_refresh
end
def get_command_to_execute
@last_command_retrieved = Command.where(:status => 'test', :scheduled_time.ne => nil).order_by([:scheduled_time,:asc]).first
@last_command_retrieved
end
def set_command_to_execute_status(new_status)
if @last_command_retrieved != nil
@last_command_retrieved.status = new_status
@last_command_retrieved.save
end
end
def check_refresh
r = Refresh.where(:name => 'FarmBotControllerSchedule').first_or_initialize
@refresh_value_new = r.value.to_i
return @refresh_value_new != @refresh_value
end
def save_refresh
@refresh_value = @refresh_value_new
end
def increment_refresh
r = Refresh.where(:name => 'FarmBotControllerSchedule').first_or_initialize
r.value = r.value.to_i + 1
r.save
end
end

View File

@ -124,47 +124,4 @@ class DbAccess
r.save
end
end
#sched = ScheduleManagement.new
#c = FarmBotCommand.new
#c.command_name = 'MOVE ABSOLUTE'
#c.status = 'test'
#c.save
#sched.test
#puts FarmBotCommand.first.inspect
#puts FarmBotCommand.count
#Mongoid.load!("config/mongo.yml", :development)
#puts FarmBotCommand.where(:status => 'test').count
#c = FarmBotCommand.where(:status => 'test').first
#c = Command.new
#puts c.id
#c.status = 'test'
#c.scheduled_time = DateTime.now
#c.save
#puts FarmBotCommand.where(:status => 'test').count
#c = FarmBotCommand.where(:status => 'test', :scheduled_time.ne => nil).order_by([:scheduled_time,:asc]).count
#c = sched.getFirstCommand
#if c == nil
# puts 'niets gevonden'
#else
# puts 'gevonden'
# puts c.id
# puts c.scheduled_time
#end
#puts c
end

View File

@ -1,22 +0,0 @@
require 'FarmBotControlInterface'
# specific implementation to control the bot using marlin software on arduino and G-Code
class FarmBotControlInterface < FarmBotControlInterfaceAbstract
def initialize
#serialPort = ...
end
def moveTo( X, Y, Z )
#serialPort.Send("G21 X#{X} Y#{Y} Z#{Z}")
end
def moveHome
#serialPort.Send("G20")
end
def setSpeed( speed )
end
end

View File

@ -3,337 +3,337 @@ require 'firmata'
class HardwareInterface
def initialize
@pos_x = 0.0
@pos_y = 0.0
@pos_z = 0.0
# should come from configuration:
@move_home_timeout = 3 # seconds after which home command is aborted
@sleep_after_pin_set = 0.005
@sleep_after_enable = 0.001
def initialize
@pos_x = 0.0
@pos_y = 0.0
@pos_z = 0.0
# should come from configuration:
@move_home_timeout = 3 # seconds after which home command is aborted
@sleep_after_pin_set = 0.005
@sleep_after_enable = 0.001
@invert_axis_x = false
@invert_axis_y = false
@invert_axis_z = false
@invert_axis_x = false
@invert_axis_y = false
@invert_axis_z = false
@steps_per_unit_x = 10 # steps per milimeter for example
@steps_per_unit_y = 10
@steps_per_unit_z = 10
@boardDevice = "/dev/ttyACM0"
@steps_per_unit_x = 10 # steps per milimeter for example
@steps_per_unit_y = 10
@steps_per_unit_z = 10
@boardDevice = "/dev/ttyACM0"
@pin_led = 13
@pin_led = 13
@pin_stp_x = 54
@pin_dir_x = 55
@pin_enb_x = 38
@pin_stp_x = 54
@pin_dir_x = 55
@pin_enb_x = 38
@pin_stp_y = 60
@pin_dir_y = 61
@pin_enb_y = 56
@pin_stp_y = 60
@pin_dir_y = 61
@pin_enb_y = 56
@pin_stp_z = 46
@pin_dir_z = 48
@pin_enb_z = 62
@pin_stp_z = 46
@pin_dir_z = 48
@pin_enb_z = 62
@pin_min_x = 3
@pin_max_x = 2
@pin_min_x = 3
@pin_max_x = 2
@pin_min_y = 14
@pin_max_y = 15
@pin_min_y = 14
@pin_max_y = 15
@pin_min_z = 18
@pin_max_z = 19
@board = Firmata::Board.new @boardDevice
@board.connect
# set motor driver pins to output and set enables for the drivers to off
@board.set_pin_mode(@pin_enb_x, Firmata::Board::OUTPUT)
@board.set_pin_mode(@pin_dir_x, Firmata::Board::OUTPUT)
@board.set_pin_mode(@pin_stp_x, Firmata::Board::OUTPUT)
@pin_min_z = 18
@pin_max_z = 19
@board = Firmata::Board.new @boardDevice
@board.connect
# set motor driver pins to output and set enables for the drivers to off
@board.set_pin_mode(@pin_enb_x, Firmata::Board::OUTPUT)
@board.set_pin_mode(@pin_dir_x, Firmata::Board::OUTPUT)
@board.set_pin_mode(@pin_stp_x, Firmata::Board::OUTPUT)
@board.set_pin_mode(@pin_enb_y, Firmata::Board::OUTPUT)
@board.set_pin_mode(@pin_dir_y, Firmata::Board::OUTPUT)
@board.set_pin_mode(@pin_stp_y, Firmata::Board::OUTPUT)
@board.set_pin_mode(@pin_enb_y, Firmata::Board::OUTPUT)
@board.set_pin_mode(@pin_dir_y, Firmata::Board::OUTPUT)
@board.set_pin_mode(@pin_stp_y, Firmata::Board::OUTPUT)
@board.set_pin_mode(@pin_enb_z, Firmata::Board::OUTPUT)
@board.set_pin_mode(@pin_dir_z, Firmata::Board::OUTPUT)
@board.set_pin_mode(@pin_stp_z, Firmata::Board::OUTPUT)
@board.set_pin_mode(@pin_enb_z, Firmata::Board::OUTPUT)
@board.set_pin_mode(@pin_dir_z, Firmata::Board::OUTPUT)
@board.set_pin_mode(@pin_stp_z, Firmata::Board::OUTPUT)
@board.digital_write(@pin_enb_x, Firmata::Board::HIGH)
@board.digital_write(@pin_enb_y, Firmata::Board::HIGH)
@board.digital_write(@pin_enb_z, Firmata::Board::HIGH)
@board.digital_write(@pin_enb_x, Firmata::Board::HIGH)
@board.digital_write(@pin_enb_y, Firmata::Board::HIGH)
@board.digital_write(@pin_enb_z, Firmata::Board::HIGH)
# set the end stop pins to input
# set the end stop pins to input
@board.set_pin_mode(@pin_min_x, Firmata::Board::INPUT)
@board.set_pin_mode(@pin_min_y, Firmata::Board::INPUT)
@board.set_pin_mode(@pin_min_z, Firmata::Board::INPUT)
@board.set_pin_mode(@pin_min_x, Firmata::Board::INPUT)
@board.set_pin_mode(@pin_min_y, Firmata::Board::INPUT)
@board.set_pin_mode(@pin_min_z, Firmata::Board::INPUT)
@board.set_pin_mode(@pin_max_x, Firmata::Board::INPUT)
@board.set_pin_mode(@pin_max_y, Firmata::Board::INPUT)
@board.set_pin_mode(@pin_max_z, Firmata::Board::INPUT)
@board.set_pin_mode(@pin_max_x, Firmata::Board::INPUT)
@board.set_pin_mode(@pin_max_y, Firmata::Board::INPUT)
@board.set_pin_mode(@pin_max_z, Firmata::Board::INPUT)
@board.toggle_pin_reporting(@pin_min_x)
@board.toggle_pin_reporting(@pin_min_y)
@board.toggle_pin_reporting(@pin_min_z)
@board.toggle_pin_reporting(@pin_min_x)
@board.toggle_pin_reporting(@pin_min_y)
@board.toggle_pin_reporting(@pin_min_z)
@board.toggle_pin_reporting(@pin_max_x)
@board.toggle_pin_reporting(@pin_max_y)
@board.toggle_pin_reporting(@pin_max_z)
@board.toggle_pin_reporting(@pin_max_x)
@board.toggle_pin_reporting(@pin_max_y)
@board.toggle_pin_reporting(@pin_max_z)
end
end
# move the bot to the home position
# move the bot to the home position
def moveHomeX
moveHome(@pin_enb_x, @pin_dir_x, @pin_stp_x, @pin_min_x, @invert_axis_x)
@pos_x = 0
end
def moveHomeX
moveHome(@pin_enb_x, @pin_dir_x, @pin_stp_x, @pin_min_x, @invert_axis_x)
@pos_x = 0
end
def moveHomeY
moveHome(@pin_enb_y, @pin_dir_y, @pin_stp_y, @pin_min_y, @invert_axis_y)
@pos_y = 0
end
def moveHomeY
moveHome(@pin_enb_y, @pin_dir_y, @pin_stp_y, @pin_min_y, @invert_axis_y)
@pos_y = 0
end
def moveHomeZ
moveHome(@pin_enb_z, @pin_dir_z, @pin_stp_z, @pin_min_z, @invert_axis_z)
@pos_z = 0
end
def moveHomeZ
moveHome(@pin_enb_z, @pin_dir_z, @pin_stp_z, @pin_min_z, @invert_axis_z)
@pos_z = 0
end
def setSpeed( speed )
end
def setSpeed( speed )
end
def moveHome(pin_enb, pin_dir, pin_stp, pin_min, invert_axis)
def moveHome(pin_enb, pin_dir, pin_stp, pin_min, invert_axis)
# set the direction and enable
# set the direction and enable
@board.digital_write(pin_enb, Firmata::Board::LOW)
sleep @sleep_after_enable
@board.digital_write(pin_enb, Firmata::Board::LOW)
sleep @sleep_after_enable
if invert_axis == false
@board.digital_write(pin_dir, Firmata::Board::LOW)
else
@board.digital_write(pin_dir, Firmata::Board::HIGH)
end
sleep @sleep_after_pin_set
if invert_axis == false
@board.digital_write(pin_dir, Firmata::Board::LOW)
else
@board.digital_write(pin_dir, Firmata::Board::HIGH)
end
sleep @sleep_after_pin_set
start = Time.now
home = 0
start = Time.now
home = 0
# keep setting pulses at the step pin until the end stop is reached of a time is reached
# keep setting pulses at the step pin until the end stop is reached of a time is reached
while home == 0 do
while home == 0 do
@board.read_and_process
span = Time.now - start
if span > @move_home_timeout
home = 1
puts 'move home timed out'
end
@board.read_and_process
span = Time.now - start
if span > @move_home_timeout
home = 1
puts 'move home timed out'
end
if @board.pins[pin_min].value == 1
home = 1
puts 'end stop reached'
end
if @board.pins[pin_min].value == 1
home = 1
puts 'end stop reached'
end
if home == 0
@board.digital_write(pin_stp, Firmata::Board::HIGH)
sleep @sleep_after_pin_set
@board.digital_write(pin_stp, Firmata::Board::LOW)
sleep @sleep_after_pin_set
end
end
if home == 0
@board.digital_write(pin_stp, Firmata::Board::HIGH)
sleep @sleep_after_pin_set
@board.digital_write(pin_stp, Firmata::Board::LOW)
sleep @sleep_after_pin_set
end
end
# disavle motor driver
@board.digital_write(pin_dir, Firmata::Board::LOW)
# disavle motor driver
@board.digital_write(pin_dir, Firmata::Board::LOW)
end
end
# move the bot to the give coordinates
# move the bot to the give coordinates
def moveAbsolute( coord_x, coord_y, coord_z)
def moveAbsolute( coord_x, coord_y, coord_z)
puts '**move absolute **'
puts '**move absolute **'
# calculate the number of steps for the motors to do
# calculate the number of steps for the motors to do
steps_x = (coord_x - @pos_x) * @steps_per_unit_x
steps_y = (coord_y - @pos_y) * @steps_per_unit_y
steps_z = (coord_z - @pos_z) * @steps_per_unit_z
steps_x = (coord_x - @pos_x) * @steps_per_unit_x
steps_y = (coord_y - @pos_y) * @steps_per_unit_y
steps_z = (coord_z - @pos_z) * @steps_per_unit_z
puts "x steps #{steps_x}"
puts "y steps #{steps_y}"
puts "z steps #{steps_z}"
puts "x steps #{steps_x}"
puts "y steps #{steps_y}"
puts "z steps #{steps_z}"
moveSteps( steps_x, steps_y, steps_z )
moveSteps( steps_x, steps_y, steps_z )
end
end
# move the bot a number of units starting from the current position
# move the bot a number of units starting from the current position
def moveRelative( amount_x, amount_y, amount_z)
def moveRelative( amount_x, amount_y, amount_z)
puts '**move relative **'
puts '**move relative **'
# calculate the number of steps for the motors to do
# calculate the number of steps for the motors to do
steps_x = amount_x * @steps_per_unit_x
steps_y = amount_y * @steps_per_unit_y
steps_z = amount_z * @steps_per_unit_z
steps_x = amount_x * @steps_per_unit_x
steps_y = amount_y * @steps_per_unit_y
steps_z = amount_z * @steps_per_unit_z
puts "x steps #{steps_x}"
puts "y steps #{steps_y}"
puts "z steps #{steps_z}"
moveSteps( steps_x, steps_y, steps_z )
puts "x steps #{steps_x}"
puts "y steps #{steps_y}"
puts "z steps #{steps_z}"
moveSteps( steps_x, steps_y, steps_z )
end
end
def moveSteps( steps_x, steps_y, steps_z)
puts '**move steps **'
puts "x #{steps_x}"
puts "y #{steps_y}"
puts "z #{steps_z}"
# set the direction and the enable bit for the motor drivers
if (steps_x < 0 and @invert_axis_x == false) or (steps_x > 0 and @invert_axis_x == true)
@board.digital_write(@pin_enb_x, Firmata::Board::LOW)
@board.digital_write(@pin_dir_x, Firmata::Board::LOW)
end
if (steps_x > 0 and @invert_axis_x == false) or (steps_x < 0 and @invert_axis_x == true)
@board.digital_write(@pin_enb_x, Firmata::Board::LOW)
@board.digital_write(@pin_dir_x, Firmata::Board::HIGH)
end
if (steps_y < 0 and @invert_axis_y == false) or (steps_y > 0 and @invert_axis_y == true)
@board.digital_write(@pin_enb_y, Firmata::Board::LOW)
@sleep_after_enable
@board.digital_write(@pin_dir_y, Firmata::Board::LOW)
sleep @sleep_after_pin_set
end
if (steps_y > 0 and @invert_axis_y == false) or (steps_y < 0 and @invert_axis_y == true)
@board.digital_write(@pin_enb_y, Firmata::Board::LOW)
@sleep_after_enable
@board.digital_write(@pin_dir_y, Firmata::Board::HIGH)
sleep @sleep_after_pin_set
end
if (steps_z < 0 and @invert_axis_z == false) or (steps_z > 0 and @invert_axis_z == true)
@board.digital_write(@pin_enb_z, Firmata::Board::LOW)
@sleep_after_enable
@board.digital_write(@pin_dir_z, Firmata::Board::LOW)
@sleep_after_pin_set
end
if (steps_z > 0 and @invert_axis_z == false) or (steps_z < 0 and @invert_axis_z == true)
@board.digital_write(@pin_enb_z, Firmata::Board::LOW)
@board.digital_write(@pin_dir_z, Firmata::Board::HIGH)
@sleep_after_pin_set
end
# make the steps positive numbers
nr_steps_x = steps_x.abs
nr_steps_y = steps_y.abs
nr_steps_z = steps_z.abs
# loop until all steps are done
while nr_steps_x > 0 or nr_steps_y > 0 or nr_steps_z > 0 do
# read all input pins and check the end stops
@board.read_and_process
#puts "x min = #{@board.pins[@pin_min_x].value} | x max = #{@board.pins[@pin_max_x].value} "
#puts "y min = #{@board.pins[@pin_min_y].value} | y max = #{@board.pins[@pin_max_y].value} "
#puts "z min = #{@board.pins[@pin_min_z].value} | z max = #{@board.pins[@pin_max_z].value} "
if @board.pins[@pin_min_x].value == 1 and steps_x < 0
nr_steps_x = 0
@pos_x = 0
puts 'end stop min x'
end
if @board.pins[@pin_max_x].value == 1 and steps_x > 0
nr_steps_x = 0
puts 'end stop max x'
end
if @board.pins[@pin_min_y].value == 1 and steps_y < 0
nr_steps_y = 0
@pos_y = 0
puts 'end stop min y'
end
if @board.pins[@pin_max_y].value == 1 and steps_y > 0
nr_steps_y = 0
puts 'end stop max y'
end
if @board.pins[@pin_min_z].value == 1 and steps_z < 0
nr_steps_z = 0
@pos_z = 0
puts 'end stop min z'
end
if @board.pins[@pin_max_z].value == 1 and steps_z > 0
nr_steps_z = 0
puts 'end stop max z'
end
# send the step pulses to the motor drivers
if nr_steps_x > 0
@board.digital_write(@pin_stp_x, Firmata::Board::HIGH)
sleep @sleep_after_pin_set
@board.digital_write(@pin_stp_x, Firmata::Board::LOW)
sleep @sleep_after_pin_set
@pos_x += 1 / @steps_per_unit_x
nr_steps_x -= 1
end
if nr_steps_y > 0
@board.digital_write(@pin_stp_y, Firmata::Board::HIGH)
sleep @sleep_after_pin_set
@board.digital_write(@pin_stp_y, Firmata::Board::LOW)
sleep @sleep_after_pin_set
@pos_y += 1 / @steps_per_unit_y
nr_steps_y -= 1
end
if nr_steps_z > 0
@board.digital_write(@pin_stp_z, Firmata::Board::HIGH)
sleep @sleep_after_pin_set
@board.digital_write(@pin_stp_z, Firmata::Board::LOW)
sleep @sleep_after_pin_set
@pos_z += 1 / @steps_per_unit_z
nr_steps_z -= 1
end
end
# disable motor drivers
@board.digital_write(@pin_enb_x, Firmata::Board::HIGH)
@board.digital_write(@pin_enb_y, Firmata::Board::HIGH)
@board.digital_write(@pin_enb_z, Firmata::Board::HIGH)
#while (X - pos_X).abs < 1/steps_per_unit_X
puts '*move done*'
end
def moveSteps( steps_x, steps_y, steps_z)
puts '**move steps **'
puts "x #{steps_x}"
puts "y #{steps_y}"
puts "z #{steps_z}"
# set the direction and the enable bit for the motor drivers
if (steps_x < 0 and @invert_axis_x == false) or (steps_x > 0 and @invert_axis_x == true)
@board.digital_write(@pin_enb_x, Firmata::Board::LOW)
@board.digital_write(@pin_dir_x, Firmata::Board::LOW)
end
if (steps_x > 0 and @invert_axis_x == false) or (steps_x < 0 and @invert_axis_x == true)
@board.digital_write(@pin_enb_x, Firmata::Board::LOW)
@board.digital_write(@pin_dir_x, Firmata::Board::HIGH)
end
if (steps_y < 0 and @invert_axis_y == false) or (steps_y > 0 and @invert_axis_y == true)
@board.digital_write(@pin_enb_y, Firmata::Board::LOW)
@sleep_after_enable
@board.digital_write(@pin_dir_y, Firmata::Board::LOW)
sleep @sleep_after_pin_set
end
if (steps_y > 0 and @invert_axis_y == false) or (steps_y < 0 and @invert_axis_y == true)
@board.digital_write(@pin_enb_y, Firmata::Board::LOW)
@sleep_after_enable
@board.digital_write(@pin_dir_y, Firmata::Board::HIGH)
sleep @sleep_after_pin_set
end
if (steps_z < 0 and @invert_axis_z == false) or (steps_z > 0 and @invert_axis_z == true)
@board.digital_write(@pin_enb_z, Firmata::Board::LOW)
@sleep_after_enable
@board.digital_write(@pin_dir_z, Firmata::Board::LOW)
@sleep_after_pin_set
end
if (steps_z > 0 and @invert_axis_z == false) or (steps_z < 0 and @invert_axis_z == true)
@board.digital_write(@pin_enb_z, Firmata::Board::LOW)
@board.digital_write(@pin_dir_z, Firmata::Board::HIGH)
@sleep_after_pin_set
end
# make the steps positive numbers
nr_steps_x = steps_x.abs
nr_steps_y = steps_y.abs
nr_steps_z = steps_z.abs
# loop until all steps are done
while nr_steps_x > 0 or nr_steps_y > 0 or nr_steps_z > 0 do
# read all input pins and check the end stops
@board.read_and_process
#puts "x min = #{@board.pins[@pin_min_x].value} | x max = #{@board.pins[@pin_max_x].value} "
#puts "y min = #{@board.pins[@pin_min_y].value} | y max = #{@board.pins[@pin_max_y].value} "
#puts "z min = #{@board.pins[@pin_min_z].value} | z max = #{@board.pins[@pin_max_z].value} "
if @board.pins[@pin_min_x].value == 1 and steps_x < 0
nr_steps_x = 0
@pos_x = 0
puts 'end stop min x'
end
if @board.pins[@pin_max_x].value == 1 and steps_x > 0
nr_steps_x = 0
puts 'end stop max x'
end
if @board.pins[@pin_min_y].value == 1 and steps_y < 0
nr_steps_y = 0
@pos_y = 0
puts 'end stop min y'
end
if @board.pins[@pin_max_y].value == 1 and steps_y > 0
nr_steps_y = 0
puts 'end stop max y'
end
if @board.pins[@pin_min_z].value == 1 and steps_z < 0
nr_steps_z = 0
@pos_z = 0
puts 'end stop min z'
end
if @board.pins[@pin_max_z].value == 1 and steps_z > 0
nr_steps_z = 0
puts 'end stop max z'
end
# send the step pulses to the motor drivers
if nr_steps_x > 0
@board.digital_write(@pin_stp_x, Firmata::Board::HIGH)
sleep @sleep_after_pin_set
@board.digital_write(@pin_stp_x, Firmata::Board::LOW)
sleep @sleep_after_pin_set
@pos_x += 1 / @steps_per_unit_x
nr_steps_x -= 1
end
if nr_steps_y > 0
@board.digital_write(@pin_stp_y, Firmata::Board::HIGH)
sleep @sleep_after_pin_set
@board.digital_write(@pin_stp_y, Firmata::Board::LOW)
sleep @sleep_after_pin_set
@pos_y += 1 / @steps_per_unit_y
nr_steps_y -= 1
end
if nr_steps_z > 0
@board.digital_write(@pin_stp_z, Firmata::Board::HIGH)
sleep @sleep_after_pin_set
@board.digital_write(@pin_stp_z, Firmata::Board::LOW)
sleep @sleep_after_pin_set
@pos_z += 1 / @steps_per_unit_z
nr_steps_z -= 1
end
end
# disable motor drivers
@board.digital_write(@pin_enb_x, Firmata::Board::HIGH)
@board.digital_write(@pin_enb_y, Firmata::Board::HIGH)
@board.digital_write(@pin_enb_z, Firmata::Board::HIGH)
#while (X - pos_X).abs < 1/steps_per_unit_X
puts '*move done*'
end
end

View File

@ -1,83 +0,0 @@
# FarmBot schedule keeper
# Get commands from the database and execute them on the hardware
require 'date'
#require './lib/dbaccess.rb'
require './lib/controller.rb'
#require './lib/filehandler.rb'
#require "./lib/hardware/ramps.rb"
class Scheduler
def runFarmBot
check = $bot_dbaccess.checkRefresh
while $shutdown == 0 do
# keep checking the database for new data
puts 'checking schedule'
command = $bot_dbaccess.getCommandToExecute
$bot_dbaccess.saveRefresh
if command != nil
puts "command retrieved is scheduled for #{command.scheduled_time}"
puts "curent time is #{Time.now}"
#scheduled_time = command.scheduled_time
if command.scheduled_time <= Time.now or command.scheduled_time == nil
# execute the command now and set the status to done
puts 'execute command'
$bot_control.setCommand( command )
$bot_control.runCycle
$bot_dbaccess.setCommandToExecuteStatus('FINISHED')
else
puts 'wait for scheduled time or refresh'
refresh_received = false
wait_start_time = Time.now
# wait until the scheduled time has arrived, or wait for a minute or until a refresh it set in the database as a sign new data has arrived
while Time.now < wait_start_time + 60 and command.scheduled_time > Time.now - 1 and refresh_received == false
sleep 1
refresh_received = $bot_dbaccess.checkRefresh
puts 'refresh received' if refresh_received != false
end
end
else
puts 'no command found, wait'
refresh_received = false
wait_start_time = Time.now
# wait for a minute or until a refresh it set in the database as a sign new data has arrived
while Time.now < wait_start_time + 60 and refresh_received == false
sleep 1
refresh_received = $bot_dbaccess.checkRefresh
puts 'refresh received' if refresh_received != false
end
end
end
end
end

15
lib/skynet.rb 100644
View File

@ -0,0 +1,15 @@
require_relative 'skynet/skynet'
# The unfortunate use of globals in this project: The SocketIO library we use to
# talk to skynet stores blocks as lambdas and calls them later under a different
# context than that which they were defined. This means that even though we
# define the .on() events within the `Device` class, self does NOT refer to the
# device, but rather the current socket connection. Using a global is a quick
# fix to ensure we always have easy access to the device. Pull requests welcome.
$skynet = Skynet.new
#TODO: Daemonize this script:
#https://www.ruby-toolbox.com/categories/daemonizing

View File

@ -0,0 +1,49 @@
require 'securerandom'
module Credentials
# Stores a references to the credentials yml file, which is used to persist
# the user's skynet Token / ID across sessions. Returns String. parameterless
def credentials_file
'credentials.yml'
end
# Returns Hash containing the a :uuid and :token key. Triggers the creation of
# new credentials if the current ones are found to be invalid.
def credentials
if valid_credentials?
return load_credentials
else
return create_credentials
end
end
# Validates that the credentials file has a :uuid and :token key. Returns Bool
#
def valid_credentials?
if File.file?(credentials_file)
cred = load_credentials
return true if cred.has_key?(:uuid) && cred.has_key?(:token)
end
return false
end
# Uses the ruby securerandom library to make a new :uuid and :token. Also
# registers with a new device :uuid and :token on skynet.im . Returns Hash
# containing :uuid and :token key.
def create_credentials
hash = {
uuid: (@uuid = SecureRandom.uuid),
token: (@token = SecureRandom.hex)
}
`curl -s -X POST -d 'uuid=#{@uuid}&token=#{@token}&type=farmbot' \
http://skynet.im/devices`
File.open(credentials_file, 'w+') {|file| file.write(hash.to_yaml) }
return hash
end
### Loads the credentials file from disk and returns it as a ruby hash.
def load_credentials
return YAML.load(File.read(credentials_file))
end
end

View File

@ -0,0 +1,75 @@
# Get the JSON command, received through skynet, and send it to the farmbot command queue
require 'json'
require './lib/database/commandqueue.rb'
class MessageHandler
def initialize
@command_queue = CommandQueue.new
@last_time_stamp = ''
end
def handle_message(skynet, channel, message)
if message["message_type"].to_s.upcase == "SINGLE_COMMAND"
# assuming a json message
#{
# "message_type" : "single_command",
# "time_stamp" : 2001-01-01 01:01:01.001
# "command" : {
# "action" : "HOME X",
# "x" : 1,
# "y" : 2,
# "z" : 3,
# "speed" : "FAST",
# "amount" : 5,
# "delay" : 6
# }
#}
time_stamp = message['time_stamp']
if time_stamp != @last_time_stamp
@last_time_stamp = time_stamp
# send the command to the queue
delay = message['command']['delay']
action = message['command']['action']
x = message['command']['x']
y = message['command']['y']
z = message['command']['z']
speed = message['command']['speed']
amount = message['command']['amount']
delay = message['command']['delay']
puts '[new command]'
puts "received at #{Time.now}"
puts "action = #{action}"
puts "x = #{x}"
puts "y = #{y}"
puts "z = #{z}"
puts "speed = #{speed}"
puts "amount = #{amount}"
puts "delay = #{delay}"
@command_queue.create_new_command(Time.now + delay.to_i)
@command_queue.add_command_line(action, x.to_i, y.to_i, z.to_i, speed.to_s, amount.to_i)
@command_queue.save_new_command
skynet.confirmed = false
command =
{
:message_type => 'confirmation',
:time_stamp => Time.now.to_f.to_s,
:confirm_id => time_stamp
}
skynet.send_message("44128811-8c59-11e3-b99a-11476114e05f", command)
end
end
end
end

View File

@ -0,0 +1,50 @@
require 'json'
require_relative 'credentials'
require_relative 'web_socket'
require_relative 'messagehandler.rb'
# The Device class is temporarily inheriting from Tim's HardwareInterface.
# Eventually, we should merge the two projects, but this is good enough for now.
class Skynet
include Credentials, WebSocket
attr_accessor :socket, :uuid, :token, :identified, :confirmed, :confirmation_id
# On instantiation #new sets the @uuid, @token variables, connects to skynet
def initialize
super
identified = false
creds = credentials
@uuid = creds[:uuid]
@token = creds[:token]
@socket = SocketIO::Client::Simple.connect 'http://skynet.im:80'
create_socket_events
@message_handler = MessageHandler.new
end
def send_message(devices, message_hash )
@socket.emit("message",{:devices => devices, :message => message_hash})
end
# Acts as the entry point for message traffic captured from Skynet.im.
# This method is a stub for now until I have time to merge into Tim's
# controller code. Returns a MessageHandler object (a class yet created).
def handle_message(channel, message)
if message.class.to_s == 'Hash'
@message_handler.handle_message(self, channel, message)
end
if message.class.to_s == 'String'
message_hash = JSON.parse(message)
@message_handler.handle_message(self, channel, message_hash)
end
rescue
raise "Runtime error while attempting to parse message: #{message}."
end
end

View File

@ -0,0 +1,30 @@
require 'socket.io-client-simple'
module WebSocket
### Bootstraps all the events for skynet in the correct order. Returns Int.
def create_socket_events
#OTHER EVENTS: :identify, :identity, :ready, :disconnect, :message
create_identify_event
create_message_event
end
#Handles self identification on skynet by responding to the :indentify with a
#:identity event / credentials Hash.
def create_identify_event
@socket.on :identify do |data|
self.emit :identity, {
uuid: $skynet.uuid,
token: $skynet.token,
socketid: data['socketid']}
$skynet.identified = true
end
end
### Routes all skynet messages to handle_event() for interpretation.
def create_message_event
@socket.on :message do |channel, message|
$skynet.handle_message(channel, message)
end
end
end

View File

@ -1,8 +1,8 @@
puts '[FarmBot Controller Menu]'
puts 'starting up'
require './lib/dbaccess.rb'
require './lib/filehandler.rb'
require './lib/database/dbaccess.rb'
require './lib/database/filehandler.rb'
#require './lib/controller.rb'
#require "./lib/hardware/ramps.rb"

View File

@ -1,32 +0,0 @@
# FarmBot Controller runtime control
# Get commands from the database and execute them on the hardware
puts '[FarmBot Controller]'
puts 'starting up'
require 'date'
require './lib/dbaccess.rb'
require './lib/controller.rb'
require './lib/filehandler.rb'
require "./lib/hardware/ramps.rb"
require './lib/schedule.rb'
puts 'connecting to hardware'
$bot_control = Control.new
$bot_hardware = HardwareInterface.new
puts 'connecting to database'
$bot_dbaccess = DbAccess.new
$bot_schedule = Scheduler.new
$shutdown = 0
# controller loop
puts 'run'
$bot_schedule.runFarmBot

View File

@ -0,0 +1,67 @@
require 'spec_helper'
describe Device do
let(:device) do
Device.new
end
describe '#credentials_file' do
it 'returns name of the credentials YML file' do
expect(device.credentials_file).to eq('credentials.yml')
end
end
describe '#load_credentials' do
it 'loads the credentials YML file' do
expected = YAML.load(File.read(device.credentials_file))
actual = device.load_credentials
expect(expected).to eq(actual)
end
end
describe '#create_credentials' do
it 'creates device UUID' do
old_uuid = device.uuid
device.create_credentials
new_uuid = device.uuid
expect(old_uuid).to_not eq(new_uuid)
end
it 'creates device token' do
old_token = device.token
device.create_credentials
new_token = device.token
expect(old_token).to_not eq(new_token)
end
end
describe '#valid_credentials?' do
it 'ensures presence of UUID' do
device.stub(:load_credentials) { {} }
expect(device.load_credentials.key?(:uuid)).to be_false
expect(device.valid_credentials?).to be_false
end
it 'ensures presence of token' do
device.stub(:load_credentials) { {} }
expect(device.load_credentials.key?(:token)).to be_false
expect(device.valid_credentials?).to be_false
end
end
describe '#credentials' do
it 'has a valid `UUID` key' do
expect(device.credentials[:uuid]).to be_instance_of(String)
end
it 'has a valid `token` key' do
expect(device.credentials[:token]).to be_instance_of(String)
end
it 'creates a new set of credentials when invalid' do
old_creds = device.credentials
device.stub('valid_credentials?') { false }
new_creds = device.credentials
expect(old_creds).not_to eq(new_creds)
end
end
end

View File

@ -0,0 +1,26 @@
require 'spec_helper'
describe Device do
let(:device) do
Device.new
end
describe '#new' do
it 'initializes a new Device instance' do
expect(device).to be_kind_of(Device)
end
end
describe '#uuid' do
it 'returns a valid UUID' do
expect(device.uuid).to be_kind_of(String)
end
end
describe '#token' do
it 'returns a valid token' do
expect(device.token).to be_kind_of(String)
end
end
end

View File

@ -0,0 +1 @@
require File.expand_path('../spec_helper', __FILE__)

View File

@ -0,0 +1,10 @@
require 'simplecov'
SimpleCov.start
require 'rspec'
require File.expand_path('../../lib/farmbot-skynet', __FILE__)
RSpec.configure do |config|
#
end

4
sync.rb 100644
View File

@ -0,0 +1,4 @@
require_relative 'lib/skynet'
puts 'press enter to exit'
gets.chomp