rpi_gpio 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c751d8fda46ee296e1df93542416ba668f7f6d9d05142f7647ce63310be42873
4
+ data.tar.gz: 471e4e0dc50ed6b265c9f9dd3ff01a4cb332ac288c8210b161c4bd69ee9cd4d1
5
+ SHA512:
6
+ metadata.gz: 00ef0c4360e5f9c41aec4cf7274249c7dae11fcadc06ad54e27b474a2ea95397fb6727a1a8360e8261fe88f3aa765d904739314da8bc5b1730cc8319c5db2042
7
+ data.tar.gz: 76a6df3e90bfc0ab0ee47030c1c18cb36098b981b72ac5ff855ca5e5abc424a9c5e7452a8e8f1c47507dc846db6ee694de6a49d06fe7bd10bae4529b7c98a9cf
data/LICENSE CHANGED
@@ -1,7 +1,7 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2014-2015 Nick Lowery
4
- Copyright (c) 2013-2014 Ben Croston
3
+ Copyright (c) 2014-2020 Nick Lowery
4
+ Copyright (c) 2012-2014 Ben Croston
5
5
 
6
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
7
7
  of this software and associated documentation files (the "Software"), to deal
@@ -20,4 +20,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
20
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
21
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
22
  SOFTWARE.
23
-
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # rpi_gpio v0.3.0
1
+ # rpi_gpio v0.5.0
2
2
 
3
3
  Ruby conversion of [RPi.GPIO Python module](https://pypi.python.org/pypi/RPi.GPIO)
4
4
 
@@ -8,8 +8,9 @@ Manipulate your Raspberry Pi's GPIO pins from Ruby!
8
8
 
9
9
  - Boolean input/output
10
10
  - Software-driven PWM (written in C for speed)
11
+ - Event-driven input (blocking and non-blocking)
11
12
 
12
- Up-to-date with RPi.GPIO Python module version 0.6.2, so it works on all Raspberry Pi models!
13
+ Up-to-date with RPi.GPIO Python module version 0.7.0, so it works on all Raspberry Pi models!
13
14
 
14
15
  ## Sample Usage
15
16
 
@@ -38,9 +39,20 @@ RPi::GPIO.set_numbering :bcm
38
39
  To receive input from a GPIO pin, you must first initialize it as an input pin:
39
40
  ```ruby
40
41
  RPi::GPIO.setup PIN_NUM, :as => :input
42
+ # or
43
+ RPi::GPIO.setup [PIN1_NUM, PIN2_NUM, ...], :as => :input
41
44
  ```
42
45
  The pin number will differ based on your selected numbering system and which pin you want to use.
43
46
 
47
+ You can use the additional hash argument `:pull` to apply a pull-up or pull-down resistor to the input pin like so:
48
+ ```ruby
49
+ RPi::GPIO.setup PIN_NUM, :as => :input, :pull => :down
50
+ # or
51
+ RPi::GPIO.setup PIN_NUM, :as => :input, :pull => :up
52
+ # or (not necessary; :off is the default value)
53
+ RPi::GPIO.setup PIN_NUM, :as => :input, :pull => :off
54
+ ```
55
+
44
56
  Now you can use the calls
45
57
  ```ruby
46
58
  RPi::GPIO.high? PIN_NUM
@@ -48,13 +60,40 @@ RPi::GPIO.low? PIN_NUM
48
60
  ```
49
61
  to receive either `true` or `false`.
50
62
 
51
- You can use the additional hash argument `:pull` to apply a pull-up or pull-down resistor to the input pin like so:
63
+ If you prefer to use a callback when a pin edge is detected, you can use the `watch` method:
52
64
  ```ruby
53
- RPi::GPIO.setup PIN_NUM, :as => :input, :pull => :down
54
- # or
55
- RPi::GPIO.setup PIN_NUM, :as => :input, :pull => :up
56
- # or (not necessary; :off is the default value)
57
- RPi::GPIO.setup PIN_NUM, :as => :input, :pull => :off
65
+ RPi::GPIO.watch PIN_NUM, :on => :rising do |pin, value| # :on supports :rising, :falling, and :both
66
+ ...
67
+ end
68
+ ```
69
+
70
+ `watch` also supports the optional `bounce_time` parameter found in the Python module to prevent duplicate events from firing:
71
+ ```ruby
72
+ RPi::GPIO.watch PIN_NUM, :on => :falling, :bounce_time => 200 do |pin, value|
73
+ ...
74
+ end
75
+ ```
76
+
77
+ To stop watching a pin, use `stop_watching`:
78
+ ```ruby
79
+ RPi::GPIO.stop_watching PIN_NUM
80
+ ```
81
+
82
+ If you want to block execution until a pin edge is detected, there's `wait_for_edge`:
83
+ ```ruby
84
+ puts 'Waiting to start...'
85
+ RPi::GPIO.wait_for_edge PIN_NUM, :rising # :rising, :falling, and :both are also supported here
86
+ puts 'Here we go!'
87
+ ```
88
+
89
+ `wait_for_edge` accepts optional `bounce_time` and `timeout` arguments too:
90
+ ```ruby
91
+ puts 'Waiting to start...'
92
+ value = RPi::GPIO.wait_for_edge PIN_NUM, :falling, :bounce_time => 200, :timeout => 5000
93
+ if value.nil? # nil is returned if the timeout is reached
94
+ print 'You took too long. '
95
+ end
96
+ puts 'Here we go!'
58
97
  ```
59
98
 
60
99
  #### Output
@@ -62,6 +101,8 @@ RPi::GPIO.setup PIN_NUM, :as => :input, :pull => :off
62
101
  To send output to a GPIO pin, you must first initialize it as an output pin:
63
102
  ```ruby
64
103
  RPi::GPIO.setup PIN_NUM, :as => :output
104
+ # or
105
+ RPi::GPIO.setup [PIN1_NUM, PIN2_NUM, ...], :as => :output
65
106
  ```
66
107
  Now you can use the calls
67
108
  ```ruby
@@ -70,6 +111,13 @@ RPi::GPIO.set_low PIN_NUM
70
111
  ```
71
112
  to set the pin either high or low.
72
113
 
114
+ You can use the additional hash argument `:initialize` to set the pin's initial state like so:
115
+ ```ruby
116
+ RPi::GPIO.setup PIN_NUM, :as => :output, :initialize => :high
117
+ # or
118
+ RPi::GPIO.setup PIN_NUM, :as => :output, :initialize => :low
119
+ ```
120
+
73
121
  #### PWM (pulse-width modulation)
74
122
 
75
123
  Pulse-width modulation is a useful tool for controlling things like LED brightness or motor speed. To utilize PWM, first create a PWM object for an [output pin](#output).
@@ -132,7 +180,7 @@ to clean up all pins and to also reset the selected numbering mode.
132
180
 
133
181
  Original Python code by Ben Croston modified for Ruby by Nick Lowery
134
182
 
135
- Copyright (c) 2014-2015 [Nick Lowery](https://github.com/ClockVapor)
183
+ Copyright (c) 2014-2020 [Nick Lowery](https://github.com/ClockVapor)
136
184
 
137
185
  View LICENSE for full license.
138
186
 
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
1
  require 'rake/extensiontask'
2
2
 
3
- Rake::ExtensionTask.new('rpi_gpio')
3
+ Rake::ExtensionTask.new('rpi_gpio') do |ext|
4
+ ext.lib_dir = 'lib/rpi_gpio'
5
+ end
@@ -1,5 +1,3 @@
1
1
  require 'mkmf'
2
2
 
3
- extension_name = 'rpi_gpio'
4
- dir_config(extension_name)
5
- create_makefile(extension_name)
3
+ create_makefile 'rpi_gpio/rpi_gpio'
@@ -0,0 +1,329 @@
1
+ require 'rpi_gpio/rpi_gpio'
2
+ require 'epoll'
3
+
4
+ module RPi
5
+ module GPIO
6
+ def self.watch(channel, on:, bounce_time: nil, &block)
7
+ gpio = get_gpio_number(channel)
8
+ ensure_gpio_input(gpio)
9
+ validate_edge(on)
10
+ if bounce_time && bounce_time <= 0
11
+ raise ArgumentError, "`bounce_time` must be greater than 0; given #{bounce_time}"
12
+ end
13
+ add_edge_detect(gpio, on, bounce_time)
14
+ add_callback(gpio, &block)
15
+ end
16
+
17
+ def self.stop_watching(channel)
18
+ gpio = get_gpio_number(channel)
19
+ ensure_gpio_input(gpio)
20
+ remove_edge_detect(gpio)
21
+ remove_callbacks(gpio)
22
+ end
23
+
24
+ def self.wait_for_edge(channel, edge, bounce_time: nil, timeout: -1)
25
+ gpio = get_gpio_number(channel)
26
+ if callback_exists(gpio)
27
+ raise RuntimeError, "conflicting edge detection already enabled for GPIO #{gpio}"
28
+ end
29
+
30
+ ensure_gpio_input(gpio)
31
+ validate_edge(edge)
32
+ was_gpio_new = false
33
+ current_edge = get_event_edge(gpio)
34
+ if current_edge == edge
35
+ g = get_gpio(gpio)
36
+ if g.bounce_time && g.bounce_time != bounce_time
37
+ raise RuntimeError, "conflicting edge detection already enabled for GPIO #{gpio}"
38
+ end
39
+ elsif current_edge.nil?
40
+ was_gpio_new = true
41
+ g = new_gpio(gpio)
42
+ set_edge(gpio, edge)
43
+ g.edge = edge
44
+ g.bounce_time = bounce_time
45
+ else
46
+ g = get_gpio(gpio)
47
+ set_edge(gpio, edge)
48
+ g.edge = edge
49
+ g.bounce_time = bounce_time
50
+ g.initial_wait = 1
51
+ end
52
+
53
+ if @@epoll_blocking.nil?
54
+ @@epoll_blocking = Epoll.create
55
+ end
56
+ @@epoll_blocking.add(g.value_file, Epoll::PRI)
57
+
58
+ initial_edge = true
59
+ the_value = nil
60
+ timed_out = false
61
+ begin
62
+ while the_value.nil? && !timed_out do
63
+ events = @@epoll_blocking.wait(timeout)
64
+ if events.empty?
65
+ timed_out = true
66
+ end
67
+ events.each do |event|
68
+ if event.events & Epoll::PRI != 0
69
+ event.data.seek(0, IO::SEEK_SET)
70
+ value = event.data.read.chomp.to_i
71
+ if event.data == g.value_file
72
+ if initial_edge # ignore first epoll trigger
73
+ initial_edge = false
74
+ else
75
+ now = Time.now.to_f
76
+ if g.bounce_time.nil? || g.last_call == 0 || g.last_call > now ||
77
+ (now - g.last_call) * 1000 > g.bounce_time then
78
+ g.last_call = now
79
+ the_value = value
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ if the_value
88
+ return the_value
89
+ end
90
+ ensure
91
+ @@epoll_blocking.del(g.value_file)
92
+ if was_gpio_new
93
+ delete_gpio(gpio)
94
+ end
95
+ end
96
+ end
97
+
98
+ private
99
+ @@callbacks = []
100
+ @@gpios = []
101
+ @@epoll = nil
102
+ @@epoll_thread = nil
103
+ @@epoll_blocking = nil
104
+
105
+ def self.export(gpio)
106
+ unless File.exist?("/sys/class/gpio/gpio#{gpio}")
107
+ File.open("/sys/class/gpio/export", 'w') do |file|
108
+ file.write(gpio.to_s)
109
+ end
110
+ end
111
+ end
112
+
113
+ def self.unexport(gpio)
114
+ File.open("/sys/class/gpio/unexport", 'w') do |file|
115
+ file.write(gpio.to_s)
116
+ end
117
+ end
118
+
119
+ def self.set_direction(gpio, direction)
120
+ validate_direction(direction)
121
+ tries = 0
122
+ begin
123
+ File.open("/sys/class/gpio/gpio#{gpio}/direction", 'w') do |file|
124
+ file.write(direction.to_s)
125
+ end
126
+ rescue
127
+ raise if tries > 5
128
+ sleep 0.1
129
+ tries += 1
130
+ retry
131
+ end
132
+ end
133
+
134
+ def self.set_edge(gpio, edge)
135
+ validate_edge(edge)
136
+ tries = 0
137
+ begin
138
+ File.open("/sys/class/gpio/gpio#{gpio}/edge", 'w') do |file|
139
+ file.write(edge.to_s)
140
+ end
141
+ rescue
142
+ raise if tries > 5
143
+ sleep 0.1
144
+ tries += 1
145
+ retry
146
+ end
147
+ end
148
+
149
+ def self.open_value_file(gpio)
150
+ File.open("/sys/class/gpio/gpio#{gpio}/value", 'r')
151
+ end
152
+
153
+ def self.new_gpio(gpio)
154
+ g = GPIO.new
155
+ g.gpio = gpio
156
+ export(gpio)
157
+ g.exported = true
158
+ set_direction(gpio, :in)
159
+ begin
160
+ g.value_file = open_value_file(gpio)
161
+ rescue
162
+ unexport(gpio)
163
+ raise
164
+ end
165
+ g.initial_thread = true
166
+ g.initial_wait = true
167
+ g.bounce_time = nil
168
+ g.last_call = 0
169
+ g.thread_added = false
170
+ @@gpios << g
171
+ g
172
+ end
173
+
174
+ def self.delete_gpio(gpio)
175
+ @@gpios.delete_if { |g| g.gpio == gpio }
176
+ end
177
+
178
+ def self.get_gpio(gpio)
179
+ @@gpios.find { |g| g.gpio == gpio }
180
+ end
181
+
182
+ def self.get_gpio_by_value_file(value_file)
183
+ @@gpios.find { |g| g.value_file == value_file }
184
+ end
185
+
186
+ def self.get_event_edge(gpio) # gpio_event_added in python library
187
+ g = @@gpios.find { |g| g.gpio == gpio }
188
+ if g
189
+ return g.edge
190
+ end
191
+ end
192
+
193
+ def self.add_edge_detect(gpio, edge, bounce_time = nil)
194
+ current_edge = get_event_edge(gpio)
195
+ if current_edge.nil?
196
+ g = new_gpio(gpio)
197
+ set_edge(gpio, edge)
198
+ g.edge = edge
199
+ g.bounce_time = bounce_time
200
+ elsif current_edge == edge
201
+ g = get_gpio(gpio)
202
+ if (bounce_time && g.bounce_time != bounce_time) || g.thread_added
203
+ raise RuntimeError, "conflicting edge detection already enabled for GPIO #{gpio}"
204
+ end
205
+ else
206
+ raise RuntimeError, "conflicting edge detection already enabled for GPIO #{gpio}"
207
+ end
208
+
209
+ if @@epoll.nil?
210
+ @@epoll = Epoll.create
211
+ end
212
+
213
+ begin
214
+ @@epoll.add(g.value_file, Epoll::PRI)
215
+ rescue
216
+ remove_edge_detect(gpio)
217
+ raise
218
+ end
219
+
220
+ g.thread_added = true
221
+ if @@epoll_thread.nil? || !@@epoll_thread.alive?
222
+ @@epoll_thread = Thread.new { poll_thread }
223
+ end
224
+ end
225
+
226
+ def self.remove_edge_detect(gpio)
227
+ g = get_gpio(gpio)
228
+ if g and @@epoll
229
+ @@epoll.del(g.value_file)
230
+ set_edge(gpio, :none)
231
+ g.edge = :none
232
+ g.value_file.close
233
+ unexport(gpio)
234
+ delete_gpio(gpio)
235
+ end
236
+ end
237
+
238
+ def self.poll_thread
239
+ loop do
240
+ begin
241
+ events = @@epoll.wait
242
+ rescue IOError # empty interest list
243
+ break
244
+ end
245
+ events.each do |event|
246
+ if event.events & Epoll::PRI != 0
247
+ event.data.seek(0, IO::SEEK_SET)
248
+ g = get_gpio_by_value_file(event.data)
249
+ value = event.data.read.chomp.to_i
250
+ if g.initial_thread # ignore first epoll trigger
251
+ g.initial_thread = false
252
+ else
253
+ now = Time.now.to_f
254
+ if g.bounce_time.nil? || g.last_call == 0 || g.last_call > now ||
255
+ (now - g.last_call) * 1000 > g.bounce_time then
256
+ g.last_call = now
257
+ run_callbacks(g.gpio, value)
258
+ end
259
+ end
260
+ end
261
+ end
262
+ end
263
+ end
264
+
265
+ def self.event_cleanup(gpio)
266
+ @@gpios.map { |g| g.gpio }.each do |gpio_|
267
+ if gpio.nil? || gpio_ == gpio
268
+ remove_edge_detect(gpio_)
269
+ end
270
+ end
271
+
272
+ if @@gpios.empty? && @@epoll_thread
273
+ @@epoll_thread.terminate
274
+ end
275
+ end
276
+
277
+ def self.event_cleanup_all
278
+ event_cleanup(nil)
279
+ end
280
+
281
+ def self.add_callback(gpio, &block)
282
+ @@callbacks << Callback.new(gpio, &block)
283
+ end
284
+
285
+ def self.remove_callbacks(gpio)
286
+ @@callbacks.delete_if { |g| g.gpio == gpio }
287
+ end
288
+
289
+ def self.callback_exists(gpio)
290
+ @@gpios.find { |g| g.gpio == gpio } != nil
291
+ end
292
+
293
+ def self.run_callbacks(gpio, value)
294
+ @@callbacks.each do |callback|
295
+ if callback.gpio == gpio
296
+ callback.block.call(channel_from_gpio(gpio), value)
297
+ end
298
+ end
299
+ end
300
+
301
+ def self.validate_direction(direction)
302
+ direction = direction.to_s
303
+ if direction != 'in' && direction != 'out'
304
+ raise ArgumentError, "`direction` must be 'in' or 'out'; given '#{direction}'"
305
+ end
306
+ end
307
+
308
+ def self.validate_edge(edge)
309
+ edge = edge.to_s
310
+ if edge != 'rising' && edge != 'falling' && edge != 'both' && edge != 'none'
311
+ raise ArgumentError, "`edge` must be 'rising', 'falling', 'both', or 'none'; given '#{edge}'"
312
+ end
313
+ end
314
+
315
+ class GPIO
316
+ attr_accessor :gpio, :exported, :value_file, :initial_thread, :initial_wait, :bounce_time, :last_call,
317
+ :thread_added, :edge
318
+ end
319
+
320
+ class Callback
321
+ attr_accessor :gpio, :block
322
+
323
+ def initialize(gpio, &block)
324
+ @gpio = gpio
325
+ @block = block
326
+ end
327
+ end
328
+ end
329
+ end