rpi_gpio 0.4.0 → 0.5.0

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.
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