rpi_gpio 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cf2e5fe321ddea6602db36bf081dcdd9b16d1248a584c9fe2156cf3b1cc8764b
4
- data.tar.gz: aea0df6d2ce23b6b60ffdd35a1a856c09920fda38dfdcc197f4d38b417129935
3
+ metadata.gz: c751d8fda46ee296e1df93542416ba668f7f6d9d05142f7647ce63310be42873
4
+ data.tar.gz: 471e4e0dc50ed6b265c9f9dd3ff01a4cb332ac288c8210b161c4bd69ee9cd4d1
5
5
  SHA512:
6
- metadata.gz: e2d50e340b0d24349c291b4927fa4bc03e3c477862659a4b6a4f81c5a6c10a94e612e70acef98299627fc299f53cad68f15606b9795b610af1e82d559face6d3
7
- data.tar.gz: f6495cbea95ddfe0348bd9be92d716d4929329414ef49d6d6649a373f87af4a62346d1fe74631771de24a066b3b7bfcb1335efda76bda8dbd934e5d1eab644de
6
+ metadata.gz: 00ef0c4360e5f9c41aec4cf7274249c7dae11fcadc06ad54e27b474a2ea95397fb6727a1a8360e8261fe88f3aa765d904739314da8bc5b1730cc8319c5db2042
7
+ data.tar.gz: 76a6df3e90bfc0ab0ee47030c1c18cb36098b981b72ac5ff855ca5e5abc424a9c5e7452a8e8f1c47507dc846db6ee694de6a49d06fe7bd10bae4529b7c98a9cf
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # rpi_gpio v0.4.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,6 +8,7 @@ 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
13
  Up-to-date with RPi.GPIO Python module version 0.7.0, so it works on all Raspberry Pi models!
13
14
 
@@ -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
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
Binary file
metadata CHANGED
@@ -1,43 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rpi_gpio
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Lowery
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-21 00:00:00.000000000 Z
11
+ date: 2020-08-09 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: epoll
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.3'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: rake-compiler
15
29
  requirement: !ruby/object:Gem::Requirement
16
30
  requirements:
17
- - - ">="
31
+ - - "~>"
18
32
  - !ruby/object:Gem::Version
19
- version: '0'
33
+ version: '1.1'
20
34
  type: :development
21
35
  prerelease: false
22
36
  version_requirements: !ruby/object:Gem::Requirement
23
37
  requirements:
24
- - - ">="
38
+ - - "~>"
25
39
  - !ruby/object:Gem::Version
26
- version: '0'
40
+ version: '1.1'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rspec
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
- - - ">="
45
+ - - "~>"
32
46
  - !ruby/object:Gem::Version
33
- version: '0'
47
+ version: '3.6'
34
48
  type: :development
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
- - - ">="
52
+ - - "~>"
39
53
  - !ruby/object:Gem::Version
40
- version: '0'
54
+ version: '3.6'
41
55
  description: Ruby conversion of RPi.GPIO Python module
42
56
  email: nick.a.lowery@gmail.com
43
57
  executables: []
@@ -46,27 +60,12 @@ extensions:
46
60
  extra_rdoc_files: []
47
61
  files:
48
62
  - Gemfile
49
- - Gemfile.lock
50
63
  - LICENSE
51
64
  - README.md
52
65
  - Rakefile
53
- - ext/rpi_gpio/c_gpio.c
54
- - ext/rpi_gpio/c_gpio.h
55
- - ext/rpi_gpio/common.c
56
- - ext/rpi_gpio/common.h
57
- - ext/rpi_gpio/cpuinfo.c
58
- - ext/rpi_gpio/cpuinfo.h
59
- - ext/rpi_gpio/event_gpio.c
60
- - ext/rpi_gpio/event_gpio.h
61
66
  - ext/rpi_gpio/extconf.rb
62
- - ext/rpi_gpio/rb_gpio.c
63
- - ext/rpi_gpio/rb_gpio.h
64
- - ext/rpi_gpio/rb_pwm.c
65
- - ext/rpi_gpio/rb_pwm.h
66
- - ext/rpi_gpio/rpi_gpio.c
67
- - ext/rpi_gpio/rpi_gpio.h
68
- - ext/rpi_gpio/soft_pwm.c
69
- - ext/rpi_gpio/soft_pwm.h
67
+ - lib/rpi_gpio.rb
68
+ - lib/rpi_gpio/rpi_gpio.so
70
69
  homepage: https://github.com/ClockVapor/rpi_gpio
71
70
  licenses:
72
71
  - MIT