pi_piper 1.3.2 → 1.9.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ require 'pi_piper'
2
+
3
+ puts "Press the switch to get started"
4
+
5
+ PiPiper.watch :pin => 17, :invert => true do |pin|
6
+ puts "Pin changed from #{pin.last_value} to #{pin.value}"
7
+ end
8
+
9
+ PiPiper.wait
10
+
@@ -1,8 +1,5 @@
1
- Dir[File.dirname(__FILE__) + '/pi_piper/*.rb'].each {|file| require file }
2
- at_exit do
3
- PiPiper::Bcm2835.close
4
- end
5
- PiPiper::Bcm2835.init
1
+ require 'eventmachine'
2
+ Dir[File.dirname(__FILE__) + '/pi_piper/*.rb'].each {|file| require file unless file.end_with?('bcm2835.rb') }
6
3
 
7
4
  module PiPiper
8
5
  extend self
@@ -38,8 +35,44 @@ module PiPiper
38
35
  options[:trigger] = options.delete(:goes) == :high ? :rising : :falling
39
36
  watch options, &block
40
37
  end
38
+
39
+ #Defines an event block to be called on a regular schedule. The block will be passed the slave output.
40
+ #
41
+ # @param [Hash] options A hash of options.
42
+ # @option options [Fixnum] :every A frequency of time (in seconds) to poll the SPI slave.
43
+ # @option options [Fixnum] :slave The slave number to poll.
44
+ # @option options [Number|Array] :write Data to poll the SPI slave with.
45
+ def poll_spi(options, &block)
46
+ EM::PeriodicTimer.new(options[:every]) do
47
+ Spi.begin options[:slave] do
48
+ output = write options[:write]
49
+ block.call output
50
+ end
51
+ end
52
+ end
53
+
54
+ #Defines an event block to be called when SPI slave output meets certain characteristics.
55
+ #The block will be passed the slave output.
56
+ #
57
+ # @param [Hash] options A hash of options.
58
+ # @option options [Fixnum] :every A frequency of time (in seconds) to poll the SPI slave.
59
+ # @option options [Fixnum] :slave The slave number to poll.
60
+ # @option options [Number|Array] :write Data to poll the SPI slave with.
61
+ # @option options [Fixnum] :eq Tests for SPI slave output equality.
62
+ # @option options [Fixnum] :lt Tests for SPI slave output less than supplied value.
63
+ # @option options [Fixnum] :gt Tests for SPI slave output greater than supplied value.
64
+ def when_spi(options, &block)
65
+ poll_spi options do |value|
66
+ if (options[:eq] && value == options[:eq]) ||
67
+ (options[:lt] && value < options[:lt]) ||
68
+ (options[:gt] && value > options[:gt])
69
+ block.call value
70
+ end
71
+ end
72
+ end
41
73
 
42
74
  #Prevents the main thread from exiting. Required when using PiPiper.watch
75
+ # @deprecated Please use EventMachine.run instead
43
76
  def wait
44
77
  loop do sleep 1 end
45
78
  end
@@ -2,19 +2,73 @@ require 'ffi'
2
2
 
3
3
  module PiPiper
4
4
  # The Bcm2835 module is not intended to be directly called.
5
- # It serves as an FFI library for PiPiper::SPI.
5
+ # It serves as an FFI library for PiPiper::SPI & PiPiper::I2C.
6
6
  module Bcm2835
7
7
  extend FFI::Library
8
- ffi_lib File.dirname(__FILE__) + '/libbcm2835.img'
8
+ ffi_lib File.dirname(__FILE__) + '/libbcm2835.so'
9
+ @pins = []
9
10
 
10
11
  SPI_MODE0 = 0
11
12
  SPI_MODE1 = 1
12
13
  SPI_MODE2 = 2
13
14
  SPI_MODE3 = 3
14
15
 
16
+ I2C_REASON_OK = 0 #Success
17
+ I2C_REASON_ERROR_NACK = 1 #Received a NACK
18
+ I2C_REASON_ERROR_CLKT = 2 #Received Clock Stretch Timeout
19
+ I2C_REASON_ERROR_DATA = 3 #Not all data is sent / received
20
+
15
21
  attach_function :init, :bcm2835_init, [], :uint8
16
22
  attach_function :close, :bcm2835_close, [], :uint8
17
23
 
24
+ #pin support...
25
+ attach_function :pin_set_pud, :bcm2835_gpio_set_pud, [:uint8, :uint8], :void
26
+
27
+ def self.pin_input(pin)
28
+ export(pin)
29
+ pin_direction(pin, 'in')
30
+ end
31
+
32
+ def self.pin_set(pin, value)
33
+ File.open("/sys/class/gpio/gpio#{pin}/value", 'w') {|f| f.write("#{value}") }
34
+ end
35
+
36
+ def self.pin_output(pin)
37
+ export(pin)
38
+ pin_direction(pin, 'out')
39
+ end
40
+
41
+ def self.pin_read(pin)
42
+ File.read("/sys/class/gpio/gpio#{pin}/value").to_i
43
+ end
44
+
45
+ def self.pin_direction(pin, direction)
46
+ File.open("/sys/class/gpio/gpio#{pin}/direction", 'w') do |f|
47
+ f.write(direction)
48
+ end
49
+ end
50
+
51
+ # Exports pin and subsequently locks it from outside access
52
+ def self.export(pin)
53
+ File.open('/sys/class/gpio/export', 'w') { |f| f.write("#{pin}") }
54
+ @pins << pin unless @pins.include?(pin)
55
+ end
56
+
57
+ def self.release_pin(pin)
58
+ File.open('/sys/class/gpio/unexport', 'w') { |f| f.write("#{pin}") }
59
+ @pins.delete(pin)
60
+ end
61
+
62
+ def self.release_pins
63
+ @pins.dup.each { |pin| release_pin(pin) }
64
+ end
65
+
66
+ #NOTE to use: chmod 666 /dev/spidev0.0
67
+ def self.spidev_out(array)
68
+ File.open('/dev/spidev0.0', 'wb'){|f| f.write(array.pack('C*')) }
69
+ end
70
+
71
+ #SPI support...
18
72
  attach_function :spi_begin, :bcm2835_spi_begin, [], :uint8
19
73
  attach_function :spi_end, :bcm2835_spi_end, [], :uint8
20
74
  attach_function :spi_transfer, :bcm2835_spi_transfer, [:uint8], :uint8
@@ -26,8 +80,23 @@ module PiPiper
26
80
  attach_function :spi_chip_select_polarity,
27
81
  :bcm2835_spi_setChipSelectPolarity, [:uint8, :uint8], :void
28
82
 
83
+ #I2C support...
84
+ attach_function :i2c_begin, :bcm2835_i2c_begin, [], :void
85
+ attach_function :i2c_end, :bcm2835_i2c_end, [], :void
86
+ attach_function :i2c_write, :bcm2835_i2c_write, [:pointer, :uint], :uint8
87
+ attach_function :i2c_set_address,:bcm2835_i2c_setSlaveAddress, [:uint8], :void
88
+ attach_function :i2c_set_clock_divider, :bcm2835_i2c_setClockDivider, [:uint16], :void
89
+ attach_function :i2c_read, :bcm2835_i2c_read, [:pointer, :uint], :uint8
90
+
91
+ def self.i2c_allowed_clocks
92
+ [100.kilohertz,
93
+ 399.3610.kilohertz,
94
+ 1.666.megahertz,
95
+ 1.689.megahertz]
96
+ end
97
+
29
98
  def self.spi_transfer_bytes(data)
30
- data_out = FFI::MemoryPointer.new(data.count)
99
+ data_out = FFI::MemoryPointer.new(data.count)
31
100
  data_in = FFI::MemoryPointer.new(data.count)
32
101
  (0..data.count-1).each { |i| data_out.put_uint8(i, data[i]) }
33
102
 
@@ -36,5 +105,18 @@ module PiPiper
36
105
  (0..data.count-1).map { |i| data_in.get_uint8(i) }
37
106
  end
38
107
 
108
+ def self.i2c_transfer_bytes(data)
109
+ data_out = FFI::MemoryPointer.new(data.count)
110
+ (0..data.count-1).each{ |i| data_out.put_uint8(i, data[i]) }
111
+
112
+ i2c_write data_out, data.count
113
+ end
114
+
115
+ def self.i2c_read_bytes(bytes)
116
+ data_in = FFI::MemoryPointer.new(bytes)
117
+ i2c_read(data_in, bytes) #TODO reason codes
118
+
119
+ (0..bytes-1).map { |i| data_in.get_uint8(i) }
120
+ end
39
121
  end
40
122
  end
@@ -0,0 +1,19 @@
1
+ module Frequency
2
+
3
+ def kilohertz
4
+ self * 1000
5
+ end
6
+
7
+ def megahertz
8
+ self * 1000000
9
+ end
10
+
11
+ end
12
+
13
+ class Float
14
+ include Frequency
15
+ end
16
+
17
+ class Integer
18
+ include Frequency
19
+ end
@@ -0,0 +1,51 @@
1
+ module PiPiper
2
+
3
+ #I2C support
4
+ class I2C
5
+
6
+ #http://www.airspayce.com/mikem/bcm2835/group__i2c.html
7
+
8
+ def initialize
9
+ Platform.driver.i2c_begin
10
+ end
11
+
12
+ def end
13
+ Platform.driver.i2c_end
14
+ end
15
+
16
+ def self.begin(&block)
17
+ instance = I2C.new
18
+ begin
19
+ if block.arity > 0
20
+ block.call instance
21
+ else
22
+ instance.instance_exec &block
23
+ end
24
+ ensure
25
+ instance.end
26
+ end
27
+ end
28
+
29
+ def self.clock=(clock)
30
+ valid_clocks = Platform.driver.i2c_allowed_clocks
31
+ raise "Invalid clock rate. Valid clocks are 100 kHz, 399.3610 kHz, 1.666 MHz and 1.689 MHz" unless valid_clocks.include? clock
32
+
33
+ Platform.driver.i2c_set_clock clock
34
+ end
35
+
36
+ def write(params)
37
+ data = params[:data]
38
+ address = params[:to]
39
+
40
+ raise ":data must be enumerable" unless data.class.include? Enumerable
41
+ Platform.driver.i2c_set_address address
42
+ Platform.driver.i2c_transfer_bytes data
43
+ end
44
+
45
+ def read(bytes)
46
+ Platform.driver.i2c_read_bytes(bytes)
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -1,70 +1,148 @@
1
+ require_relative 'pin_values'
2
+
1
3
  module PiPiper
2
4
  # Represents a GPIO pin on the Raspberry Pi
3
5
  class Pin
4
- attr_reader :pin, :last_value, :value, :direction, :invert
5
-
6
+ include PiPiper::PinValues
7
+
8
+ attr_reader :pin, :last_value, :direction, :invert
9
+
6
10
  #Initializes a new GPIO pin.
7
11
  #
8
12
  # @param [Hash] options A hash of options
9
13
  # @option options [Fixnum] :pin The pin number to initialize. Required.
10
- # @option options [Symbol] :direction The direction of communication, either :in or :out. Defaults to :in.
11
- # @option options [Boolean] :invert Indicates if the value read from the physical pin should be inverted. Defaults to false.
12
- # @option options [Symbol] :trigger Indicates when the wait_for_change method will detect a change, either :rising, :falling, or :both edge triggers. Defaults to :both.
14
+ #
15
+ # @option options [Symbol] :direction The direction of communication,
16
+ # either :in or :out. Defaults to :in.
17
+ #
18
+ # @option options [Boolean] :invert Indicates if the value read from the
19
+ # physical pin should be inverted. Defaults to false.
20
+ #
21
+ # @option options [Symbol] :trigger Indicates when the wait_for_change
22
+ # method will detect a change, either :rising, :falling, or :both edge
23
+ # triggers. Defaults to :both.
24
+ #
25
+ # @option options [Symbol] :pull Indicates if and how pull mode must be
26
+ # set when pin direction is set to :in. Either :up, :down or :offing.
27
+ # Defaults to :off.
28
+ #
13
29
  def initialize(options)
14
- options = {:direction => :in, :invert => false, :trigger => :both}.merge options
15
- @pin = options[:pin]
30
+ options = { :direction => :in,
31
+ :invert => false,
32
+ :trigger => :both,
33
+ :pull => :off}.merge(options)
34
+
35
+ @pin = options[:pin]
16
36
  @direction = options[:direction]
17
- @invert = options[:invert]
18
- @trigger = options[:trigger]
19
-
20
- raise "Invalid direction. Options are :in or :out" unless [:in, :out].include? @direction
21
- raise "Invalid trigger. Options are :rising, :falling, or :both" unless [:rising, :falling, :both].include? @trigger
22
-
23
- File.open("/sys/class/gpio/export", "w") { |f| f.write("#{@pin}") }
24
- File.open(direction_file, "w") { |f| f.write(@direction == :out ? "out" : "in") }
25
-
26
- read
27
- end
28
-
29
- # If the pin has been initialized for output this method will set the logic level high.
37
+ @invert = options[:invert]
38
+ @trigger = options[:trigger]
39
+ @pull = options[:pull]
40
+ @released = false
41
+
42
+ raise "Invalid pull mode. Options are :up, :down or :float (default)" unless
43
+ [:up, :down, :float, :off].include? @pull
44
+ raise "Unable to use pull-ups : pin direction must be ':in' for this" if
45
+ @direction != :in and [:up, :down].include?(@pull)
46
+ raise "Invalid direction. Options are :in or :out" unless
47
+ [:in, :out].include? @direction
48
+ raise "Invalid trigger. Options are :rising, :falling, or :both" unless
49
+ [:rising, :falling, :both].include? @trigger
50
+
51
+ if @direction == :out
52
+ Platform.driver.pin_output(@pin)
53
+ else
54
+ Platform.driver.pin_input(@pin)
55
+ end
56
+ pull!(@pull)
57
+
58
+ read
59
+ end
60
+
61
+ # If the pin has been initialized for output this method will set the
62
+ # logic level high.
30
63
  def on
31
- File.open(value_file, 'w') {|f| f.write("1") } if direction == :out
64
+ fail PiPiper::PinError, "Pin #{@pin} already released" if released?
65
+ Platform.driver.pin_set(pin, GPIO_HIGH) if direction == :out
32
66
  end
33
-
67
+
34
68
  # Tests if the logic level is high.
35
69
  def on?
36
70
  not off?
37
71
  end
38
-
39
- # If the pin has been initialized for output this method will set the logic level low.
72
+
73
+ # If the pin has been initialized for output this method will set
74
+ # the logic level low.
40
75
  def off
41
- File.open(value_file, 'w') {|f| f.write("0") } if direction == :out
76
+ fail PiPiper::PinError, "Pin #{@pin} already released" if released?
77
+ Platform.driver.pin_set(pin, GPIO_LOW) if direction == :out
42
78
  end
43
-
79
+
44
80
  # Tests if the logic level is low.
45
81
  def off?
46
- value == 0
82
+ value == GPIO_LOW
47
83
  end
48
84
 
49
- # If the pin has been initialized for output this method will either raise or lower the logic level depending on `new_value`.
85
+ def value
86
+ @value ||= read
87
+ end
88
+
89
+ # If the pin has been initialized for output this method will either raise
90
+ # or lower the logic level depending on `new_value`.
50
91
  # @param [Object] new_value If false or 0 the pin will be set to off, otherwise on.
51
92
  def update_value(new_value)
52
- !new_value || new_value == 0 ? off : on
93
+ !new_value || new_value == GPIO_LOW ? off : on
94
+ end
95
+ alias_method :value=, :update_value
96
+
97
+ # When the pin has been initialized in input mode, internal resistors can
98
+ # be pulled up or down (respectively with :up and :down).
99
+ #
100
+ # Pulling an input pin will prevent noise from triggering it when the input
101
+ # is floating.
102
+ #
103
+ # For instance when nothing is plugged in, pulling the pin-up will make
104
+ # subsequent value readings to return 'on' (or high, or 1...).
105
+ # @param [Symbol] state Indicates if and how pull mode must be set when
106
+ # pin direction is set to :in. Either :up, :down or :offing. Defaults to :off.
107
+ def pull!(state)
108
+ return nil if @direction != :in
109
+ fail PiPiper::PinError, "Pin #{@pin} already released" if released?
110
+ @pull = case state
111
+ when :up then GPIO_PUD_UP
112
+ when :down then GPIO_PUD_DOWN
113
+ # :float and :off are just aliases
114
+ when :float, :off then GPIO_PUD_OFF
115
+ else nil
116
+ end
117
+
118
+ Platform.driver.pin_set_pud(@pin, @pull) if @pull
119
+ @pull
120
+ end
121
+
122
+ # If the pin direction is input, it will return the current state of
123
+ # pull-up/pull-down resistor, either :up, :down or :off.
124
+ def pull?
125
+ case @pull
126
+ when GPIO_PUD_UP then :up
127
+ when GPIO_PUD_DOWN then :down
128
+ else :off
129
+ end
53
130
  end
54
-
131
+
55
132
  # Tests if the logic level has changed since the pin was last read.
56
133
  def changed?
57
134
  last_value != value
58
135
  end
59
136
 
60
- # blocks until a logic level change occurs. The initializer option `:trigger` modifies what edge this method will release on.
137
+ # Blocks until a logic level change occurs. The initializer option
138
+ # `:trigger` modifies what edge this method will release on.
61
139
  def wait_for_change
62
140
  fd = File.open(value_file, "r")
63
141
  File.open(edge_file, "w") { |f| f.write("both") }
64
142
  loop do
65
143
  fd.read
66
144
  IO.select(nil, nil, [fd], nil)
67
- read
145
+ read
68
146
  if changed?
69
147
  next if @trigger == :rising and value == 0
70
148
  next if @trigger == :falling and value == 1
@@ -73,15 +151,29 @@ module PiPiper
73
151
  end
74
152
  end
75
153
 
76
- # Reads the current value from the pin. Without calling this method first, `value`, `last_value` and `changed?` will not be updated.
77
- # In short, you must call this method if you are curious about the current state of the pin.
78
- def read
154
+ # Reads the current value from the pin. Without calling this method
155
+ # first, `value`, `last_value` and `changed?` will not be updated.
156
+ #
157
+ # In short, you must call this method if you are curious about the
158
+ # current state of the pin.
159
+ def read
160
+ fail PiPiper::PinError, "Pin #{@pin} already released" if released?
79
161
  @last_value = @value
80
- val = File.read(value_file).to_i
162
+ val = Platform.driver.pin_read(@pin)
81
163
  @value = invert ? (val ^ 1) : val
82
164
  end
83
-
165
+
166
+ def release
167
+ Platform.driver.release_pin(@pin)
168
+ @released = true
169
+ end
170
+
171
+ def released?
172
+ @released == true
173
+ end
174
+
84
175
  private
176
+
85
177
  def value_file
86
178
  "/sys/class/gpio/gpio#{pin}/value"
87
179
  end
@@ -93,6 +185,5 @@ module PiPiper
93
185
  def direction_file
94
186
  "/sys/class/gpio/gpio#{pin}/direction"
95
187
  end
96
-
97
188
  end
98
189
  end