Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

point update. still working on elegant pin-matching

  • Loading branch information...
commit b6cecd22c5edcbfdffef97988d2aeea6498771b1 1 parent 46c3277
Darius authored
View
4 .gitignore
@@ -1,2 +1,4 @@
+pkg/**/*
features.html
-pkg/**/*
+features/support/cucumber-textmate.css
+features/support/texmate_formatter.rb
View
6 CHANGELOG
@@ -1,6 +1,8 @@
-*0.1.1 (Sept 3rd, 2009)
+*0.1.2 (Sept 19th, 2009)
-* * devices can be added to each other, with dumb algorithm for guessing pin matching:
+* * devices can be added to each other, with dumb algorithm for guessing pin matching (see associations)
+
+* TODO
- rake sensors:schematic FORMAT=pretty
- mysql testing database (i'm using postgresql)
View
10 Rakefile
@@ -135,15 +135,9 @@ namespace :postgresql do
end
desc 'Rebuild the PostgreSQL test databases'
- task :rebuild_databases => [:drop_databases, :build_databases]
-
-
+ task :rebuild_databases => [:drop_databases, :build_databases, :migrate]
end
-
-task :build_postgresql_databases => 'postgresql:build_databases'
-task :drop_postgresql_databases => 'postgresql:drop_databases'
-task :rebuild_postgresql_databases => 'postgresql:rebuild_databases'
-
+task :pg_rebuild => 'postgresql:rebuild_databases'
View
8 db/migrate/20090628073134_create_leds.rb
@@ -1,10 +1,10 @@
class CreateLeds < ActiveRecord::Migration
def self.up
create_table :leds, :force=>true do |t|
- t.integer :brightness
- t.float :amperage
- t.integer :wavelength
- t.references :parent, :polymorphic=>true
+ t.integer :brightness
+ t.float :amperage
+ t.integer :wavelength
+ t.references :parent, :polymorphic=>true
t.string :parent_association, :default=>""
end
create_join_pins :leds
View
2  features/build_a_bluetooth_detector.feature
@@ -29,7 +29,7 @@ Feature: Manage pins
| digital | type | number |
| false | Input | 0 |
- When an "Led" is added to a "BluetoothDetector" device as a "inputs.analog"
+ When an "Led" is added to a "BluetoothDetector" device into "inputs.analog"
Then a "Led" device has the following pins available:
| digital | type | number |
View
1  features/lib/abstract/input_device.rb
@@ -1,4 +1,5 @@
module InputDevice
def self.included(base)
+ def io_type; "InputDevice" end
end
end
View
6 features/lib/abstract/output_device.rb
@@ -6,11 +6,7 @@ def self.included(base)
emulator_connection :none
-
-
-
-
-
+ def io_type; "OutputDevice" end
end
View
5 features/lib/bluetooth_detector.rb
@@ -4,12 +4,13 @@ class BluetoothDetector < ActiveRecord::Base
has_one_sensor :power_led
#blink the signal strength
- has_one :signal_strength_low, :as=>:parent, :class_name=>'Led'#, :freq=>lambda{ 500 } # or other behavior proc?
+# has_one :signal_strength_low, :as=>:parent, :conditions=>{:parent_association=>:signal_strength_low}, :class_name=>'Led'#, :freq=>lambda{ 500 } # or other behavior proc?
+ has_one_sensor :signal_strength_low, :class_name=>'Led'#, :freq=>lambda{ 500 }
has_one_sensor :signal_strength_med, :class_name=>'Led'#, :freq=>lambda{ 500 }
has_one_sensor :signal_strength_high, :class_name=>'Led'#, :freq=>lambda{ 500 }
- has_many :leds, :as=>:parent
+ has_many_sensors :leds
# has_many :iwrap_entries
# has_many :cellphones, :through=>:iwrap_entries # bluetooth
View
142 features/steps/build_a_bluetooth_detector_steps.rb
@@ -5,25 +5,6 @@ def device(klass)
def alphabet_sort_from_hash(hash)
hash.sort_by{|k,v|k}.map{|(k,v)| v.to_i }
end
-=begin
-def find_or_create_association(association_name, base_device)
- found_association = device(base_device).send(association_name)
- new_association_record = found_association ? found_association : device(base_device).send("create_#{association_name}")
-end
-=end
-def build_association(base_device, association_name, klass = nil)
- klass ||= Kernel.const_get(association_name.camelcase.singularize)
-
- if eval("base_device.#{association_name}").is_a? Array
- # has_many
- eval "base_device.#{association_name} << klass.make"
- else
- # has_one (by process of elimination)
- base_device.send("#{association_name}=", klass.make)
- end
-
-end
-
def state_table(array_of_hashes, *states)
returning ({}) do |results|
@@ -37,96 +18,97 @@ def state_table(array_of_hashes, *states)
end
end
+Given /^"([^\"]*)" devices are defined to have pins:$/ do |klass, pins|
+ pin_counts = state_table(pins.hashes, "analog", 'inputs', 'digital', 'outputs')
+
+# analog_input_count,analog_output_count, digital_input_count, digital_output_count
+ expected_counts = alphabet_sort_from_hash(pin_counts)
+ actual_pins = [
+ device(klass).input_reflections.select {|r| r.options[:digital] == false },
+ device(klass).output_reflections.select{|r| r.options[:digital] == false },
+ device(klass).input_reflections.select {|r| r.options[:digital] == true },
+ device(klass).output_reflections.select{|r| r.options[:digital] == true }
+ ]
+
+ expected_counts.should == actual_pins.map(&:size)
+end
+When /^a.* "([^\"]*)" is added to a "([^\"]*)" device(.* "([^\"]*)"|)( \d+ times|)/ do |peripheral, base_device, *options|
+ multiple_times, association_chain = options.reverse[0..1]
+ # when no chain is included, cucumber should evaluate the peripheral association (eg :power_leds)
+ association_chain ||= peripheral.underscore.pluralize
-When /^I upload it$/ do
+
+ (0..multiple_times.to_i).collect do |times|
+ next if times == 1 # correction for (n-1) error
+ begin
+ @assoc = device(base_device).send("build_#{association_chain}")
+ rescue NoMethodError
+ debugger
+ @assoc = device(base_device).instance_eval("#{association_chain}.connect #{peripheral.classify}.new")
+ end
+ end
end
-Then /^I should see "([^\"]*)"$/ do |arg1|
+When /^the "([^\"]*)" and all associated device pins are configured\/connected/ do |klass|
pending
+ device(klass).valid?
end
-
-When /^I wait for (\d+) seconds$/ do |duration|
- pending
+When /^a.* "([^\"]*)" device is asked for "([^\"]*)"$/ do |klass, method_string|
+ @response = eval(klass + ".first." + method_string)
end
-Then /^"([^\"]*)" should be on$/ do |arg1|
- pending
-end
-Then /^"([^\"]*)" should drive "([^\"]*)"$/ do |arg1, arg2|
- pending
-end
+
Then /^a.* "([^\"]*)" device has the following (analog|digital|)(| )(inputs|outputs|)(| )pin assignments:$/ do |base_class, analog, space1, inputs, space2, desired_assignments_table|
- debugger
- input_pins = device(base_class).inputs
- # GOTCHA... not how it shows in cucumber docs
- stringified_hash_of_pin_attr = input_pins.map{|p| p.attributes.except('device_id', 'name').inject({}){|i,(k,v)| i.merge(k=>v.to_s)}}
+ association_chain = [inputs,analog]
+ association_chain.delete("")
+ association_chain << "pins" if association_chain.empty?
+ connected_pins = eval("device(base_class).#{association_chain.join('.')}")
- #matching???
- unless desired_assignments_table.hashes.empty?
- desired_assignments_table.diff! stringified_hash_of_pin_attr
- end
+ # GOTCHA... not how it shows in cucumber docs
+ # this really SHOULD be
+ # desired_assignments_table.diff!(connected_pins)
+
+ stringified_hash_of_pin_attr = connected_pins.map{|p| p.attributes.except('active_sensor_type', 'active_sensor_id', 'name').inject({}){|i,(k,v)| i.merge(k=>v.to_s)}}
+
+ if desired_assignments_table.hashes == stringified_hash_of_pin_attr
+ [].should == []
+ else
+ desired_assignments_table.diff! stringified_hash_of_pin_attr
+ end
end
Then /^a.* "([^\"]*)" device has the following (analog|digital|pins|)(| )(inputs|outputs|)(| )available:$/ do |base_class, analog, space1, inputs, space2, desired_availability_table|
association_chain = [inputs,analog]
association_chain.delete("")
- connected_pins = eval("device(base_class).#{association_chain.join('.')}.build_unassigned")
-
+ association_chain << "pins" if association_chain.empty?
+ unconnected_pins = eval("device(base_class).#{association_chain.join('.')}.build_unassigned")
- # can't figure out how to make connected_pins into a comparable table for diff!()... #CUCUMBER GOTCHA
- stringified_hash_of_pin_attr = connected_pins.map{|p| p.attributes.except('active_sensor_type', 'active_sensor_id', 'name').inject({}){|i,(k,v)| i.merge(k=>v.to_s)}}
- # [{"digital"=>"false", "number"=>"0", "type"=>"Input"}]
-
- desired_availability_table.hashes.should == stringified_hash_of_pin_attr
-end
+
+ # GOTCHA... not how it shows in cucumber docs
+ # this really SHOULD be
+ # desired_assignments_table.diff!(unconnected_pins)
+ stringified_hash_of_pin_attr = unconnected_pins.map{|p| p.attributes.except('active_sensor_type', 'active_sensor_id', 'name').inject({}){|i,(k,v)| i.merge(k=>v.to_s)}}
+# stringified_hash_of_pin_attr = unconnected_pins.map{|p| p.attributes.except('device_id', 'name').inject({}){|i,(k,v)| i.merge(k=>v.to_s)}}
-Given /^"([^\"]*)" devices are defined to have pins:$/ do |klass, pins|
- pin_counts = state_table(pins.hashes, "analog", 'inputs', 'digital', 'outputs')
-
-# analog_input_count,analog_output_count, digital_input_count, digital_output_count
- expected_counts = alphabet_sort_from_hash(pin_counts)
- actual_pins = [
- device(klass).input_reflections.select {|r| r.options[:digital] == false },
- device(klass).output_reflections.select{|r| r.options[:digital] == false },
- device(klass).input_reflections.select {|r| r.options[:digital] == true },
- device(klass).output_reflections.select{|r| r.options[:digital] == true }
- ]
-
- expected_counts.should == actual_pins.map(&:size)
-end
-When /^a.* "([^\"]*)" device is asked for "([^\"]*)"$/ do |klass, method_string|
- @response = eval(klass + ".first." + method_string)
+ if desired_availability_table.hashes == stringified_hash_of_pin_attr
+ [].should == []
+ else
+ desired_availability_table.diff! stringified_hash_of_pin_attr
+ end
end
+
Then /^it should be empty\.$/ do
@response.should be_an_instance_of Array
@response.length.should == 0
end
-
-
-When /^a.* "([^\"]*)" is added to a "([^\"]*)" device(.* "([^\"]*)"|)( \d+ times|)/ do |peripheral, base_device, *options|
- # try to dedupe the association name
- multiple_times, association_name = options.reverse[0..1]
- association_name ||= peripheral.underscore.pluralize
- klass = Kernel.const_get(peripheral) rescue nil
-
- (0..multiple_times.to_i).collect do |times| #hacky
- next if times == 1
- @assoc = build_association(device(base_device),association_name, klass) # when peripheral is included, you can evaluate a chain as the association_name
- end
-end
-When /^the "([^\"]*)" and all associated device pins are configured\/connected/ do |klass|
- pending
- device(klass).reconnect_pins
-end
-
Then /^the "([^\"]*)" device should have only the following pin assignments:$/ do |klass,assignments|
assignments.hashes
View
2  lib/active_sensor/acts_as_hardware.rb
@@ -25,8 +25,8 @@ def acts_as_hardware
# require 'active_sensor/custom_validations'
# Validations.extend ActiveSensor::CustomValidations
- # lazy-assignment helper. haven't decided on syntax yet
belongs_to :parent, :polymorphic=>true
+ # named_scope :children, lambda {|*assoc| assoc.blank? {} : {:conditions=>{:parent_association=>}}}
#MYTODO: delegate all methods called to the :children namespace to :devices (and then rename the db columns on device_connections)
# delegate :children, :to=>:devices
View
127 lib/active_sensor/associations.rb
@@ -36,44 +36,31 @@ def derive_class_name
module Associations
- def has_many_sensors(*arguments, &blk)
+ def has_many_sensors(assoc, *arguments, &blk)
options = arguments.extract_options!
- assoc = arguments.shift
-
- callback_options = {}
-# callback_options.merge! :before_add => ((options[:before_add] ? options[:before_add] : []) << Proc.new{|p,child| p.devices << child })
-# options.merge!(:parent_association=>assoc)
-
- has_many assoc, options.merge(callback_options), &blk
+ options.merge! :before_add => ((options[:before_add] ? options[:before_add] : []) << Proc.new{|p,child| child.parent_association = assoc.to_s })
+# options.merge! :after_add => ((options[:after_add] ? options[:after_add] : []) << Proc.new{|p,child| p.instance_eval("#{assoc}" << child) })
+ has_many assoc, options.deep_merge(:as=>:parent, :conditions=>{:parent_association=>assoc.to_s}), &blk
+
+ # delegations to take the form of #has_one calls. super-duper-reduntant-scoped by class definition!
+ # NB: conflicts with has_one ARE possible.
+ for method in ["build"]#, "replace", "destroy"] # "create" onlyly possible through :after_add options
+ define_method "#{method}_#{assoc.to_s.singularize}" do |*args|
+ results = self.instance_eval "#{assoc}.#{method} #{args.inspect if args}"
+ results.many? ? results : results.pop
+ end
+ end
end
- def has_one_sensor(*arguments, &blk)
+ def has_one_sensor(assoc, *arguments, &blk)
options = arguments.extract_options!
- assoc = arguments.shift
-
- options = options.deep_merge(:as=>:parent, :conditions=>{:parent_association=>assoc})
-# options.merge!(:parent_association=>assoc)
-
- has_one assoc, options, &blk
+ has_one assoc, options.deep_merge(:as=>:parent, :conditions=>{:parent_association=>assoc.to_s}), &blk
-=begin
- define_method "build_#{assoc}" do |*args|
-
- end
- define_method "replace_#{assoc}" do |*args|
-
- end
- define_method "destroy_#{assoc}" do |*args|
-
- end
-
- for method in ["create", "replace"]
- alias_method "#{method}_#{assoc}", "#{assoc}="
+ # the alias chain below can be removed after has_one conditions set association before_add correctly (3.0?)
+ define_method "build_#{assoc}_with_parent_association" do |*args|
+ options = args.extract_options!
+ self.send "build_#{assoc}_without_parent_association", options.reverse_merge(:parent_association=>assoc.to_s)
end
- for method in ["create"]#, "replace"]
- alias_method "#{method}_#{assoc}", "#{assoc}="
- end
-=end
-
+ alias_method_chain "build_#{assoc}", "parent_association"
end
def has_one_pin(*arguments)
@@ -183,15 +170,70 @@ def create_power_reflections(arguments,options={},&extension)
end
module PinConverters
- def <<(device)
- case device
- when Pin
- "is a pin" #self.push(device)
- when OutputDevice; "is an ouput_device" #self.inputs reflection.options.merge(:device=>reflection.owner)
- when InputDevice; "is an input_device" #self.outputs
- else
- "we don't know if this is an output or an input device. Please specify pins to add"
+ def connect(device, *args)
+ options = args.extract_options!
+ validate_io_symetricality(device) #unless options[:force]
+ validate_sufficient_pins(device) #if options[:fully_connected]
+
+ begin
+ connect_pins(device) #:digital options?!
+ rescue NotEnoughPins
+ raise NotEnoughDigitalPins unless options[:allow_analog_use]
+ connect_pins(device,:analog=>true)
end
+
+ parent_association = options[:parent_association] ||= case device.parent_association
+ when "", NilClass; device.class_name.underscore
+ else; device.parent_association
+ end
+
+# proxy_owner.send("#{parent_association}=",device)
+ end
+
+ def connect_pins(peripheral, *args)
+
+ # options!
+
+ available = proxy_owner.pins.build_unassigned
+ assignments = device.pins.build_unassigned.inject([]) do |i,device_pin|
+ options[:pins] ||= # a proc
+ case options[:pins].first
+ when Numeric; proc { true }# integers compared with numbers, strings with names??
+ when String; proc { true }
+ else; proc { true }
+ end
+
+ found_pins = available.select{|parent_pin|
+ options[:pins].call && #yield &&
+ parent_pin.type == chained_conditions[:class_name] &&
+ parent_pin.digital == chained_conditions[:digital]
+ }
+# raise NotEnoughPins if found_pins.empty?
+ i << available.delete(found_pins.pop)
+ end
+ end
+
+# private
+ def validate_io_symetricality(device)
+ case device.io_type
+ when "Pin"; raise ArgumentError, "is a pin"
+ when "OutputDevice"; chained_conditions
+ when "InputDevice"; "is an input_device" #self.outputs
+ else
+ raise AmbiguousDeviceType, "not sure whether to create input or output pins. Please specify."
+ end
+ end
+
+ def validate_sufficient_pins(device)
+
+=begin available_pins = proxy_owner.pins.build_unassigned
+ begin
+ device.inputs << proxy_owner.pins.build_unassigned
+ rescue NotEnoughDigitalPins
+
+ end
+=end
+ true
end
end
@@ -232,11 +274,12 @@ def build_unassigned
class ActiveRecord::Associations::AssociationCollection
def chained_conditions
return {} if self.proxy_reflection.class_name == 'Pin'
- {:class_name=> self.proxy_reflection.class_name}
+ {:class_name=> self.proxy_reflection.class_name }
end
end
class ActiveRecord::NamedScope::Scope
include BuildUnassignedPins
+ include PinConverters
# goes up the proxy_ chain, merging all options
def chained_conditions
@chained_conditions ||= begin
View
2  lib/active_sensor/version.rb
@@ -2,7 +2,7 @@ module ActiveSensor #:nodoc:
module VERSION #:nodoc:
MAJOR = 0
MINOR = 1
- TINY = 1
+ TINY = 2
STRING = [MAJOR, MINOR, TINY].join('.')
end
View
3  lib/pin.rb
@@ -11,6 +11,7 @@ class << self
def device_vocabulary; [] end # override for pins -- they don't have pins!
end
+ def io_type; "Pin" end
def analog?; digital? == false end
def output?; type == 'Output' end
def input?; type == 'Input' end
@@ -31,8 +32,6 @@ def freq=(something); true end
class Input < Pin
-# validates_presence_of :number
end
class Output < Pin
-# validates_presence_of :number
end
View
BIN  pkg/activesensor-0.1.1.gem
Binary file not shown
Please sign in to comment.
Something went wrong with that request. Please try again.