Merge pull request #38 from rickcarlino/master

May 8th
pull/40/head
Rick Carlino 2014-05-09 06:48:51 -07:00
commit 5dc145396f
23 changed files with 441 additions and 1097 deletions

View File

@ -26,6 +26,7 @@ end
group :development, :test do
gem 'pry'
gem 'factory_girl_rails'
gem 'faker'
end
gem 'haml'
@ -35,6 +36,7 @@ group :test do
gem 'rspec-rails'
gem 'simplecov'
gem 'capybara'
gem 'launchy' #save_and_open_page while debugging integration tests.
end
gem 'devise', github: 'plataformatec/devise'

View File

@ -1,6 +1,6 @@
GIT
remote: git://github.com/mongoid/mongoid.git
revision: 026e32109178eef2a50b31924f45eee2b7e05c82
revision: e93a4837b0266db46c24aae172e184f57c847b04
specs:
mongoid (4.0.0.beta1)
activemodel (>= 4.0.0)
@ -10,7 +10,7 @@ GIT
GIT
remote: git://github.com/plataformatec/devise.git
revision: b0b18fb80508ae37a7460e1f6d1064adf9593cce
revision: 6129215afecc0b3628e0d46f5253f89e2a862c6b
specs:
devise (3.2.4)
bcrypt (~> 3.0)
@ -48,9 +48,10 @@ GEM
minitest (~> 5.1)
thread_safe (~> 0.1)
tzinfo (~> 1.1)
arel (5.0.0)
addressable (2.3.6)
arel (5.0.1.20140414130214)
bcrypt (3.1.7)
bson (2.2.2)
bson (2.2.3)
builder (3.2.2)
capybara (2.2.1)
mime-types (>= 1.16)
@ -71,14 +72,13 @@ GEM
docile (1.1.3)
erubis (2.7.0)
execjs (2.0.2)
<<<<<<< HEAD
factory_girl (4.4.0)
activesupport (>= 3.0.0)
factory_girl_rails (4.4.1)
factory_girl (~> 4.4.0)
railties (>= 3.0.0)
=======
>>>>>>> f3343624fe42bfb16f3428f39098c5913d4b0608
faker (1.3.0)
i18n (~> 0.5)
haml (4.0.5)
tilt
high_voltage (2.1.0)
@ -88,18 +88,20 @@ GEM
activesupport (>= 3.0.0)
multi_json (>= 1.2.0)
json (1.8.1)
launchy (2.4.2)
addressable (~> 2.3)
mail (2.5.4)
mime-types (~> 1.16)
treetop (~> 1.4.8)
method_source (0.8.2)
mime-types (1.25.1)
mini_portile (0.5.3)
minitest (5.3.2)
minitest (5.3.3)
moped (2.0.0.rc1)
bson (~> 2.2)
connection_pool (~> 2.0)
optionable (~> 0.2.0)
multi_json (1.9.2)
multi_json (1.10.0)
nokogiri (1.6.1)
mini_portile (~> 0.5.0)
optionable (0.2.0)
@ -133,30 +135,25 @@ GEM
activesupport (= 4.1.0)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rake (10.2.2)
rake (10.3.1)
rdoc (4.1.1)
json (~> 1.4)
rspec (1.3.2)
rspec-collection_matchers (0.0.4)
rspec-expectations (>= 2.99.0.beta1)
rspec-core (3.0.0.beta2)
rspec-support (= 3.0.0.beta2)
rspec-expectations (3.0.0.beta2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (= 3.0.0.beta2)
rspec-mocks (3.0.0.beta2)
rspec-support (= 3.0.0.beta2)
rspec-rails (3.0.0.beta2)
rspec (2.14.1)
rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0)
rspec-core (2.14.8)
rspec-expectations (2.14.5)
diff-lcs (>= 1.1.3, < 2.0)
rspec-mocks (2.14.6)
rspec-rails (2.14.2)
actionpack (>= 3.0)
activemodel (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-collection_matchers
rspec-core (= 3.0.0.beta2)
rspec-expectations (= 3.0.0.beta2)
rspec-mocks (= 3.0.0.beta2)
rspec-support (= 3.0.0.beta2)
rspec-support (3.0.0.beta2)
rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0)
sass (3.2.19)
sass-rails (4.0.3)
railties (>= 4.0.0, < 5.0)
@ -177,7 +174,7 @@ GEM
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sprockets-rails (2.1.1)
sprockets-rails (2.1.3)
actionpack (>= 3.0)
activesupport (>= 3.0)
sprockets (~> 2.8)
@ -204,13 +201,12 @@ DEPENDENCIES
capybara
coffee-rails
devise!
<<<<<<< HEAD
factory_girl_rails
=======
>>>>>>> f3343624fe42bfb16f3428f39098c5913d4b0608
faker
haml
high_voltage (~> 2.1.0)
jbuilder (~> 1.2)
launchy
mongoid (~> 4.0.0.beta1)!
pry
rails (= 4.1.0)

View File

@ -17,8 +17,14 @@
%a{href: "#"} Menu
%section.top-bar-section
%ul.left
-if current_user
%li{class: ("active" if current_page? page_path('dashboard'))}
= link_to 'Dashboard', page_path('dashboard')
%li{class: ("active" if current_page? page_path('data'))}
= link_to 'Data', page_path('data') # TODO: View helper for these items.
%li{class: ("active" if current_page? page_path('help'))}
= link_to 'Help', page_path('help')
%ul.right
- if current_user
%li

View File

@ -0,0 +1,95 @@
<ul class="button-group even-4">
<li><a href="#" class="button secondary"> Wed, Aug 21, 2:36 PM <span>74</span></a></li>
<li><a href="#" class="button secondary"> $4.12/day <span>Water </span></a></li>
<li><a href="#" class="button secondary"> $0.73/day <span>Pesticide </span></a></li>
<li><a href="#" class="button secondary"> $1.19/day <span>Fertilizer </span></a></li>
</ul>
<div class="container">
<div class="large-2 columns">
<div class="panel">
<input type="text" placeholder="Search">
<h5>My Plants &amp; Operations</h5>
<ul class="large-block-grid-2">
<li><img src="http://placehold.it/100&amp;text=Plant"></li>
<li><img src="http://placehold.it/100&amp;text=Plant"></li>
<li><img src="http://placehold.it/100&amp;text=Plant"></li>
<li><img src="http://placehold.it/100&amp;text=Plant"></li>
<li><img src="http://placehold.it/100&amp;text=Plant"></li>
<li><img src="http://placehold.it/100&amp;text=Plant"></li>
</ul>
<br>
<h5>All Plants &amp; Operations</h5>
<ul class="large-block-grid-2">
<li><img src="http://placehold.it/100&amp;text=Plant"></li>
<li><img src="http://placehold.it/100&amp;text=Plant"></li>
<li><img src="http://placehold.it/100&amp;text=Plant"></li>
<li><img src="http://placehold.it/100&amp;text=Plant"></li>
<li><img src="http://placehold.it/100&amp;text=Plant"></li>
<li><img src="http://placehold.it/100&amp;text=Plant"></li>
</ul>
</div><!-- End Panel -->
</div><!-- End Large-2 -->
<div class="large-8 columns">
<div class="panel">Farm Designer</div><!-- End Panel -->
</div><!-- End Large-8 -->
<div class="large-2 columns">
<div class="panel">
<input type="text" placeholder="Search">
<h4>Operation Agenda</h4>
<br>
<section>
<h6>Wednesday, Aug 21</h6>
<ul class="side-nav">
<li class="active"><a href="#">4am Water 1, 2, 3, 4</a></li>
<li><a href="#">10am Water 2, 3</a></li>
<li><a href="#">1pm Till 5</a></li>
<li class="divider"></li>
<li><a href="#">3pm Water 5</a></li>
<li><a href="#">5pm Water 5</a></li>
</ul>
</section>
<section>
<h6>Thursday, Aug 22</h6>
<ul class="side-nav">
<li class="active"><a href="#">4am Water 1, 2, 3, 4</a></li>
<li><a href="#">10am Water 2, 3</a></li>
<li><a href="#">1pm Till 5</a></li>
<li class="divider"></li>
<li><a href="#">3pm Water 5</a></li>
<li><a href="#">5pm Water 5</a></li>
</ul>
</section>
<section>
<h6>Friday, Aug 23</h6>
<ul class="side-nav">
<li class="active"><a href="#">4am Water 1, 2, 3, 4</a></li>
<li><a href="#">10am Water 2, 3</a></li>
<li><a href="#">1pm Till 5</a></li>
<li class="divider"></li>
<li><a href="#">3pm Water 5</a></li>
<li><a href="#">5pm Water 5</a></li>
</ul>
</section>
</div><!-- End Panel -->
</div><!-- End Large-2 -->
</div><!-- End Container -->

View File

@ -0,0 +1,256 @@
<ul class="button-group even-4">
<li><a href="#" class="button secondary"> Wed, Aug 21, 2:36 PM <span>74</span></a></li>
<li><a href="#" class="button secondary"> $4.12/day <span>Water </span></a></li>
<li><a href="#" class="button secondary"> $0.73/day <span>Pesticide </span></a></li>
<li><a href="#" class="button secondary"> $1.19/day <span>Fertilizer </span></a></li>
</ul>
<div class="container">
<!-- Side Bar -->
<div class="large-6 columns">
<div class="panel clearfix">
<div class="large-4 columns">
<h6>Farmbot</h6>
<dl class="accordion" data-accordion="">
<dd class="">
<a href="#panel1">Genesis Tracks</a>
<div id="panel1" class="content">
<p>Model: Genesis Tracks v2.6</p>
<p>Length: 36 meters </p>
<p>Width: 2 meters</p>
<p>Height: 18 centimeters</p>
<p>Installation date: 9/6/2013</p>
</div>
</dd>
<dd>
<a href="#panel2">Genesis Gantry</a>
<div id="panel2" class="content">
Panel 2. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</div>
</dd>
<dd>
<a href="#panel3">Richmond Cross Slide</a>
<div id="panel3" class="content">
Panel 3. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</div>
</dd>
<dd class="">
<a href="#panel4">Custom Tool Mount</a>
<div id="panel4" class="content">
Panel 3. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</div>
</dd>
</dl>
</div><!-- end large-4 -->
<div class="large-4 columns">
<h6>Tools</h6>
<dl class="accordion" data-accordion="">
<dd class="">
<a href="#panel5">Genesis Seed Injector</a>
<div id="panel5" class="content">
</div>
</dd>
<dd class="">
<a href="#panel6">FarmBot Pro Nozzle</a>
<div id="panel6" class="content">
Panel 2. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</div>
</dd>
<dd>
<a href="#panel7">Precision Burner</a>
<div id="panel7" class="content">
Panel 3. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</div>
</dd>
<dd class="">
<a href="#panel8">Alain Tiller</a>
<div id="panel8" class="content">
Panel 3. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</div>
</dd>
</dl>
</div><!-- end large-4 -->
<div class="large-4 columns">
<h6>Sensors</h6>
<dl class="accordion" data-accordion="">
<dd class="">
<a href="#panel9">Ambient Temperature</a>
<div id="panel9" class="content">
</div>
</dd>
<dd>
<a href="#panel10">Moisture Meter</a>
<div id="panel10" class="content">
Panel 2. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</div>
</dd>
<dd class="">
<a href="#panel11">Genesis pH</a>
<div id="panel11" class="content">
Panel 3. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</div>
</dd>
</dl>
</div><!-- end large-4 -->
</div> <!-- End Panel -->
<div class="panel">
<div class="container clearfix">
<div class="large-4 columns">Gantry (X)</div>
<div class="large-4 columns"><span class="label round success"><i class="fi-plus"></i></span> 46 <span class="label round alert"><i class="fi-minus"></i></span></div>
<div class="large-4 columns">Jog to: </div>
</div>
<br>
<div class="container clearfix">
<div class="large-4 columns">Cross-Slide (Y)</div>
<div class="large-4 columns"><span class="label round success"><i class="fi-plus"></i></span> 46 <span class="label round alert"><i class="fi-minus"></i></span></div>
<div class="large-4 columns">Jog to: </div>
</div>
<br>
<div class="container clearfix">
<div class="large-4 columns">Tool Mount (Z)</div>
<div class="large-4 columns"><span class="label round success"><i class="fi-plus"></i></span> 46 <span class="label round alert"><i class="fi-minus"></i></span></div>
<div class="large-4 columns">Jog to: </div>
</div>
<br><br>
<ul class="button-group even-3">
<li><a href="#" class="button small">Jog to Home</a></li>
<li><a href="#" class="button small">Calibrate</a></li>
<li><a href="#" class="button small alert">Shutdown</a></li>
</ul>
</div> <!-- End Panel -->
<a href="#" data-reveal-id="myModal" data-reveal="">
<div class="panel callout radius">
<h6>View System Information</h6>
</div>
</a>
</div>
<!-- End Side Bar -->
<div id="myModal" class="reveal-modal" data-reveal="" style="display: none; opacity: 1; visibility: hidden; top: -665px;">
<h2>System Information</h2>
<p>Next operation in: 9 minutes (Watering)</p>
<a class="close-reveal-modal">×</a>
</div>
<!-- Thumbnails -->
<div class="large-6 columns">
<div class="panel">
<nav class="top-bar" data-topbar="">
<ul class="title-area">
<li class="name">
<h1><a href="#">Resource Usage</a></h1>
</li>
<li class="toggle-topbar menu-icon"><a href="#">Menu</a></li>
</ul>
<section class="top-bar-section">
<!-- Right Nav Section -->
<ul class="right">
<li class="has-dropdown not-click">
<a href="#">Plot 1: Nitrogen Fertilizer</a>
<ul class="dropdown"><li class="title back js-generated"><h5><a href="javascript:void(0)">Back</a></h5></li>
<li><a href="#">First link in dropdown</a></li>
</ul>
</li>
<li class="has-dropdown not-click">
<a href="#">Plot 2: Water</a>
<ul class="dropdown"><li class="title back js-generated"><h5><a href="javascript:void(0)">Back</a></h5></li>
<li><a href="#">First link in dropdown</a></li>
</ul>
</li>
</ul>
</section></nav>
<div class="panel-chart"><img src="http://placehold.it/1000x400&amp;text=Graph"></div>
</div><!-- end panel -->
<div class="panel">
<nav class="top-bar" data-topbar="">
<ul class="title-area">
<li class="name">
<h1><a href="#">Finances</a></h1>
</li>
<li class="toggle-topbar menu-icon"><a href="#">Menu</a></li>
</ul>
<section class="top-bar-section">
<!-- Right Nav Section -->
<ul class="right">
<li class="has-dropdown not-click">
<a href="#">Plot 1: Revenue</a>
<ul class="dropdown"><li class="title back js-generated"><h5><a href="javascript:void(0)">Back</a></h5></li>
<li><a href="#">First link in dropdown</a></li>
</ul>
</li>
<li class="has-dropdown not-click">
<a href="#">Plot 2: Expenditures</a>
<ul class="dropdown"><li class="title back js-generated"><h5><a href="javascript:void(0)">Back</a></h5></li>
<li><a href="#">First link in dropdown</a></li>
</ul>
</li>
</ul>
</section></nav>
<div class="panel-chart"><img src="http://placehold.it/1000x400&amp;text=Graph"></div>
</div><!-- end panel -->
</div><!-- End Thumbnails -->
</div><!-- End Container -->

View File

@ -25,5 +25,13 @@ module Dss
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
config.generators do |g|
g.template_engine :haml
g.test_framework :rspec, :fixture_replacement => :factory_girl, :views => false, :helper => false
g.view_specs false
g.helper_specs false
g.fixture_replacement :factory_girl, :dir => 'spec/factories'
end
end
end

View File

@ -1,76 +0,0 @@
development:
# Configure available database sessions. (required)
sessions:
# Defines the default session. (required)
default:
# Defines the name of the default database that Mongoid can connect to.
# (required).
database: farmbot_backend_development
# Provides the hosts the default session can connect to. Must be an array
# of host:port pairs. (required)
hosts:
- localhost:27017
options:
# Change the default write concern. (default = { w: 1 })
# write:
# w: 1
# Change the default consistency model to primary, secondary.
# 'secondary' will send reads to secondaries, 'primary' sends everything
# to master. (default: primary)
# read: secondary_preferred
# How many times Moped should attempt to retry an operation after
# failure. (default: 30)
# max_retries: 30
# The time in seconds that Moped should wait before retrying an
# operation on failure. (default: 1)
# retry_interval: 1
# Configure Mongoid specific options. (optional)
options:
# Enable the identity map, needed for eager loading. (default: false)
# identity_map_enabled: false
# Includes the root model name in json serialization. (default: false)
# include_root_in_json: false
# Include the _type field in serializaion. (default: false)
# include_type_for_serialization: false
# Preload all models in development, needed when models use
# inheritance. (default: false)
# preload_models: false
# Protect id and type from mass assignment. (default: true)
# protect_sensitive_fields: true
# Raise an error when performing a #find and the document is not found.
# (default: true)
# raise_not_found_error: true
# Raise an error when defining a scope with the same name as an
# existing method. (default: false)
# scope_overwrite_exception: false
# Skip the database version check, used when connecting to a db without
# admin access. (default: false)
# skip_version_check: false
# Use Active Support's time zone in conversions. (default: true)
# use_activesupport_time_zone: true
# Ensure all times are UTC in the app side. (default: false)
# use_utc: false
test:
sessions:
default:
database: farmbot_backend_test
hosts:
- localhost:27017
options:
read: primary
# In the test environment we lower the retries and retry interval to
# low amounts for fast failures.
max_retries: 1
retry_interval: 0

View File

@ -1,4 +0,0 @@
---
:uuid: 40b8cfdf-92e9-450d-b603-1e9efbbecf47
:token: !binary |-
MzIwMTc3M2IwZDA3N2NmM2ZlZmQ5ZDQwZjNiZGE1NmI=

View File

@ -1,71 +0,0 @@
# ####################################
# Send something to farmbot controller
# ####################################
class FarmBotControllerComm
# send command to farmbot
def send_single_command(action, x, y, z, amount, speed, delay)
$skynet.confirmed = false
command =
{
:message_type => 'single_command',
:time_stamp => Time.now.to_f.to_s,
:command => {
:action => action,
:x => x,
:y => y,
:z => z,
:speed => speed,
:amount => amount,
:delay => delay
}}
$skynet.send_message($farmbot_uuid, command)
return wait_for_confirmation()
end
# send schedule to farmbot
def send_schedule(uuid, schedule)
$skynet.confirmed = false
sched_hash = schedule.to_hash
sched_hash[:message_type] = 'crop_schedule_update'
sched_hash[:time_stamp] = Time.now.to_f.to_s
puts sched_hash
$skynet.send_message(uuid, sched_hash)
return wait_for_confirmation()
end
def wait_for_confirmation
puts 'waiting for confirmation'
count = 0
while $skynet.confirmed != true and count < 10
sleep 0.5
print '.'
count += 1
end
puts ''
if $skynet.confirmed
puts 'confirmation received'
return true
else
puts 'confirmation timed out'
sleep 2
return false
end
end
end

View File

@ -1,118 +0,0 @@
#
# Classes to make a schedule that can be transmitted to the bot
#
class CropSchedule
attr_accessor :crop_id
def initialize
@commands = Array.new
end
def read_from_db(crop)
@crop_id = crop.crop_id
crop.scheduled_commands.each do |dbcommand|
command = CropScheduleCommand.new
command.read_from_db(dbcommand)
add_command(command)
end
end
def to_hash
command_nr = 0
schedule_hash = {:crop_id => @crop_id, :commands => {} }
commands = Hash.new
@commands.each do |command|
command_nr += 1
commands["command_#{command_nr}"]= command.to_hash
end
schedule_hash[:commands] = commands
return schedule_hash
end
def add_command(command)
@commands << command
end
end
class CropScheduleCommand
attr_accessor :scheduled_time
def initialize
@command_lines = Array.new
end
def read_from_db(command)
puts command
puts command.scheduled_time
@scheduled_time = command.scheduled_time
seq = 0
command.scheduled_command_lines.each do |dbline|
seq += 1
line = CropScheduleCommandLine.new
line.read_from_db(dbline, seq)
add_command_line(line)
end
end
def to_hash
sequence_nr = 0
command_hash = {:scheduled_time => @scheduled_time, :command_lines => {} }
command_lines = Hash.new
@command_lines.each do |line|
sequence_nr += 1
line.sequence_nr = sequence_nr
command_lines["command_line_#{sequence_nr}"]= line.to_hash
end
command_hash[:command_lines] = command_lines
return command_hash
end
def add_command_line(line)
@command_lines << line
end
end
class CropScheduleCommandLine
attr_accessor :action, :x, :y, :z, :amount, :speed, :sequence_nr
def read_from_db(line, sequence_nr)
@action = line.action
@x = line.coord_x
@y = line.coord_y
@z = line.coord_z
@amount = line.amount
@speed = line.speed
@sequence_nr = sequence_nr
end
def to_hash
line = {
:action => @action ,
:x => @x ,
:y => @y ,
:z => @z ,
:amount => @amount ,
:speed => @speed ,
:sequence_nr => @sequence_nr
}
return line
end
end

View File

@ -1,157 +0,0 @@
require 'bson'
require 'mongo'
require 'mongoid'
# Data classes
# This class is dedicated to retrieving and inserting commands into the schedule
# queue for the farm bot Mongo is used as the database, Mongoid as the
# databasemapper
class Command
include Mongoid::Document
embeds_many :commandlines
field :plant_id
field :crop_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
# Access class for the database
class DbAccess
def initialize
Mongoid.load!("config/mongo.yml", :development)
@last_command_retrieved = nil
@refresh_value = 0
@refresh_value_new = 0
@new_command = nil
end
def test
db_connection = Mongo::Connection.new
db_farmbot = db_connection['farmbot_development']
db_schedule = db_farmbot['schedule']
db_connection.database_names.each do |name|
db = db_connection.db(name)
db.collections.each do |collection|
puts "#{name} - #{collection.name}"
end
end
end
def create_new_command(scheduled_time, crop_id)
@new_command = Command.new
@new_command.scheduled_time = scheduled_time
@new_command.crop_id = crop_id
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 = 'scheduled'
@new_command.save
end
increment_refresh
end
def clear_schedule
Command.where(
:status => 'scheduled',
:scheduled_time.ne => nil
).order_by([:scheduled_time,:asc]).each do |command|
command.status = 'deleted'
command.save
end
end
def clear_crop_schedule(crop_id)
Command.where(
:status => 'scheduled',
:scheduled_time.ne => nil,
:crop_id => crop_id
).order_by([:scheduled_time,:asc]).each do |command|
command.status = 'deleted'
command.save
end
end
def get_command_to_execute
@last_command_retrieved = Command.where(
:status => 'scheduled',
: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

@ -1,134 +0,0 @@
# This module holds our data definitions to store all basic plant and watering data
# Mongo is used as the database, Mongoid as the databasemapper
require 'bson'
require 'mongo'
require 'mongoid'
#require 'bson_ext'
# The different farmbots are stored here
class FarmBot
include Mongoid::Document
embeds_many :crops
field :active
field :name
field :environmental_coefficient
field :uuid
# also needs user settings, security and whatsnot
end
# The list of crops tended by one farm bot. The crop is planted as a seed (age = 0) or when it has already sprouted.
# Coordinates x, y and z are used to drive the robot to the right place
# The age of maturing and harvesting should be customized to local conditions
class Crop
include Mongoid::Document
embedded_in :farmbot
embeds_many :grow_coefficients
embeds_many :waterings
embeds_many :historic_actions
embeds_many :scheduled_commands
field :plant_type
field :coord_x
field :coord_y
field :coord_z
field :radius
field :height
field :status
field :date_at_planting
field :age_at_planting
field :age_at_fully_grown
field :age_at_harvest
field :valid_data
field :crop_id
end
# Coefficients are used by the evapotransporation system. It expresses the amount of water (mm/day) the plant needs for a good growth at a certain age
# The values for the coefficient is the result of the local climate reference value multiplied with the
# The age is represented as a precentage, where 100% is fully grown and 200% is ready for harvesting
# a typical curve for a crops. the Y axis is here a multiplication factor for the reference crops (fictional grass or alfalfa)
#
# 1.0 *****
# *| **
# * | ***
# * | |
# ** | |
# 0.1 *** | |
# | | |
# 0% 100% 200%
class GrowCoefficient
include Mongoid::Document
embedded_in :crop
field :age_in_percentage
field :amount_water_manual
end
# These are the times when the robot is supposed to water the crop
class Watering
include Mongoid::Document
embedded_in :crop
field :time
field :percentage
end
# A log of what happended to the plant. Waterings and rainfall are the most important probably
class HistoricAction
include Mongoid::Document
embedded_in :crop
field :start_time
field :stop_time
field :action
field :amount
end
# This is the schedule for the next hours/days that the bot has to execute. This is synchronized to the bot.
class ScheduledCommand
include Mongoid::Document
embedded_in :crop
embeds_many :scheduled_command_lines
field :crop_id
field :schedule_id
field :one_time_command
field :scheduled_time
field :command_id
end
class ScheduledCommandLine
include Mongoid::Document
embedded_in :scheduled_command
field :action
field :coord_x
field :coord_y
field :coord_z
field :speed
field :amount
end

View File

@ -1,15 +0,0 @@
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

@ -1,49 +0,0 @@
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

@ -1,201 +0,0 @@
require 'json'
#require './lib/database/commandqueue.rb'
require './lib/database/dbcommand.rb'
require 'time'
# Get the JSON command, received through skynet, and send it to the farmbot
# command queue Parses JSON messages received through SkyNet.
class MessageHandler
attr_accessor :message
def initialize
@dbaccess = DbAccess.new
@last_time_stamp = ''
end
# A list of MessageHandler methods (as strings) that a Skynet User may access.
#
def whitelist
["single_command","crop_schedule_update"]
end
# Main entry point for (Hash) commands coming in over SkyNet.
# {
# "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
# }
# }
def handle_message(message)
puts 'handle_message'
#puts message
#puts message['message']
@message = message['message']
#fromUuid = message['fromUuid']
#puts fromUuid
requested_command = message['message']["message_type"].to_s.downcase
#puts requested_command
if whitelist.include?(requested_command)
#puts 'sending'
self.send(requested_command, message)
else
self.error(message)
end
end
# Handles an erorr (typically, an unauthorized or unknown message). Returns
# Hash.
def error
return {error: ""}
end
def single_command(message)
puts 'single_command'
#puts message
time_stamp = message['message']['time_stamp']
sender = message['fromUuid']
if time_stamp != @last_time_stamp
@last_time_stamp = time_stamp
# send the command to the queue
delay = message['message']['command']['delay']
action = message['message']['command']['action']
x = message['message']['command']['x']
y = message['message']['command']['y']
z = message['message']['command']['z']
speed = message['message']['command']['speed']
amount = message['message']['command']['amount']
delay = message['message']['command']['delay']
puts "[new command] received at #{Time.now} from #{sender}"
puts "[#{action}] x: #{x}, y: #{y}, z: #{z}, speed: #{speed}, amount: #{amount} delay: #{delay}"
@dbaccess.create_new_command(Time.now + delay.to_i,'single_command')
@dbaccess.add_command_line(action, x.to_i, y.to_i, z.to_i, speed.to_s, amount.to_i)
@dbaccess.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(sender, command)
end
end
def crop_schedule_update(message)
puts 'crop_schedule_update'
#puts message
time_stamp = message['message']['time_stamp']
sender = message['fromUuid']
puts "time_stamp #{time_stamp}"
puts "sender #{sender}"
if time_stamp != @last_time_stamp
@last_time_stamp = time_stamp
message_contents = message['message']
#puts message_contents
crop_id = message_contents['crop_id']
puts crop_id
puts 'removing old crop schedule'
@dbaccess.clear_crop_schedule(crop_id)
message_contents['commands'].each do |command|
#puts command
#puts command.class
#puts command[0]
#puts command[0].class
#puts command[1]
#puts command[1].class
scheduled_time = Time.parse(command[1]['scheduled_time'])
@dbaccess.create_new_command(scheduled_time, crop_id)
#@dbaccess.create_new_command(Time.now, 'debug')
puts scheduled_time
puts Time.now
command[1]['command_lines'].each do |command_line|
action = command_line[1]['action']
x = command_line[1]['x']
y = command_line[1]['y']
z = command_line[1]['z']
speed = command_line[1]['speed']
amount = command_line[1]['amount']
puts "[#{action}] x: #{x}, y: #{y}, z: #{z}, speed: #{speed}, amount: #{amount}"
@dbaccess.add_command_line(action, x.to_i, y.to_i, z.to_i, speed.to_s, amount.to_i)
end
@dbaccess.save_new_command
end
# send the command to the queue
#delay = message['message']['command']['delay']
#action = message['message']['command']['action']
#x = message['message']['command']['x']
#y = message['message']['command']['y']
#z = message['message']['command']['z']
#speed = message['message']['command']['speed']
#amount = message['message']['command']['amount']
#delay = message['message']['command']['delay']
#puts "[new command] received at #{Time.now} from #{sender}"
#puts "[#{action}] x: #{x}, y: #{y}, z: #{z}, speed: #{speed}, amount: #{amount} delay: #{delay}"
#@dbaccess.create_new_command(Time.now + delay.to_i)
#@dbaccess.add_command_line(action, x.to_i, y.to_i, z.to_i, speed.to_s, amount.to_i)
#@dbaccess.save_new_command
puts 'sending comfirmation'
$skynet.confirmed = false
command =
{
:message_type => 'confirmation',
:time_stamp => Time.now.to_f.to_s,
:confirm_id => time_stamp
}
$skynet.send_message(sender, command)
end
end
end

View File

@ -1,59 +0,0 @@
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'
@confirmed = false
create_socket_events
puts "uuid: #{@uuid}"
@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)
def handle_message(message)
puts "> message received at #{Time.now}"
#puts message
if message.class.to_s == 'Hash'
@message_handler.handle_message(message)
end
if message.class.to_s == 'String'
message_hash = JSON.parse(message)
@message_handler.handle_message(message_hash)
end
rescue
raise "Runtime error while attempting to parse message: #{message}."
end
end

View File

@ -1,33 +0,0 @@
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
@socket.on :message do |message|
$skynet.handle_message(message)
end
end
end

View File

@ -1,44 +0,0 @@
$farmbot_uuid = "063df52b-0698-4e1c-b2bb-4c0890019782"
require_relative 'lib/skynet/skynet'
require_relative 'lib/botcomm'
require_relative 'lib/cropschedule'
require_relative 'lib/database/dbcommand'
require_relative 'lib/database/dbfarmbot'
puts '[FarmBot schedule transmit]'
puts 'starting up'
# connecting to skynet framework
$skynet = Skynet.new
$farmbot_comm = FarmBotControllerComm.new
while $skynet.identified != true
sleep 0.5
print '.'
end
puts ''
puts 'connecting to database'
Mongoid.load!("config/mongo.yml", :development)
puts 'checking list of bots'
FarmBot.where(:active => true).order_by([:name,:asc]).each do |farmbot|
puts "checking bot #{farmbot.name} uuid #(farmbot.uuid)"
farmbot.crops.where(:valid_data => true).each do |crop|
puts "crop type=#{crop.plant_type} @ x=#{crop.coord_x} y=#{crop.coord_y}"
crop_schedule = CropSchedule.new
crop_schedule.read_from_db( crop )
$farmbot_comm.send_schedule(farmbot.uuid,crop_schedule)
end
end

View File

@ -1,104 +0,0 @@
$farmbot_uuid = "063df52b-0698-4e1c-b2bb-4c0890019782"
require_relative 'lib/device/device'
require_relative 'lib/botcomm'
puts '[FarmBot Remote Control]'
puts 'starting up'
# connecting to skynet framework
$device = Device.new
$farmbot_comm = FarmBotControllerComm.new
while $device.identified != true
sleep 0.5
print '.'
end
puts ''
# send schedule to farmbot
def send_singe_command(action, x, y, z, amount, speed, delay)
$farmbot_comm.send_single_command(action, x, y, z, amount, speed, delay)
end
$shutdown = 0
# just a little menu for testing
$move_size = 10
$command_delay = 0
while $shutdown == 0 do
system('cls')
system('clear')
puts '[FarmBot Controller Menu]'
puts ''
puts 'p - stop'
puts ''
puts "move size = #{$move_size}"
puts "command delay = #{$command_delay}"
puts ''
puts 'w - forward'
puts 's - back'
puts 'a - left'
puts 'd - right'
puts 'r - up'
puts 'f - down'
puts ''
puts 'z - home z axis'
puts 'x - home x axis'
puts 'c - home y axis'
puts ''
puts 'y - dose water'
puts ''
puts 'q - step size'
puts 'g - delay seconds'
puts ''
print 'command > '
input = gets
puts ''
case input.upcase[0]
when "P" # Quit
$shutdown = 1
puts 'Shutting down...'
when "O" # Get status
puts 'Not implemented yet. Press \'Enter\' key to continue.'
gets
when "Q" # Set step size
print 'Enter new step size > '
move_size_temp = gets
$move_size = move_size_temp.to_i if move_size_temp.to_i > 0
when "G" # Set step delay (seconds)
print 'Enter new delay in seconds > '
command_delay_temp = gets
$command_delay = command_delay_temp.to_i if command_delay_temp.to_i > 0
when "Y" # Water
send_single_command('DOSE WATER', 0, 0, 0, 15, 0, $command_delay)
when "Z" # Move to home
send_single_command('HOME Z', 0, 0, 0, 0, 0, $command_delay)
when "X" # Move to home
send_single_command('HOME X', 0, 0, 0, 0, 0, $command_delay)
when "C" # Move to home
send_single_command('HOME Y',0 ,0 ,-$move_size, 0, 0, $command_delay)
when "W" # Move forward
send_single_command('MOVE RELATIVE',0,$move_size, 0, 0, 0, $command_delay)
when "S" # Move back
send_single_command('MOVE RELATIVE',0,-$move_size, 0, 0, 0, $command_delay)
when "A" # Move left
send_single_command('MOVE RELATIVE', -$move_size, 0, 0, 0, 0, $command_delay)
when "D" # Move right
send_single_command('MOVE RELATIVE', $move_size, 0, 0, 0, 0, $command_delay)
when "R" # Move up
send_single_command('MOVE RELATIVE', 0, 0, $move_size, 0, 0, $command_delay)
when "F" # Move down
send_single_command("MOVE RELATIVE", 0, 0, -$move_size, 0, 0, $command_delay)
end
end

View File

@ -0,0 +1,9 @@
# Read about factories at https://github.com/thoughtbot/factory_girl
FactoryGirl.define do
factory :user do
name Faker::Name.name
email Faker::Internet.email
password Faker::Internet.password(8)
end
end

View File

@ -0,0 +1,9 @@
module Helpers
def sign_in_as(user)
#TODO: Sign in via Warden instead.
visit new_user_session_path
fill_in 'user_email', with: user.email
fill_in 'user_password', with: user.password
click_button 'Sign in'
end
end

View File

@ -1,7 +1,32 @@
require 'spec_helper'
describe 'User Session' do
xit 'logs the user in' do
it 'logs the user in' do
user = FactoryGirl.create(:user)
visit new_user_session_path
fill_in 'user_email', with: user.email
fill_in 'user_password', with: user.password
click_button 'Sign in'
expect(page).to have_content('Signed in successfully.')
end
it 'edits user settings' do
user = FactoryGirl.create(:user)
sign_in_as(user)
visit edit_user_registration_path
old_email = user.email
new_email = Faker::Internet.email
fill_in 'user_email', with: new_email
fill_in 'user_current_password', with: user.password
click_button 'Update'
expect(page).to have_content('Your account has been updated successfully.')
expect(user.reload.email).to eq(new_email)
end
it 'logs the user out' do
user = FactoryGirl.create(:user)
sign_in_as(user)
click_link 'Sign out'
expect(page).to have_content('Signed out successfully.')
end
end

View File

@ -8,6 +8,7 @@ require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'capybara/rails'
require 'capybara/rspec'
require 'features/helpers'
# Requires supporting ruby files with custom matchers and macros, etc, in
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
# run as spec files by default. This means that files in spec/support that end
@ -19,6 +20,8 @@ Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
RSpec.configure do |config|
config.include Helpers
config.after do
Mongoid.purge!
end