rpi-io 1.2.4 → 2.0.1
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.
- package/README.md +428 -252
- package/addon/gpio.c +875 -0
- package/binding.gyp +33 -0
- package/esm/_util.mjs +122 -0
- package/esm/main.mjs +307 -0
- package/history.md +5 -4
- package/package.json +54 -7
- package/script/check-env.sh +192 -0
- package/script/detect-gpiod-version.sh +21 -0
- package/test/benchmark-pwm.js +44 -0
- package/test/benchmark-read.js +37 -0
- package/test/benchmark-write.js +38 -0
- package/test/close-all.js +27 -0
- package/test/duplicate-error.js +29 -0
- package/test/line-configuration.js +16 -0
- package/test/pwm-led.js +36 -0
- package/test/pwm-motor.js +40 -0
- package/test/read.js +30 -0
- package/test/write.js +29 -0
- package/esm/dev.mjs +0 -59
- package/esm/rio.mjs +0 -675
- package/esm/utl.mjs +0 -100
- package/scr/test.js +0 -171
package/esm/rio.mjs
DELETED
|
@@ -1,675 +0,0 @@
|
|
|
1
|
-
// -------------------------------------------------------------------
|
|
2
|
-
// JS GPIO API: IN, OUT, PWM
|
|
3
|
-
// Source: https://libgpiod.readthedocs.io/en/stable/gpio_tools.html
|
|
4
|
-
// Details: man gpiodetect
|
|
5
|
-
// -------------------------------------------------------------------
|
|
6
|
-
import {spawn} from "child_process";
|
|
7
|
-
import {writeFileSync, readFileSync} from "fs"
|
|
8
|
-
import {exeFile, exeShell, wait} from "./utl.mjs"
|
|
9
|
-
import {logCfg, log, trap} from "./dev.mjs"
|
|
10
|
-
|
|
11
|
-
// -------------------------------------------------------------------
|
|
12
|
-
// CLASS RIO AND FURTHER
|
|
13
|
-
/** ------------------------------------------------------------------
|
|
14
|
-
* @class RIO
|
|
15
|
-
* @classDesc Raspberry IO
|
|
16
|
-
* @param {Number} gpio
|
|
17
|
-
*/
|
|
18
|
-
export class Rio {
|
|
19
|
-
|
|
20
|
-
// ---------------------------------------------------------------
|
|
21
|
-
// CONSTANTS AND VARIABLES AND FUNCTIONS
|
|
22
|
-
// ---------------------------------------------------------------
|
|
23
|
-
// Tested system environments: model, os, libgpiod
|
|
24
|
-
static TESTED_MODELS = ["Raspberry Pi 5 Model B Rev 1.0"]
|
|
25
|
-
static TESTED_OS = ["Bookworm", "Trixie"]
|
|
26
|
-
static TESTED_LIBGPIOD = ["v1.6.3", "v2.2.1"]
|
|
27
|
-
|
|
28
|
-
// Lists of supported GPIOs. Reference GPIO: pinout.xyz
|
|
29
|
-
static RPi_GPIO_CORE = [4, 5, 6, 16, 17, 20, 21, 22, 23, 24, 25, 26, 27]
|
|
30
|
-
static RPi_GPIO_PWM = [12, 13, 18, 19]
|
|
31
|
-
static RPI_GPIOS = [...Rio.RPi_GPIO_CORE, ...Rio.RPi_GPIO_PWM]
|
|
32
|
-
static RPI_CHIP = "gpiochip0"
|
|
33
|
-
static PWM_CHIP = "pwmchip0"
|
|
34
|
-
static TYPES = ["in", "out", "pwm"]
|
|
35
|
-
|
|
36
|
-
// Map of instances
|
|
37
|
-
static instances = new Map()
|
|
38
|
-
|
|
39
|
-
// ---------------------------------------------------------------
|
|
40
|
-
// GENERIC METHODS: constructor, disable
|
|
41
|
-
/** --------------------------------------------------------------
|
|
42
|
-
* @function constructor
|
|
43
|
-
* @description
|
|
44
|
-
* @param {Number} gpio
|
|
45
|
-
* @param {String} type - {'in', 'out', 'pwm'}
|
|
46
|
-
* @return {Object}
|
|
47
|
-
*/
|
|
48
|
-
constructor(gpio, type) {
|
|
49
|
-
|
|
50
|
-
this.gpio = gpio
|
|
51
|
-
this.gpioStr = this.gpio.toString()
|
|
52
|
-
this.gpioName = "GPIO" + this.gpioStr
|
|
53
|
-
this.type = type
|
|
54
|
-
this.os = Rio.os().name
|
|
55
|
-
this.versionLib = Rio.version().substring(0, 4)
|
|
56
|
-
// Monitoring properties
|
|
57
|
-
this.watch = {
|
|
58
|
-
edge: "stop",
|
|
59
|
-
callback: false,
|
|
60
|
-
process: null
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// OS is not supported
|
|
64
|
-
if (Rio.TESTED_OS.indexOf(this.os) === -1)
|
|
65
|
-
throw new Error("This OS is not supported: " + this.os)
|
|
66
|
-
|
|
67
|
-
// libgpiod is not supported
|
|
68
|
-
const foundLib = Rio.TESTED_LIBGPIOD.find(testedVersion => {
|
|
69
|
-
return this.versionLib === testedVersion.substring(0, 4)
|
|
70
|
-
})
|
|
71
|
-
if (!foundLib)
|
|
72
|
-
throw new Error("This version of libgpiod is not supported: " + this.versionLib)
|
|
73
|
-
|
|
74
|
-
// Unknown type
|
|
75
|
-
if (Rio.TYPES.indexOf(type) === -1)
|
|
76
|
-
throw new Error("This type is not supported: " + gpio)
|
|
77
|
-
|
|
78
|
-
// gpio is not supported
|
|
79
|
-
if (Rio.RPI_GPIOS.indexOf(gpio) === -1)
|
|
80
|
-
throw new Error("This gpio is not supported: " + gpio)
|
|
81
|
-
|
|
82
|
-
// gpio is already defined
|
|
83
|
-
if (Rio.instances.has(gpio))
|
|
84
|
-
throw new Error("This gpio is already defined: " + gpio)
|
|
85
|
-
|
|
86
|
-
// this.read, this.write, monArgs depending on gpiolib version
|
|
87
|
-
switch (this.versionLib) {
|
|
88
|
-
case "v1.6":
|
|
89
|
-
this.write = (value) => {
|
|
90
|
-
exeFile("gpioset", [Rio.RPI_CHIP, this.gpioStr + "=" + value])
|
|
91
|
-
}
|
|
92
|
-
this.read = () => {
|
|
93
|
-
return exeFile("gpioget", [Rio.RPI_CHIP, this.gpioStr])
|
|
94
|
-
}
|
|
95
|
-
break
|
|
96
|
-
case "v2.2":
|
|
97
|
-
this.write = (value, time = 0) => {
|
|
98
|
-
exeFile("gpioset", ["-t" + time, this.gpioName + "=" + value])
|
|
99
|
-
}
|
|
100
|
-
this.read = () => {
|
|
101
|
-
return exeFile("gpioget", ["--numeric", this.gpioName])
|
|
102
|
-
}
|
|
103
|
-
break
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// pwm specific cases
|
|
107
|
-
if (type === "pwm") {
|
|
108
|
-
|
|
109
|
-
// gpio is not supported for PWM
|
|
110
|
-
if (Rio.RPi_GPIO_PWM.indexOf(gpio) === -1) {
|
|
111
|
-
throw new Error("PWM gpio must be one of " + Rio.RPi_GPIO_PWM)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// gpio is not setup for PWM
|
|
115
|
-
const thisConfig = Rio.config(gpio)
|
|
116
|
-
if (thisConfig.indexOf("pwm") === -1)
|
|
117
|
-
throw new Error("This gpio is not setup as PWM (see README)")
|
|
118
|
-
|
|
119
|
-
this.channel = thisConfig.slice(-1)
|
|
120
|
-
this.pwm = Rio.PWM_CHIP
|
|
121
|
-
this.pathPwm = "/sys/class/pwm/" + this.pwm + "/"
|
|
122
|
-
this.pathChannel = this.pathPwm + "pwm" + this.channel + "/"
|
|
123
|
-
}
|
|
124
|
-
// Everything OK => Add this to instance list
|
|
125
|
-
Rio.instances.set(gpio, this)
|
|
126
|
-
log("new gpio instance created:", this.gpio)
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/** --------------------------------------------------------------
|
|
130
|
-
* @function disable
|
|
131
|
-
* @description Remove instance from Rio.instances
|
|
132
|
-
* Set this.gpio = -1
|
|
133
|
-
*/
|
|
134
|
-
disable() {
|
|
135
|
-
if (Rio.instances.has(this.gpio)) {
|
|
136
|
-
|
|
137
|
-
// Stop watch process
|
|
138
|
-
if (this.type === "in") {
|
|
139
|
-
this.watch.edge = "stop"
|
|
140
|
-
this.watch.callback = false
|
|
141
|
-
if (this.watch.process !== null) {
|
|
142
|
-
this.watch.process.kill("SIGTERM")
|
|
143
|
-
log("monitoring of gpio", this.gpio, "has been stopped")
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
// Disable signal then unexport channel
|
|
149
|
-
if (this.type === "pwm") {
|
|
150
|
-
writeFileSync(this.pathChannel + "enable", "0")
|
|
151
|
-
this.enabled = false
|
|
152
|
-
writeFileSync(this.pathPwm + "unexport", this.channel)
|
|
153
|
-
this.exported = true
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Delete entry in instance Map
|
|
157
|
-
Rio.instances.delete(this.gpio)
|
|
158
|
-
log("gpio", this.gpio, "has been disabled")
|
|
159
|
-
this.gpio = -1
|
|
160
|
-
} else {
|
|
161
|
-
log("gpio", this.gpio, "is already disabled")
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// ---------------------------------------------------------------
|
|
166
|
-
// OUTPUT METHODS: set
|
|
167
|
-
/** --------------------------------------------------------------
|
|
168
|
-
* @function set
|
|
169
|
-
* @description Set gpio to 1 or 0
|
|
170
|
-
* Then invert when duration time is <> 0
|
|
171
|
-
* @param {Number} value
|
|
172
|
-
* @param {Number} duration ms
|
|
173
|
-
*/
|
|
174
|
-
set(value = 0, duration = 0) {
|
|
175
|
-
|
|
176
|
-
if (this.gpio === -1)
|
|
177
|
-
throw new Error("This instance is disabled")
|
|
178
|
-
|
|
179
|
-
if (this.type !== "out")
|
|
180
|
-
throw new Error("Set is dedicated to 'out' instances")
|
|
181
|
-
|
|
182
|
-
if ([0, 1].indexOf(value) === -1)
|
|
183
|
-
throw new Error("Value must be either 0 or 1")
|
|
184
|
-
|
|
185
|
-
if (duration === 0) {
|
|
186
|
-
this.write(value)
|
|
187
|
-
log("set gpio", this.gpio, "to", value)
|
|
188
|
-
}
|
|
189
|
-
// Optional timer
|
|
190
|
-
else {
|
|
191
|
-
this.write(value)
|
|
192
|
-
log("set gpio", this.gpio, "to", value)
|
|
193
|
-
setTimeout(() => {
|
|
194
|
-
const valueStop = Math.abs(value - 1)
|
|
195
|
-
this.write(valueStop)
|
|
196
|
-
log("set gpio", this.gpio, "to", valueStop)
|
|
197
|
-
}, duration)
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// ---------------------------------------------------------------
|
|
202
|
-
// INPUT METHODS
|
|
203
|
-
/** --------------------------------------------------------------
|
|
204
|
-
* @function get
|
|
205
|
-
* @description Get gpio value (0/1)
|
|
206
|
-
*/
|
|
207
|
-
get() {
|
|
208
|
-
if (this.gpio === -1)
|
|
209
|
-
throw new Error("This instance is disabled")
|
|
210
|
-
|
|
211
|
-
if (this.type !== "in")
|
|
212
|
-
throw new Error("Get is dedicated to 'in' instances")
|
|
213
|
-
|
|
214
|
-
const str = this.read()
|
|
215
|
-
|
|
216
|
-
let result
|
|
217
|
-
str.length === 0 ? result = -1 : result = parseInt(str.replace(/\r?\n|\r/, ""))
|
|
218
|
-
log("get gpio", this.gpio, "value:", result)
|
|
219
|
-
return result
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/** --------------------------------------------------------------
|
|
223
|
-
* @function monArgs
|
|
224
|
-
* @description Utility to take care gpiomon arguments according to libgpiod version
|
|
225
|
-
* @param {Array} gpios
|
|
226
|
-
* @param {Object} opt
|
|
227
|
-
* @return {Array}
|
|
228
|
-
*/
|
|
229
|
-
#monArgs = (gpios, opt) => {
|
|
230
|
-
let args = []
|
|
231
|
-
switch (this.versionLib) {
|
|
232
|
-
case "v1.6":
|
|
233
|
-
args.push("-b") // line-buffered: output is flushed after every line
|
|
234
|
-
args.push("--bias=" + opt.bias)
|
|
235
|
-
args.push("--format=%o %e") // gpio, edge. Others: seconds(%s) and nanoseconds (%n)
|
|
236
|
-
args.push(Rio.RPI_CHIP)
|
|
237
|
-
return args.concat(gpios)
|
|
238
|
-
case "v2.2":
|
|
239
|
-
opt.bias === "disable" ? opt.bias = "disabled" : false
|
|
240
|
-
gpios = gpios.map(gpio => "GPIO" + gpio)
|
|
241
|
-
args.push("--bias")
|
|
242
|
-
args.push(opt.bias)
|
|
243
|
-
return args.concat(gpios)
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/** --------------------------------------------------------------
|
|
248
|
-
* @function monData
|
|
249
|
-
* @description Utility to take care of gpiomon stdout data
|
|
250
|
-
* @param {String} data
|
|
251
|
-
* @return {Object}
|
|
252
|
-
*/
|
|
253
|
-
#monData = data => {
|
|
254
|
-
switch (this.versionLib) {
|
|
255
|
-
case "v1.6":
|
|
256
|
-
const [gpioV1, edgeV1] = data.toString().trim().split(/\s+/)
|
|
257
|
-
return {
|
|
258
|
-
gpio: gpioV1,
|
|
259
|
-
edge: edgeV1
|
|
260
|
-
}
|
|
261
|
-
case "v2.2":
|
|
262
|
-
const [timeV2, edgeV2, gpioV2] = data.toString().trim().split(/\s+/)
|
|
263
|
-
return {
|
|
264
|
-
time: timeV2,
|
|
265
|
-
gpio: gpioV2.substring(5, 7),
|
|
266
|
-
edge: edgeV2
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
/** --------------------------------------------------------------
|
|
271
|
-
* @function monCallback
|
|
272
|
-
* @description Utility to trigger callback from gpiomon stdout data
|
|
273
|
-
* @param {String} data
|
|
274
|
-
*/
|
|
275
|
-
#monCallback = data => {
|
|
276
|
-
const eventData = this.#monData(data)
|
|
277
|
-
const now = new Date()
|
|
278
|
-
const delta = Math.max(1, now - this.monitoredEvent.latest) // delta is always > 0ms
|
|
279
|
-
const edge = eventData.edge === "1" ? "rising" : "falling"
|
|
280
|
-
|
|
281
|
-
// Bounce detected
|
|
282
|
-
if (delta <= this.bounceTime && edge === this.monitoredEvent.edge) {
|
|
283
|
-
log("bounce detected on gpio", this.gpio, edge, delta + "ms")
|
|
284
|
-
}
|
|
285
|
-
// Trigger callback according to edge requirement
|
|
286
|
-
else {
|
|
287
|
-
if (this.watch.enabled.indexOf(edge) !== -1)
|
|
288
|
-
this.watch.callback(edge, now)
|
|
289
|
-
// log("monitored event on gpio", this.gpio, edge,)
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Update latest monitored event
|
|
293
|
-
this.monitoredEvent.latest = now
|
|
294
|
-
this.monitoredEvent.edge = edge
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/** --------------------------------------------------------------
|
|
298
|
-
* @function monitor
|
|
299
|
-
* @description Wait for edge events
|
|
300
|
-
* @param {String} edge {'stop', 'rising', 'falling' 'both'}
|
|
301
|
-
* @param {Function} callback (edge, time)
|
|
302
|
-
* @param {Object} opt - opt.bias {'disable','pull-up','pull-down'}
|
|
303
|
-
* - opt.bounce 0-1000
|
|
304
|
-
*/
|
|
305
|
-
monitor(edge, callback, opt) {
|
|
306
|
-
|
|
307
|
-
// Default and clean parameters
|
|
308
|
-
const defaultCallback = (edge, time) => {
|
|
309
|
-
log("gpio", this.gpio, "event:", edge, time)
|
|
310
|
-
}
|
|
311
|
-
const defopt = {
|
|
312
|
-
bias: "disable",
|
|
313
|
-
bounce: 0
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
callback === undefined ? callback = defaultCallback : false
|
|
317
|
-
opt = {...defopt, ...opt}
|
|
318
|
-
opt.bounce < 0 ? opt.bounce = 0 : false
|
|
319
|
-
opt.bounce > 1000 ? opt.bounce = 1000 : false
|
|
320
|
-
this.bounceTime = opt.bounce
|
|
321
|
-
this.monitoredEvent = {
|
|
322
|
-
latest: new Date(null),
|
|
323
|
-
edge: "none"
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const biases = ["disable", "pull-up", "pull-down"]
|
|
327
|
-
biases.indexOf(opt.bias) === -1 ? opt.bias = "disable" : false
|
|
328
|
-
|
|
329
|
-
if (this.gpio === -1)
|
|
330
|
-
throw new Error("This instance is disabled")
|
|
331
|
-
|
|
332
|
-
if (this.type !== "in")
|
|
333
|
-
throw new Error("Monitor is dedicated to 'in' instances")
|
|
334
|
-
|
|
335
|
-
if (["stop", "rising", "falling", "both"].indexOf(edge) === -1)
|
|
336
|
-
throw new Error("Edge " + edge + "is not supported")
|
|
337
|
-
|
|
338
|
-
if (typeof callback !== "function")
|
|
339
|
-
throw new Error("Callback is not a function")
|
|
340
|
-
|
|
341
|
-
// First time the instance is monitored or has been stopped
|
|
342
|
-
if (this.watch === "stop") {
|
|
343
|
-
|
|
344
|
-
if (edge === "stop") {
|
|
345
|
-
trap("cannot stop an unstarted monitoring on gpio", this.gpio)
|
|
346
|
-
}
|
|
347
|
-
// Edge is in ["rising", "falling", "both"]
|
|
348
|
-
else {
|
|
349
|
-
this.watch = {
|
|
350
|
-
edge: edge,
|
|
351
|
-
enabled: edge === "both" ? ["rising", "falling"] : [edge],
|
|
352
|
-
callback: callback,
|
|
353
|
-
process: spawn("gpiomon", this.#monArgs([this.gpio], opt))
|
|
354
|
-
}
|
|
355
|
-
this.watch.process.stdout.on("data", this.#monCallback)
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
// Instance is already monitored
|
|
359
|
-
else {
|
|
360
|
-
// Stop: Kill process and delete monitored data
|
|
361
|
-
if (edge === "stop") {
|
|
362
|
-
this.watch.process !== null ? this.watch.process.kill("SIGTERM") : false
|
|
363
|
-
this.watch.edge = "stop"
|
|
364
|
-
this.watch.enabled = []
|
|
365
|
-
this.watch.callback = false
|
|
366
|
-
this.watch.process = null
|
|
367
|
-
Rio.instances.set(this.gpio, this)
|
|
368
|
-
log("monitoring of gpio", this.gpio, "has been stopped")
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// Kill process, update edge and callback, define new process
|
|
372
|
-
else {
|
|
373
|
-
this.watch.process !== null ? this.watch.process.kill("SIGTERM") : false
|
|
374
|
-
this.watch.edge = edge
|
|
375
|
-
this.watch.enabled = edge === "both" ? ["rising", "falling"] : [edge]
|
|
376
|
-
this.watch.callback = callback
|
|
377
|
-
this.watch.process = spawn("gpiomon", this.#monArgs([this.gpio], opt))
|
|
378
|
-
this.watch.process.stdout.on("data", this.#monCallback)
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// ---------------------------------------------------------------
|
|
384
|
-
// PWM METHODS
|
|
385
|
-
// PLEASE NOTE - Commands to unlock a channel:
|
|
386
|
-
// pwm channel 0: echo 0 > /sys/class/pwm/pwmchip0/unexport
|
|
387
|
-
// pwm channel 0: echo 1 > /sys/class/pwm/pwmchip0/unexport
|
|
388
|
-
/** --------------------------------------------------------------
|
|
389
|
-
* @function pwmInit
|
|
390
|
-
* @description Start PWM modulation
|
|
391
|
-
* @param {Number} period in ns
|
|
392
|
-
* @param {Number} duty cycle in ns
|
|
393
|
-
* @param {Object} opt
|
|
394
|
-
*/
|
|
395
|
-
pwmInit(period = 20000000, duty = 0, opt) {
|
|
396
|
-
|
|
397
|
-
// Stop on wrong type
|
|
398
|
-
if (this.type !== "pwm")
|
|
399
|
-
throw new Error("This GPIO is not configured as PWM")
|
|
400
|
-
|
|
401
|
-
// Stop disabled device
|
|
402
|
-
if (this.gpio === -1)
|
|
403
|
-
throw new Error("This instance is disabled")
|
|
404
|
-
|
|
405
|
-
// Set default options
|
|
406
|
-
const defopt = {
|
|
407
|
-
dutyMin: 0,
|
|
408
|
-
dutyMax: 1000000000, // 1s
|
|
409
|
-
}
|
|
410
|
-
opt = {...defopt, ...opt}
|
|
411
|
-
|
|
412
|
-
this.period = period
|
|
413
|
-
this.duty = duty
|
|
414
|
-
this.dutyMin = Math.max(0, opt.dutyMin)
|
|
415
|
-
this.dutyMax = Math.min(period, opt.dutyMax)
|
|
416
|
-
|
|
417
|
-
// Check period
|
|
418
|
-
if (this.period < 100 || this.period > 1000000000)
|
|
419
|
-
throw new Error("PWM period is out of range (100ns - 1s")
|
|
420
|
-
|
|
421
|
-
// Check duty cycle
|
|
422
|
-
if (duty < this.dutyMin)
|
|
423
|
-
throw new Error("PWM duty " + duty + " is lower than duty min " + this.dutyMin)
|
|
424
|
-
if (duty > this.dutyMax)
|
|
425
|
-
throw new Error("PWM duty" + duty + " is upper than duty max" + this.dutyMax)
|
|
426
|
-
|
|
427
|
-
log("channel", this.channel, "normalized PWM data (period, duty, duty min, duty max):", this.period, this.duty, this.dutyMin, this.dutyMax)
|
|
428
|
-
|
|
429
|
-
// Export channel, then set period, duty cycle and enable
|
|
430
|
-
writeFileSync(this.pathPwm + "export", this.channel)
|
|
431
|
-
this.exported = true
|
|
432
|
-
|
|
433
|
-
try {
|
|
434
|
-
wait(100)
|
|
435
|
-
writeFileSync(this.pathChannel + "period", String(this.period))
|
|
436
|
-
writeFileSync(this.pathChannel + "duty_cycle", String(this.duty))
|
|
437
|
-
writeFileSync(this.pathChannel + "enable", "1")
|
|
438
|
-
this.enabled = true
|
|
439
|
-
|
|
440
|
-
} catch (err) {
|
|
441
|
-
trap("pwm start error:", err)
|
|
442
|
-
this.pwmStop()
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
/** --------------------------------------------------------------
|
|
447
|
-
* @function pwmStop
|
|
448
|
-
* @description Stop PWM modulation
|
|
449
|
-
*/
|
|
450
|
-
pwmStop() {
|
|
451
|
-
|
|
452
|
-
if (this.type !== "pwm")
|
|
453
|
-
throw new Error("This GPIO is not configured as PWM")
|
|
454
|
-
|
|
455
|
-
// Disable signal then unexport channel
|
|
456
|
-
writeFileSync(this.pathChannel + "enable", "0")
|
|
457
|
-
this.enabled = false
|
|
458
|
-
writeFileSync(this.pathPwm + "unexport", this.channel)
|
|
459
|
-
this.exported = true
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
/** --------------------------------------------------------------
|
|
463
|
-
* @function pwmDuty
|
|
464
|
-
* @description Change PWM duty cycle
|
|
465
|
-
* @parameter {Number} time
|
|
466
|
-
*/
|
|
467
|
-
pwmDuty(time) {
|
|
468
|
-
|
|
469
|
-
// Not PWM
|
|
470
|
-
if (this.type !== "pwm")
|
|
471
|
-
throw new Error("This GPIO is not configured as PWM")
|
|
472
|
-
|
|
473
|
-
// Not initialized
|
|
474
|
-
if (!this.enabled || !this.exported) {
|
|
475
|
-
trap("PWM duty cycle cannot be update if port is not exported and enabled")
|
|
476
|
-
return
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
// Out of range
|
|
480
|
-
this.duty = time
|
|
481
|
-
if (typeof this.duty !== "number" || this.duty < this.dutyMin) {
|
|
482
|
-
this.duty = this.dutyMin
|
|
483
|
-
trap("duty cycle forced to", this.dutyMin)
|
|
484
|
-
}
|
|
485
|
-
if (typeof this.duty !== "number" || this.duty > this.dutyMax) {
|
|
486
|
-
this.duty = this.dutyMax
|
|
487
|
-
trap("duty cycle forced to", this.dutyMax)
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
writeFileSync(this.pathChannel + "duty_cycle", this.duty.toString())
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
// -------------------------------------------------------------------
|
|
495
|
-
// STATIC METHODS
|
|
496
|
-
/** ------------------------------------------------------------------
|
|
497
|
-
* @function Rio.config
|
|
498
|
-
* @description Return GPIO configuration
|
|
499
|
-
* @param {Number} gpio
|
|
500
|
-
* @return {String}
|
|
501
|
-
*/
|
|
502
|
-
Rio.config = gpio => {
|
|
503
|
-
const a = exeShell("pinctrl get " + gpio).trim().split(/\s+/)
|
|
504
|
-
return a[a.length - 1].toLowerCase()
|
|
505
|
-
}
|
|
506
|
-
/** ------------------------------------------------------------------
|
|
507
|
-
* @function Rio.configs
|
|
508
|
-
* @description Return GPIO configuration for all supported GPIO
|
|
509
|
-
* @return {Object}
|
|
510
|
-
*/
|
|
511
|
-
Rio.configs = () => {
|
|
512
|
-
let configs = {}
|
|
513
|
-
Rio.RPI_GPIOS.forEach(gpio => {
|
|
514
|
-
configs[gpio] = Rio.config(gpio)
|
|
515
|
-
})
|
|
516
|
-
log("gpio configs:", configs)
|
|
517
|
-
return configs
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
/** ------------------------------------------------------------------
|
|
521
|
-
* @function Rio.info
|
|
522
|
-
* @description Exec gpioinfo
|
|
523
|
-
* @return {Object}
|
|
524
|
-
*/
|
|
525
|
-
Rio.info = () => {
|
|
526
|
-
return exeFile("gpioinfo", [Rio.RPI_CHIP])
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
/** ------------------------------------------------------------------
|
|
530
|
-
* @function Rio.detect
|
|
531
|
-
* @description Exec gpiodetect
|
|
532
|
-
* @return {Object}
|
|
533
|
-
*/
|
|
534
|
-
Rio.detect = () => {
|
|
535
|
-
return exeFile("gpiodetect")
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
/** ------------------------------------------------------------------
|
|
539
|
-
* @function Rio.version
|
|
540
|
-
* @description Return libgpiod version
|
|
541
|
-
* @return {Object}
|
|
542
|
-
*/
|
|
543
|
-
Rio.version = () => {
|
|
544
|
-
return exeFile("gpiodetect", ["-v"]).split("\n").shift().substring(22).trim()
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
/** ------------------------------------------------------------------
|
|
548
|
-
* @function Rio.stopMonitoring
|
|
549
|
-
* @description Stop monitoring of all inputs
|
|
550
|
-
*/
|
|
551
|
-
Rio.stopMonitoring = () => {
|
|
552
|
-
Rio.instances.forEach((that) => {
|
|
553
|
-
that.watch.edge = "stop"
|
|
554
|
-
that.watch.callback = false
|
|
555
|
-
if (that.watch.process !== null) {
|
|
556
|
-
that.watch.process.kill("SIGTERM")
|
|
557
|
-
log("monitoring of gpio", that.gpio, "has been stopped")
|
|
558
|
-
}
|
|
559
|
-
})
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
/** ------------------------------------------------------------------
|
|
563
|
-
* @function Rio.disableAll
|
|
564
|
-
* @description Disable all instances
|
|
565
|
-
*/
|
|
566
|
-
Rio.disableAll = () => {
|
|
567
|
-
Rio.instances.forEach((that) => {
|
|
568
|
-
that.disable()
|
|
569
|
-
})
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
/** ------------------------------------------------------------------
|
|
573
|
-
* @function Rio.cpuInfo
|
|
574
|
-
* @description Read '/proc/cpuinfo' and store it for further call
|
|
575
|
-
* @return {String}
|
|
576
|
-
*/
|
|
577
|
-
Rio.cpuInfo = () => {
|
|
578
|
-
if (typeof Rio.cpuInfo.value === "undefined") {
|
|
579
|
-
try {
|
|
580
|
-
Rio.cpuInfo.value = readFileSync("/proc/cpuinfo", "utf8")
|
|
581
|
-
} catch (err) {
|
|
582
|
-
trap("catched error opening '/proc/cpuinfo:", err)
|
|
583
|
-
Rio.cpuInfo.value = ""
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
return Rio.cpuInfo.value
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
/** ------------------------------------------------------------------
|
|
590
|
-
* @function Rio.os
|
|
591
|
-
* @description Return OS ID and name
|
|
592
|
-
* @return {Object} {id: '12', name: 'Bookworm'}
|
|
593
|
-
*/
|
|
594
|
-
Rio.os = () => {
|
|
595
|
-
const strOs = exeShell("cat /etc/os-release")
|
|
596
|
-
if (strOs.length > 0) {
|
|
597
|
-
const lines = strOs.split("\n")
|
|
598
|
-
const info = {}
|
|
599
|
-
lines.forEach(line => {
|
|
600
|
-
const [key, value] = line.split("=")
|
|
601
|
-
if (key && value) {
|
|
602
|
-
info[key] = value.replace(/"/g, "");
|
|
603
|
-
}
|
|
604
|
-
})
|
|
605
|
-
let res = {
|
|
606
|
-
id: info["VERSION_ID"],
|
|
607
|
-
name: info["VERSION_CODENAME"]
|
|
608
|
-
}
|
|
609
|
-
res.name = res.name.substring(0, 1).toUpperCase() + res.name.substring(1);
|
|
610
|
-
return res
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
/** ------------------------------------------------------------------
|
|
614
|
-
* @function Rio.isSystemSupported
|
|
615
|
-
* @description Check if all hardware and software requirements are
|
|
616
|
-
* satisfied to run the RPI-IO module
|
|
617
|
-
* @return {Boolean}
|
|
618
|
-
*/
|
|
619
|
-
Rio.isSystemSupported = () => {
|
|
620
|
-
|
|
621
|
-
// Test hardware
|
|
622
|
-
if (/Raspberry Pi/i.test(Rio.cpuInfo()) === false) {
|
|
623
|
-
trap("not a Raspberry Pi hardware")
|
|
624
|
-
return false
|
|
625
|
-
}
|
|
626
|
-
log("👍 Hardware is Raspberry Pi")
|
|
627
|
-
|
|
628
|
-
// Test model
|
|
629
|
-
const line = Rio.cpuInfo().split("\n").find(str => str.indexOf("Model") !== -1)
|
|
630
|
-
const model = line.split(":")[1].trim()
|
|
631
|
-
if (Rio.TESTED_MODELS.indexOf(model) === -1) {
|
|
632
|
-
trap("model not tested:", model)
|
|
633
|
-
return false
|
|
634
|
-
}
|
|
635
|
-
log("👍 ", model, "is supported")
|
|
636
|
-
|
|
637
|
-
// Test OS
|
|
638
|
-
const thisOs = Rio.os()
|
|
639
|
-
if (Rio.TESTED_OS.indexOf(thisOs.name) === -1) {
|
|
640
|
-
trap("OS not tested:", thisOs.name, thisOs.id)
|
|
641
|
-
return false
|
|
642
|
-
}
|
|
643
|
-
log("👍 ", thisOs.name, "OS is supported")
|
|
644
|
-
|
|
645
|
-
// Test libgpiod version x.y
|
|
646
|
-
const gpiodVersion = Rio.version().substring(0, 4)
|
|
647
|
-
const foundLib = Rio.TESTED_LIBGPIOD.find(testedVersion => {
|
|
648
|
-
return gpiodVersion === testedVersion.substring(0, 4)
|
|
649
|
-
})
|
|
650
|
-
if (!foundLib) {
|
|
651
|
-
trap("libgpiod version not tested:", gpiodVersion)
|
|
652
|
-
return false
|
|
653
|
-
}
|
|
654
|
-
log("👍 libgpiod version", gpiodVersion + ".x", "is supported")
|
|
655
|
-
log("👍 Systems requirements for rpi-io are met")
|
|
656
|
-
return true
|
|
657
|
-
}
|
|
658
|
-
/** ------------------------------------------------------------------
|
|
659
|
-
* @function Rio.logCfg
|
|
660
|
-
*/
|
|
661
|
-
Rio.logCfg = logCfg
|
|
662
|
-
|
|
663
|
-
/** ------------------------------------------------------------------
|
|
664
|
-
* @function Rio.log
|
|
665
|
-
*/
|
|
666
|
-
Rio.log = log
|
|
667
|
-
|
|
668
|
-
/** ------------------------------------------------------------------
|
|
669
|
-
* @function Rio.trap
|
|
670
|
-
*/
|
|
671
|
-
Rio.trap = trap
|
|
672
|
-
|
|
673
|
-
// -------------------------------------------------------------------
|
|
674
|
-
// EoF
|
|
675
|
-
// -------------------------------------------------------------------
|