From 16fe1034516c64c33d44e48cf4bf4d5bb0327976 Mon Sep 17 00:00:00 2001 From: TimEvWw Date: Thu, 13 Feb 2014 22:04:39 +0100 Subject: [PATCH] merging with skynet --- .gitignore | 22 + Gemfile | 19 + README.md | 45 +- README_2.md | 9 + Rakefile | 0 app/models/command.rb | 35 ++ controller.rb | 19 + lib/controller.rb | 153 ++++--- lib/database/commandqueue.rb | 78 ++++ lib/{ => database}/controlcommand.rb | 0 lib/{ => database}/dbaccess.rb | 45 +- lib/{ => database}/filehandler.rb | 0 lib/hardware/marlin.rb | 22 - lib/hardware/ramps.rb | 562 ++++++++++++------------- lib/schedule.rb | 83 ---- lib/skynet.rb | 15 + lib/skynet/credentials.rb | 49 +++ lib/skynet/messagehandler.rb | 75 ++++ lib/skynet/skynet.rb | 50 +++ lib/skynet/web_socket.rb | 30 ++ menu.rb | 4 +- runtime.rb | 32 -- spec/device/device_credentials_spec.rb | 67 +++ spec/device/device_spec.rb | 26 ++ spec/farmbot-skynet_spec.rb | 1 + spec/spec_helper.rb | 10 + sync.rb | 4 + 27 files changed, 926 insertions(+), 529 deletions(-) create mode 100644 .gitignore create mode 100644 Gemfile create mode 100644 README_2.md create mode 100644 Rakefile create mode 100644 app/models/command.rb create mode 100644 controller.rb create mode 100644 lib/database/commandqueue.rb rename lib/{ => database}/controlcommand.rb (100%) rename lib/{ => database}/dbaccess.rb (78%) rename lib/{ => database}/filehandler.rb (100%) delete mode 100644 lib/hardware/marlin.rb delete mode 100644 lib/schedule.rb create mode 100644 lib/skynet.rb create mode 100644 lib/skynet/credentials.rb create mode 100644 lib/skynet/messagehandler.rb create mode 100644 lib/skynet/skynet.rb create mode 100644 lib/skynet/web_socket.rb delete mode 100644 runtime.rb create mode 100644 spec/device/device_credentials_spec.rb create mode 100644 spec/device/device_spec.rb create mode 100644 spec/farmbot-skynet_spec.rb create mode 100644 spec/spec_helper.rb create mode 100644 sync.rb diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..30abe54f --- /dev/null +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..dafd9aaa --- /dev/null +++ b/Gemfile @@ -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' + diff --git a/README.md b/README.md index f036b2bc..61bb69b5 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/README_2.md b/README_2.md new file mode 100644 index 00000000..7d45a7ab --- /dev/null +++ b/README_2.md @@ -0,0 +1,9 @@ +gem install firmata + +gem install bson + +gem install mongo + +gem install mongoid + +gem install socket.io-client-simple diff --git a/Rakefile b/Rakefile new file mode 100644 index 00000000..e69de29b diff --git a/app/models/command.rb b/app/models/command.rb new file mode 100644 index 00000000..0714da2e --- /dev/null +++ b/app/models/command.rb @@ -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 + diff --git a/controller.rb b/controller.rb new file mode 100644 index 00000000..21ba6ccb --- /dev/null +++ b/controller.rb @@ -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 diff --git a/lib/controller.rb b/lib/controller.rb index 27f3538e..9c02221b 100644 --- a/lib/controller.rb +++ b/lib/controller.rb @@ -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 \ No newline at end of file diff --git a/lib/database/commandqueue.rb b/lib/database/commandqueue.rb new file mode 100644 index 00000000..789d3ef3 --- /dev/null +++ b/lib/database/commandqueue.rb @@ -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 \ No newline at end of file diff --git a/lib/controlcommand.rb b/lib/database/controlcommand.rb similarity index 100% rename from lib/controlcommand.rb rename to lib/database/controlcommand.rb diff --git a/lib/dbaccess.rb b/lib/database/dbaccess.rb similarity index 78% rename from lib/dbaccess.rb rename to lib/database/dbaccess.rb index de09f1c8..be241adf 100644 --- a/lib/dbaccess.rb +++ b/lib/database/dbaccess.rb @@ -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 \ No newline at end of file diff --git a/lib/filehandler.rb b/lib/database/filehandler.rb similarity index 100% rename from lib/filehandler.rb rename to lib/database/filehandler.rb diff --git a/lib/hardware/marlin.rb b/lib/hardware/marlin.rb deleted file mode 100644 index 6ee35f34..00000000 --- a/lib/hardware/marlin.rb +++ /dev/null @@ -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 - diff --git a/lib/hardware/ramps.rb b/lib/hardware/ramps.rb index 36ad3ea8..d19694b9 100644 --- a/lib/hardware/ramps.rb +++ b/lib/hardware/ramps.rb @@ -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 diff --git a/lib/schedule.rb b/lib/schedule.rb deleted file mode 100644 index af323918..00000000 --- a/lib/schedule.rb +++ /dev/null @@ -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 diff --git a/lib/skynet.rb b/lib/skynet.rb new file mode 100644 index 00000000..dba52ad8 --- /dev/null +++ b/lib/skynet.rb @@ -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 \ No newline at end of file diff --git a/lib/skynet/credentials.rb b/lib/skynet/credentials.rb new file mode 100644 index 00000000..2825d553 --- /dev/null +++ b/lib/skynet/credentials.rb @@ -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 \ No newline at end of file diff --git a/lib/skynet/messagehandler.rb b/lib/skynet/messagehandler.rb new file mode 100644 index 00000000..a67e8cac --- /dev/null +++ b/lib/skynet/messagehandler.rb @@ -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 \ No newline at end of file diff --git a/lib/skynet/skynet.rb b/lib/skynet/skynet.rb new file mode 100644 index 00000000..c28a65be --- /dev/null +++ b/lib/skynet/skynet.rb @@ -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 \ No newline at end of file diff --git a/lib/skynet/web_socket.rb b/lib/skynet/web_socket.rb new file mode 100644 index 00000000..23566958 --- /dev/null +++ b/lib/skynet/web_socket.rb @@ -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 \ No newline at end of file diff --git a/menu.rb b/menu.rb index 02db3d97..585613e9 100644 --- a/menu.rb +++ b/menu.rb @@ -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" diff --git a/runtime.rb b/runtime.rb deleted file mode 100644 index 65f40d2e..00000000 --- a/runtime.rb +++ /dev/null @@ -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 diff --git a/spec/device/device_credentials_spec.rb b/spec/device/device_credentials_spec.rb new file mode 100644 index 00000000..c9f081f2 --- /dev/null +++ b/spec/device/device_credentials_spec.rb @@ -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 \ No newline at end of file diff --git a/spec/device/device_spec.rb b/spec/device/device_spec.rb new file mode 100644 index 00000000..1a2bedb5 --- /dev/null +++ b/spec/device/device_spec.rb @@ -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 \ No newline at end of file diff --git a/spec/farmbot-skynet_spec.rb b/spec/farmbot-skynet_spec.rb new file mode 100644 index 00000000..bb96b4ee --- /dev/null +++ b/spec/farmbot-skynet_spec.rb @@ -0,0 +1 @@ +require File.expand_path('../spec_helper', __FILE__) \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 00000000..9144f454 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,10 @@ +require 'simplecov' +SimpleCov.start +require 'rspec' + +require File.expand_path('../../lib/farmbot-skynet', __FILE__) + +RSpec.configure do |config| + # +end + diff --git a/sync.rb b/sync.rb new file mode 100644 index 00000000..a6f32888 --- /dev/null +++ b/sync.rb @@ -0,0 +1,4 @@ +require_relative 'lib/skynet' + +puts 'press enter to exit' +gets.chomp