From 402c05c8ed4ccde0e064b6e0293d3ec1f95e39c3 Mon Sep 17 00:00:00 2001 From: Zshawn Syed Date: Tue, 29 Mar 2016 23:39:00 -0500 Subject: [PATCH 1/4] Create driver class and associated tests. Fleshed out tests further to include edge cases and sad paths. --- lib/pi_piper/sysfs.rb | 7 +- lib/pi_piper/sysfs/driver.rb | 77 ++++++++++ lib/pi_piper/sysfs/sysfs.rb | 73 ---------- lib/pi_piper/sysfs/version.rb | 4 +- pi_piper-sysfs.gemspec | 2 +- spec/pi_piper/sysfs/driver_spec.rb | 217 +++++++++++++++++++++++++++++ spec/pi_piper/sysfs_spec.rb | 122 +--------------- 7 files changed, 304 insertions(+), 198 deletions(-) create mode 100644 lib/pi_piper/sysfs/driver.rb delete mode 100644 lib/pi_piper/sysfs/sysfs.rb create mode 100644 spec/pi_piper/sysfs/driver_spec.rb diff --git a/lib/pi_piper/sysfs.rb b/lib/pi_piper/sysfs.rb index 4c23e62..9c4aac4 100644 --- a/lib/pi_piper/sysfs.rb +++ b/lib/pi_piper/sysfs.rb @@ -1,6 +1,7 @@ -require "pi_piper/sysfs/version" -require "pi_piper/sysfs/sysfs" +require 'pi_piper' +require 'pi_piper/sysfs/version' +require 'pi_piper/sysfs/driver' module PiPiper - self.driver= PiPiper::Sysfs + #self.driver = PiPiper::Sysfs::Driver.new end diff --git a/lib/pi_piper/sysfs/driver.rb b/lib/pi_piper/sysfs/driver.rb new file mode 100644 index 0000000..747b3d4 --- /dev/null +++ b/lib/pi_piper/sysfs/driver.rb @@ -0,0 +1,77 @@ +module PiPiper + module Sysfs + class Driver < PiPiper::Driver + + GPIO_HIGH = 1 + GPIO_LOW = 0 + + def initialize + @exported_pins = Set.new + end + + def close + unexport_all + @exported_pins.empty? + end + + # Support GPIO pins + def pin_direction(pin, direction) + raise ArgumentError, "direction should be :in or :out" unless [:in, :out].include? direction + export(pin) + raise RuntimeError, "Pin #{pin} not exported" unless exported?(pin) + File.write("/sys/class/gpio/gpio#{pin}/direction", direction) + end + + def pin_read(pin) + raise ArgumentError, "Pin #{pin} not exported" unless exported?(pin) + File.read("/sys/class/gpio/gpio#{pin}/value").to_i + end + + def pin_write(pin, value) + raise ArgumentError, "value should be GPIO_HIGH or GPIO_LOW" unless [GPIO_LOW, GPIO_HIGH].include? value + raise ArgumentError, "Pin #{pin} not exported" unless exported?(pin) + File.write("/sys/class/gpio/gpio#{pin}/value", value) + end + + def pin_set_pud(pin, value) + raise NotImplementedError, "Pull up/down not available with this driver. keep it on :off" unless value == :off + end + + def pin_set_trigger(pin, trigger) + raise ArgumentError, "trigger should be :falling, :rising, :both or :none" unless [:falling, :rising, :both, :none].include? trigger + raise ArgumentError, "Pin #{pin} not exported" unless exported?(pin) + File.write("/sys/class/gpio/gpio#{pin}/edge", trigger) + end + + def pin_wait_for(pin) + fd = File.open("/sys/class/gpio/gpio#{pin}/value", "r") + fd.read + IO.select(nil, nil, [fd], nil) + true + end + + # Specific behaviours + + def unexport(pin) + File.write("/sys/class/gpio/unexport", pin) + @exported_pins.delete(pin) + end + + def unexport_all + @exported_pins.dup.each { |pin| unexport(pin) } + end + + def exported?(pin) + @exported_pins.include?(pin) + end + + private + + def export(pin) + raise RuntimeError, "pin #{pin} is already reserved by another Pin instance" if @exported_pins.include?(pin) + File.write("/sys/class/gpio/export", pin) + @exported_pins << pin + end + end + end +end diff --git a/lib/pi_piper/sysfs/sysfs.rb b/lib/pi_piper/sysfs/sysfs.rb deleted file mode 100644 index 2ef92ff..0000000 --- a/lib/pi_piper/sysfs/sysfs.rb +++ /dev/null @@ -1,73 +0,0 @@ -module PiPiper - class Sysfs < Driver - - GPIO_HIGH = 1 - GPIO_LOW = 0 - - def initialize - @exported_pins = Set.new - end - - def close - unexport_all - @exported_pins.empty? - end - -# Support GPIO pins - def pin_direction(pin, direction) - raise ArgumentError, "direction should be :in or :out" unless [:in, :out].include? direction - export(pin) - raise RuntimeError, "Pin #{pin} not exported" unless exported?(pin) - File.write("/sys/class/gpio/gpio#{pin}/direction", direction) - end - - def pin_read(pin) - raise ArgumentError, "Pin #{pin} not exported" unless exported?(pin) - File.read("/sys/class/gpio/gpio#{pin}/value").to_i - end - - def pin_write(pin, value) - raise ArgumentError, "value should be GPIO_HIGH or GPIO_LOW" unless [GPIO_LOW, GPIO_HIGH].include? value - raise ArgumentError, "Pin #{pin} not exported" unless exported?(pin) - File.write("/sys/class/gpio/gpio#{pin}/value", value) - end - - def pin_set_pud(pin, value) - raise NotImplementedError, "Pull up/down not avaliable with this driver. keep it on :off" unless value == :off - end - - def pin_set_trigger(pin, trigger) - raise ArgumentError, "trigger should be :falling, :rising, :both or :none" unless [:falling, :rising, :both, :none].include? trigger - raise ArgumentError, "Pin #{pin} not exported" unless exported?(pin) - File.write("/sys/class/gpio/gpio#{pin}/edge", trigger) - end - - def pin_wait_for(pin) - fd = File.open("/sys/class/gpio/gpio#{pin}/value", "r") - fd.read - IO.select(nil, nil, [fd], nil) - true - end - -# Specific behaviours - - def export(pin) - raise RuntimeError, "pin #{pin} is already reserved by another Pin instance" if @exported_pins.include?(pin) - File.write("/sys/class/gpio/export", pin) - @exported_pins << pin - end - - def unexport(pin) - File.write("/sys/class/gpio/unexport", pin) - @exported_pins.delete(pin) - end - - def unexport_all - @exported_pins.dup.each { |pin| unexport(pin) } - end - - def exported?(pin) - @exported_pins.include?(pin) - end - end -end diff --git a/lib/pi_piper/sysfs/version.rb b/lib/pi_piper/sysfs/version.rb index 5da3e42..33a5c38 100644 --- a/lib/pi_piper/sysfs/version.rb +++ b/lib/pi_piper/sysfs/version.rb @@ -1,6 +1,6 @@ module PiPiper class Driver; end - class Sysfs < Driver - VERSION = "0.1.0" + module Sysfs + VERSION = '0.1.0' end end diff --git a/pi_piper-sysfs.gemspec b/pi_piper-sysfs.gemspec index cd0a6d8..b4b3d4c 100644 --- a/pi_piper-sysfs.gemspec +++ b/pi_piper-sysfs.gemspec @@ -12,7 +12,7 @@ Gem::Specification.new do |spec| spec.summary = %q{GPIO kernel driver library for the Raspberry Pi and PiPiper} spec.description = 'GPIO kernel driver library for the Raspberry Pi and other' \ ' boards that use the chipset. Commonly used with the' \ - ' PiPiper ruby library. it implements Pin (with events)' \ + ' PiPiper ruby library. It implements Pin (with events),' \ ' it reads from sysfs, and needs root UID to work' spec.homepage = "https://github.com/PiPiper/sysfs" diff --git a/spec/pi_piper/sysfs/driver_spec.rb b/spec/pi_piper/sysfs/driver_spec.rb new file mode 100644 index 0000000..f939a67 --- /dev/null +++ b/spec/pi_piper/sysfs/driver_spec.rb @@ -0,0 +1,217 @@ +describe PiPiper::Sysfs::Driver do + let(:pins) { '@exported_pins' } + let(:file) { double('File', read: true) } + + before(:each) do + allow(File).to receive(:write).with('/sys/class/gpio/export', 4) + allow(File).to receive(:write).with('/sys/class/gpio/gpio4/direction', :in) + allow(File).to receive(:write).with('/sys/class/gpio/gpio4/direction', :out) + end + + describe '#initialize' do + it 'should load the driver' do + expect { PiPiper::Sysfs::Driver.new }.not_to raise_error + end + + it 'should not export any pins' do + expect(subject.instance_variable_get(pins)).to be_empty + end + end + + describe '#pin_direction' do + it 'should export the pin' do + allow(File).to receive(:write) + allow(subject).to receive(:exported?).with(4).and_return(true) + expect(subject).to receive(:export).with(4) + subject.pin_direction(4, :in) + end + + it 'should set the pin to :in when given :in' do + expect(File).to( + receive(:write).with('/sys/class/gpio/gpio4/direction', :in)) + + subject.pin_direction(4, :in) + end + + it 'should set the pin to :out when given :out' do + expect(File).to( + receive(:write).with('/sys/class/gpio/gpio4/direction', :out)) + + subject.pin_direction(4, :out) + end + + it 'should allow multiple pins to be exported' do + allow(File).to receive(:write).with('/sys/class/gpio/export', 5) + allow(File).to( + receive(:write).with('/sys/class/gpio/gpio5/direction', :in)) + + subject.pin_direction(4, :in) + subject.pin_direction(5, :in) + + expect(subject.instance_variable_get(pins)).to include(4, 5) + end + + it 'should raise an error if pin is already exported' do + subject.pin_direction(4, :in) + + expect { subject.pin_direction(4, :in) }.to raise_error(RuntimeError) + end + + it 'should raise an error on invalid directions' do + expect { subject.pin_direction(4, :bad) }.to raise_error(ArgumentError) + end + + it 'should raise an error if export fails' do + allow(subject).to receive(:exported?).with(4).and_return(false) + expect { subject.pin_direction(4, :in) }.to( + raise_error(RuntimeError, 'Pin 4 not exported')) + end + end + + describe '#pin_read' do + it 'should return the value of the pin' do + subject.pin_direction(4, :in) + expect(File).to receive(:read).with('/sys/class/gpio/gpio4/value') + subject.pin_read(4) + end + + it 'should raise an error if pin is not exported' do + expect { subject.pin_read(4) }.to( + raise_error(ArgumentError, 'Pin 4 not exported')) + end + end + + describe '#pin_write' do + it 'should write the value to the pin' do + subject.pin_direction(4, :out) + expect(File).to receive(:write).with('/sys/class/gpio/gpio4/value', 1) + subject.pin_write(4, 1) + end + + it 'should raise an error if invalid value' do + expect { subject.pin_write(4, 25) }.to raise_error(ArgumentError) + end + + it 'should raise an error if pin is not exported' do + expect { subject.pin_write(4, 1) }.to( + raise_error(ArgumentError, 'Pin 4 not exported')) + end + end + + describe '#pin_set_pud' do + it 'should raise not implemented error because it is not implemented' do + expect { subject.pin_set_pud(4, :up) }.to raise_error(NotImplementedError) + end + end + + describe '#pin_set_trigger' do + before(:each) do + subject.pin_direction(4, :in) + end + + it 'should set the trigger for the pin to :both' do + expect(File).to receive(:write).with('/sys/class/gpio/gpio4/edge', :both) + subject.pin_set_trigger(4, :both) + end + + it 'should set the trigger for the pin to :none' do + expect(File).to receive(:write).with('/sys/class/gpio/gpio4/edge', :none) + subject.pin_set_trigger(4, :none) + end + + it 'should set the trigger for the pin to :falling' do + expect(File).to( + receive(:write).with('/sys/class/gpio/gpio4/edge', :falling)) + subject.pin_set_trigger(4, :falling) + end + + it 'should set the trigger for the pin to :rising' do + expect(File).to( + receive(:write).with('/sys/class/gpio/gpio4/edge', :rising)) + subject.pin_set_trigger(4, :rising) + end + + it 'should raise an error for invalid triggers' do + expect { subject.pin_set_trigger(4, :invalid) }.to( + raise_error(ArgumentError)) + end + + it 'should raise an error if pin is not exported' do + expect { subject.pin_set_trigger(5, :rising) }.to( + raise_error(ArgumentError, 'Pin 5 not exported')) + end + end + + describe '#pin_wait_for' do + it 'should wait for the value of the pin to change' do + allow(File).to( + receive(:open).with('/sys/class/gpio/gpio4/value', 'r') + .and_return(file)) + allow(IO).to receive(:select) + expect(subject.pin_wait_for(4)).to be(true) + end + end + + describe '#close' do + it 'should unexport all exported pins' do + subject.pin_direction(4, :in) + allow(File).to receive(:write).with('/sys/class/gpio/unexport', 4) + + subject.close + expect(subject.instance_variable_get(pins)).to be_empty + end + end + + describe '#unexport' do + before(:each) do + subject.pin_direction(4, :in) + end + + it 'should unexport the pin' do + allow(File).to receive(:write).with('/sys/class/gpio/unexport', 4) + subject.unexport(4) + expect(subject.instance_variable_get(pins)).to be_empty + end + + it 'should not export other pins' do + allow(File).to receive(:write).with('/sys/class/gpio/unexport', 4) + allow(File).to receive(:write).with('/sys/class/gpio/export', 5) + allow(File).to( + receive(:write).with('/sys/class/gpio/gpio5/direction', :in)) + subject.pin_direction(5, :in) + + subject.unexport(4) + + exported_pins = subject.instance_variable_get(pins) + expect(exported_pins).to include(5) + expect(exported_pins).not_to include(4) + end + end + + describe '#unexport_all' do + it 'should unexport all pins' do + allow(File).to receive(:write).with('/sys/class/gpio/export', 5) + allow(File).to( + receive(:write).with('/sys/class/gpio/gpio5/direction', :in)) + allow(File).to receive(:write).with('/sys/class/gpio/unexport', 4) + allow(File).to receive(:write).with('/sys/class/gpio/unexport', 5) + + subject.pin_direction(4, :in) + subject.pin_direction(5, :in) + + subject.unexport_all + expect(subject.instance_variable_get(pins)).to be_empty + end + end + + describe '#exported?' do + it 'should return true if pin is exported' do + subject.pin_direction(4, :in) + expect(subject.exported?(4)).to be(true) + end + + it 'should return false if pin is not exported' do + expect(subject.exported?(4)).to be(false) + end + end +end diff --git a/spec/pi_piper/sysfs_spec.rb b/spec/pi_piper/sysfs_spec.rb index 356c46f..198fe88 100644 --- a/spec/pi_piper/sysfs_spec.rb +++ b/spec/pi_piper/sysfs_spec.rb @@ -1,121 +1,5 @@ -require 'spec_helper' - describe PiPiper::Sysfs do - it 'has a version number' do - expect(PiPiper::Sysfs::VERSION).not_to be nil + it 'should have a version' do + expect(PiPiper::Sysfs::VERSION).not_to be_nil end - - context 'init & close' do - it 'should load the driver' do - expect{ PiPiper::Sysfs.new }.not_to raise_error - end - - it 'should unexport every pin at close' do - expect(subject).to receive(:unexport_all) - subject.close - end - end - - let(:file_like_object) { double("file like object") } - - before :example do - allow(File).to receive(:read).and_return("1") - allow(File).to receive(:write).and_return("1") - allow(File).to receive(:open).and_return(file_like_object) - end - - describe 'Specific behaviours' do - it '#export(pin)' do - expect(File).to receive(:write).with("/sys/class/gpio/export", 4) - expect(subject.instance_variable_get('@exported_pins')).not_to include(4) - subject.export(4) - expect(subject.instance_variable_get('@exported_pins')).to include(4) - end - - it '#unexport(pin)' do - expect(File).to receive(:write).with("/sys/class/gpio/unexport", 4) - - subject.export(4) - expect(subject.instance_variable_get('@exported_pins')).to include(4) - subject.unexport(4) - expect(subject.instance_variable_get('@exported_pins')).not_to include(4) - end - - it '#unexport_all' do - subject.export(4) - subject.export(18) - subject.export(27) - expect(subject.instance_variable_get('@exported_pins')).to eq Set.new([4,18,27]) - subject.unexport_all - expect(subject.instance_variable_get('@exported_pins')).to eq Set.new([]) - end - - it '#exported?(pin)' do - subject.export(4) - expect(subject.exported?(4)).to be true - expect(subject.exported?(112)).to be false - end - - context "when a pin is not exported" do - it 'should stop RW access to pin after unexport' do - subject.unexport(4) - expect { subject.pin_read(4) }.to raise_error ArgumentError, "Pin 4 not exported" - expect { subject.pin_write(4, 1) }.to raise_error ArgumentError, "Pin 4 not exported" - end - end - - context "when a pin is already exported" do - it 'should raise_error when a pin is already in use' do - subject.export(4) - expect { subject.export(4) }.to raise_error RuntimeError - end - end - end - - context 'API for Pin' do - it '#pin_direction(pin, direction)' do - expect(File).to receive(:write).with("/sys/class/gpio/gpio5/direction", :in) - subject.pin_direction(5, :in) - expect(File).to receive(:write).with("/sys/class/gpio/gpio6/direction", :out) - subject.pin_direction(6, :out) - - expect{ subject.pin_direction(7, :inout) }.to raise_error ArgumentError, "direction should be :in or :out" - end - - it '#pin_write(pin, value)' do - subject.export(5) - expect(File).to receive(:write).with("/sys/class/gpio/gpio5/value", 1) - subject.pin_write(5, 1) - expect(File).to receive(:write).with("/sys/class/gpio/gpio5/value", 0) - subject.pin_write(5, 0) - expect { subject.pin_write(5, 99) }.to raise_error ArgumentError, "value should be GPIO_HIGH or GPIO_LOW" - end - - it '#pin_read(pin)' do - subject.export(5) - expect(File).to receive(:read).with("/sys/class/gpio/gpio5/value") - subject.pin_read(5) - end - - it '#pin_set_pud(pin, value)' do - expect {subject.pin_set_pud(5, :up)}.to raise_error NotImplementedError - end - - it '#pin_set_trigger(pin, trigger)' do - subject.export(5) - - expect(File).to receive(:write).with("/sys/class/gpio/gpio5/edge", :none) - subject.pin_set_trigger(5, :none) - expect(File).to receive(:write).with("/sys/class/gpio/gpio5/edge", :both) - subject.pin_set_trigger(5, :both) - expect(File).to receive(:write).with("/sys/class/gpio/gpio5/edge", :falling) - subject.pin_set_trigger(5, :falling) - expect(File).to receive(:write).with("/sys/class/gpio/gpio5/edge", :rising) - subject.pin_set_trigger(5, :rising) - - expect { subject.pin_set_trigger(5, :not_a_trigger) }.to raise_error ArgumentError, "trigger should be :falling, :rising, :both or :none" - end - - it '#pin_wait_for(pin)' - end -end \ No newline at end of file +end From 968b8cba8a22207b84ffed77cb6f321cd0ae9207 Mon Sep 17 00:00:00 2001 From: Zshawn Syed Date: Sun, 3 Apr 2016 17:50:23 -0500 Subject: [PATCH 2/4] use filewatcher instead of IO.select to prevent 100% cpu usage while waiting. Method call to pin_wait_for now blocks instead. Test coverage up to 100% --- Gemfile | 2 +- lib/pi_piper/sysfs/driver.rb | 45 ++++++++++++++++++++++++------ pi_piper-sysfs.gemspec | 1 + spec/pi_piper/sysfs/driver_spec.rb | 27 ++++++++++++------ 4 files changed, 58 insertions(+), 17 deletions(-) diff --git a/Gemfile b/Gemfile index ec31b88..0c67494 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' # Specify your gem's dependencies in pi_piper-sysfs.gemspec -gemspec \ No newline at end of file +gemspec diff --git a/lib/pi_piper/sysfs/driver.rb b/lib/pi_piper/sysfs/driver.rb index 747b3d4..88e6302 100644 --- a/lib/pi_piper/sysfs/driver.rb +++ b/lib/pi_piper/sysfs/driver.rb @@ -1,3 +1,5 @@ +require 'filewatcher' + module PiPiper module Sysfs class Driver < PiPiper::Driver @@ -19,18 +21,18 @@ def pin_direction(pin, direction) raise ArgumentError, "direction should be :in or :out" unless [:in, :out].include? direction export(pin) raise RuntimeError, "Pin #{pin} not exported" unless exported?(pin) - File.write("/sys/class/gpio/gpio#{pin}/direction", direction) + File.write(direction_file(pin), direction) end def pin_read(pin) raise ArgumentError, "Pin #{pin} not exported" unless exported?(pin) - File.read("/sys/class/gpio/gpio#{pin}/value").to_i + File.read(value_file(pin)).to_i end def pin_write(pin, value) raise ArgumentError, "value should be GPIO_HIGH or GPIO_LOW" unless [GPIO_LOW, GPIO_HIGH].include? value raise ArgumentError, "Pin #{pin} not exported" unless exported?(pin) - File.write("/sys/class/gpio/gpio#{pin}/value", value) + File.write(value_file(pin), value) end def pin_set_pud(pin, value) @@ -40,13 +42,19 @@ def pin_set_pud(pin, value) def pin_set_trigger(pin, trigger) raise ArgumentError, "trigger should be :falling, :rising, :both or :none" unless [:falling, :rising, :both, :none].include? trigger raise ArgumentError, "Pin #{pin} not exported" unless exported?(pin) - File.write("/sys/class/gpio/gpio#{pin}/edge", trigger) + File.write(edge_file(pin), trigger) end - def pin_wait_for(pin) - fd = File.open("/sys/class/gpio/gpio#{pin}/value", "r") - fd.read - IO.select(nil, nil, [fd], nil) + def pin_wait_for(pin, trigger) + pin_set_trigger(pin, trigger) + value = pin_read(pin) + + FileWatcher.new([value_file(pin)]).watch do |filename, event| + next unless event == :changed || event == :new + next unless pin_value_changed?(pin, trigger, value) + break + end + true end @@ -72,6 +80,27 @@ def export(pin) File.write("/sys/class/gpio/export", pin) @exported_pins << pin end + + def value_file(pin) + "/sys/class/gpio/gpio#{pin}/value" + end + + def edge_file(pin) + "/sys/class/gpio/gpio#{pin}/edge" + end + + def direction_file(pin) + "/sys/class/gpio/gpio#{pin}/direction" + end + + def pin_value_changed?(pin, trigger, value) + last_value = value + value = pin_read(pin) + return false if value == last_value + return false if trigger == :rising && value == 0 + return false if trigger == :falling && value == 1 + true + end end end end diff --git a/pi_piper-sysfs.gemspec b/pi_piper-sysfs.gemspec index b4b3d4c..b5b78d6 100644 --- a/pi_piper-sysfs.gemspec +++ b/pi_piper-sysfs.gemspec @@ -24,6 +24,7 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib/pi_piper"] spec.add_runtime_dependency 'pi_piper', ">= 2.0.0" + spec.add_runtime_dependency 'filewatcher', '~> 0.5.3' spec.add_development_dependency "bundler", "~> 1.11" spec.add_development_dependency "rake", "~> 10.0" diff --git a/spec/pi_piper/sysfs/driver_spec.rb b/spec/pi_piper/sysfs/driver_spec.rb index f939a67..ebe71c1 100644 --- a/spec/pi_piper/sysfs/driver_spec.rb +++ b/spec/pi_piper/sysfs/driver_spec.rb @@ -1,11 +1,10 @@ describe PiPiper::Sysfs::Driver do let(:pins) { '@exported_pins' } - let(:file) { double('File', read: true) } + let(:filewatcher) { double('FileWatcher') } before(:each) do allow(File).to receive(:write).with('/sys/class/gpio/export', 4) allow(File).to receive(:write).with('/sys/class/gpio/gpio4/direction', :in) - allow(File).to receive(:write).with('/sys/class/gpio/gpio4/direction', :out) end describe '#initialize' do @@ -83,6 +82,8 @@ describe '#pin_write' do it 'should write the value to the pin' do + allow(File).to( + receive(:write).with('/sys/class/gpio/gpio4/direction', :out)) subject.pin_direction(4, :out) expect(File).to receive(:write).with('/sys/class/gpio/gpio4/value', 1) subject.pin_write(4, 1) @@ -143,12 +144,22 @@ end describe '#pin_wait_for' do - it 'should wait for the value of the pin to change' do - allow(File).to( - receive(:open).with('/sys/class/gpio/gpio4/value', 'r') - .and_return(file)) - allow(IO).to receive(:select) - expect(subject.pin_wait_for(4)).to be(true) + before(:each) do + subject.pin_direction(4, :in) + allow(filewatcher).to receive(:watch).and_yield('', :new) + allow(FileWatcher).to receive(:new).and_return(filewatcher) + end + + it 'should wait for pin to trigger :rising' do + expect(subject).to receive(:pin_set_trigger).with(4, :rising) + allow(subject).to receive(:pin_read).with(4).and_return(0, 1) + expect(subject.pin_wait_for(4, :rising)).to be(true) + end + + it 'should wait for pin to trigger :falling' do + expect(subject).to receive(:pin_set_trigger).with(4, :falling) + allow(subject).to receive(:pin_read).with(4).and_return(1, 0) + expect(subject.pin_wait_for(4, :falling)).to be(true) end end From 80ffa73ab8ccbe1113c159484769713c0526e072 Mon Sep 17 00:00:00 2001 From: Zshawn Syed Date: Sun, 3 Apr 2016 17:52:36 -0500 Subject: [PATCH 3/4] add call to autoload the driver back in --- lib/pi_piper/sysfs.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pi_piper/sysfs.rb b/lib/pi_piper/sysfs.rb index 9c4aac4..29aaaff 100644 --- a/lib/pi_piper/sysfs.rb +++ b/lib/pi_piper/sysfs.rb @@ -3,5 +3,5 @@ require 'pi_piper/sysfs/driver' module PiPiper - #self.driver = PiPiper::Sysfs::Driver.new + self.driver = PiPiper::Sysfs::Driver.new end From c7b7a01fb959ed7d6b0563685491a6e72287b19f Mon Sep 17 00:00:00 2001 From: Zshawn Syed Date: Tue, 3 May 2016 10:50:51 -0500 Subject: [PATCH 4/4] Removed current changes to #pin_wait_for, will move into another branch. Removed commented out driver line and added a placeholder in the spec_helper until 3.0 PiPiper is released --- lib/pi_piper/sysfs.rb | 2 +- lib/pi_piper/sysfs/driver.rb | 22 +++++++++++++--------- pi_piper-sysfs.gemspec | 1 - spec/pi_piper/sysfs/driver_spec.rb | 21 ++------------------- spec/spec_helper.rb | 6 ++++++ 5 files changed, 22 insertions(+), 30 deletions(-) diff --git a/lib/pi_piper/sysfs.rb b/lib/pi_piper/sysfs.rb index 29aaaff..266ca43 100644 --- a/lib/pi_piper/sysfs.rb +++ b/lib/pi_piper/sysfs.rb @@ -3,5 +3,5 @@ require 'pi_piper/sysfs/driver' module PiPiper - self.driver = PiPiper::Sysfs::Driver.new + self.driver = PiPiper::Sysfs::Driver end diff --git a/lib/pi_piper/sysfs/driver.rb b/lib/pi_piper/sysfs/driver.rb index 88e6302..234b5dc 100644 --- a/lib/pi_piper/sysfs/driver.rb +++ b/lib/pi_piper/sysfs/driver.rb @@ -1,5 +1,3 @@ -require 'filewatcher' - module PiPiper module Sysfs class Driver < PiPiper::Driver @@ -47,15 +45,21 @@ def pin_set_trigger(pin, trigger) def pin_wait_for(pin, trigger) pin_set_trigger(pin, trigger) - value = pin_read(pin) - - FileWatcher.new([value_file(pin)]).watch do |filename, event| - next unless event == :changed || event == :new - next unless pin_value_changed?(pin, trigger, value) - break + fd = File.open(value_file(pin), 'r') + value = nil + + loop do + fd.read + IO.select(nil, nil, [fd], nil) + last_value = value + value = self.pin_read(pin) + if last_value != value + next if trigger == :rising and value == 0 + next if trigger == :falling and value == 1 + break + end end - true end # Specific behaviours diff --git a/pi_piper-sysfs.gemspec b/pi_piper-sysfs.gemspec index b5b78d6..b4b3d4c 100644 --- a/pi_piper-sysfs.gemspec +++ b/pi_piper-sysfs.gemspec @@ -24,7 +24,6 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib/pi_piper"] spec.add_runtime_dependency 'pi_piper', ">= 2.0.0" - spec.add_runtime_dependency 'filewatcher', '~> 0.5.3' spec.add_development_dependency "bundler", "~> 1.11" spec.add_development_dependency "rake", "~> 10.0" diff --git a/spec/pi_piper/sysfs/driver_spec.rb b/spec/pi_piper/sysfs/driver_spec.rb index ebe71c1..47264cd 100644 --- a/spec/pi_piper/sysfs/driver_spec.rb +++ b/spec/pi_piper/sysfs/driver_spec.rb @@ -1,6 +1,5 @@ describe PiPiper::Sysfs::Driver do let(:pins) { '@exported_pins' } - let(:filewatcher) { double('FileWatcher') } before(:each) do allow(File).to receive(:write).with('/sys/class/gpio/export', 4) @@ -143,24 +142,8 @@ end end - describe '#pin_wait_for' do - before(:each) do - subject.pin_direction(4, :in) - allow(filewatcher).to receive(:watch).and_yield('', :new) - allow(FileWatcher).to receive(:new).and_return(filewatcher) - end - - it 'should wait for pin to trigger :rising' do - expect(subject).to receive(:pin_set_trigger).with(4, :rising) - allow(subject).to receive(:pin_read).with(4).and_return(0, 1) - expect(subject.pin_wait_for(4, :rising)).to be(true) - end - - it 'should wait for pin to trigger :falling' do - expect(subject).to receive(:pin_set_trigger).with(4, :falling) - allow(subject).to receive(:pin_read).with(4).and_return(1, 0) - expect(subject.pin_wait_for(4, :falling)).to be(true) - end + xdescribe '#pin_wait_for' do + end describe '#close' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 19f0f6c..2488316 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,9 @@ +module PiPiper + def self.driver=(driver) + # Placeholder for testing, PiPiper 3.0 contains this so we can remove + end +end + require 'pi_piper' require 'simplecov' SimpleCov.start