rpi-io 1.0.0 → 1.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Guillaume DORBES
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,438 @@
1
+ # rpi-io
2
+ ![Static Badge](https://img.shields.io/badge/rpi--io-_1.1_-FF5500?style=flat) ![Static Badge](https://img.shields.io/badge/Nodejs-%3E_23-66cc33?logo=nodedotjs&logoColor=white) ![Static Badge](https://img.shields.io/badge/NPM-%3E_10-CC3534?logo=npm&logoColor=white) ![Static Badge](https://img.shields.io/badge/Raspberry_Pi-Zero2_4B_5B-C51A4A?logo=raspberrypi&logoColor=white) ![Static Badge](https://img.shields.io/badge/OS-Bookworm_Trixie-0D7AB9?style=flat)
3
+
4
+
5
+ **rpi-io** is a lite [ESM](https://nodejs.org/api/esm.html#modules-ecmascript-modules) module for **Node.js** to control **Raspberry Pi** GPIO: access (in, out), input event detection and [PWM](https://en.wikipedia.org/wiki/Pulse-width_modulation) peripheral control.
6
+
7
+ **rpi-io** supports only recent versions of Raspberry and related OS and middlewares :
8
+
9
+ - Raspberry Pi models: *RPi 5,* *RPi 4B* and *RPi Zero 2.*
10
+ - Raspberry Pi OS (64-bit): Debian *Bookworm* and *Trixie*.
11
+ - Middlewares (installed by default with OS distribution):
12
+ - Input/Output - [libgpiod](https://libgpiod.readthedocs.io/en/stable/) v1.6.3 (*Bookworm*) and v2.2.1 (*Trixie*)
13
+ - PWM - [sysfs](https://en.wikipedia.org/wiki/Sysfs) interface.
14
+
15
+ ## Installation
16
+
17
+ ````shell
18
+ npm install rpi-io
19
+ ````
20
+
21
+ ## Configuration
22
+
23
+ By default rpi-io does not require specific configuration.
24
+
25
+ If you want to use [hardware-base PWM](https://pinout.xyz/pinout/pwm) peripherals, some configuration is required:
26
+
27
+ - Open the file `/boot/firmware/config.txt` and add the required [dtoverlay](https://www.raspberrypi.com/documentation/computers/configuration.html#part3.1) configuration for PWM as showned in the examples below.
28
+
29
+ ```shell
30
+ # Examples of PWM configuration to add to/boot/firmware/config.txt
31
+ # Default one-channel config: GPIO 18 as channel 0
32
+ [all]
33
+ dtoverlay=pwm
34
+ # Default two-channel config: GPIO 18 as channel 0 and GPIO 19 as channel 1
35
+ [all]
36
+ dtoverlay=pwm-2chan
37
+ # Custom two-channel config: GPIO 12 as channel 0 and GPIO 13 as channel 1
38
+ [all]
39
+ dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4
40
+ ```
41
+
42
+ - Reboot the Raspberry Pi.
43
+ - Test the PWM configuration.
44
+
45
+ ```shell
46
+ # Testing PWM configuration after reboot
47
+ pinctrl get 12
48
+ # 12: a0 pd | lo // GPIO12 = PWM0_CHAN0
49
+ pinctrl get 13
50
+ # 13: a0 pd | lo // GPIO13 = PWM0_CHAN1
51
+ pinctrl get 18
52
+ # 18: no pd | -- // GPIO18 = none
53
+ pinctrl get 19
54
+ # 19: no pd | -- // GPIO19 = none
55
+ ```
56
+
57
+ ## Usage
58
+
59
+ PLEASE NOTE: In all this document, GPIOs numbers are the BCM ones as defined in https://pinout.xyz/.
60
+
61
+ ### *OUT* operations
62
+
63
+ #### LED control
64
+
65
+ ![Connecting a LED to Raspberry Pi GPIO XYZ](https://rpisquare.com/img/rpi-io-led.png)
66
+
67
+ ```javascript
68
+ // Import rpi-io module
69
+ import {Rio} from "rpi-io";
70
+ // Define instance for OUT operation on some GPIO
71
+ const led = new Rio(17, "out");
72
+ // Turn the led on
73
+ led.set(1);
74
+ // Turn the led off
75
+ led.set(0);
76
+ ```
77
+
78
+ #### Active buzzer beep
79
+
80
+ `set` method has an optional *time* parameter to invert the command after some delay (ms). It may be useful to beep a buzzer.
81
+
82
+ ![Connecting an active buzzer to Raspberry Pi GPIO XYZ](https://rpisquare.com/img/rpi-io-active-buzzer.png)
83
+
84
+ ```javascript
85
+ // Import rpi-io module
86
+ import {Rio} from "rpi-io";
87
+ // Define instance for OUT operation on some GPIO
88
+ const buzzer = new Rio(23, "out");
89
+ // Turn the buzzer on for 3 seconds
90
+ buzzer.set(1, 3000);
91
+ ```
92
+
93
+ ### *IN* operations
94
+
95
+ #### Getting button status and listening to input events
96
+
97
+ ![Connecting a pull-down button to Raspberry Pi GPIO XYZ](https://rpisquare.com/img/rpi-io-pull-down-btn.png)
98
+
99
+ ```javascript
100
+ // Import rpi-io module
101
+ import {Rio} from "rpi-io";
102
+
103
+ // Define instance for IN operation on some GPIO
104
+ const button = new Rio(18, "in");
105
+
106
+ // Get the instant value of the button
107
+ button.get(); // Return 0 (opened) or 1 (closed)
108
+
109
+ // Monitor button events ("rising, "falling" or "both")
110
+ // and receive results in callback function
111
+ button.monitor("both", (event) => {
112
+ // event.edge = "rising" or "falling"
113
+ // event.time = timestamp
114
+ console.log("event:", event.edge, event.time)
115
+ });
116
+ ```
117
+
118
+ ### PWM operations with servo motor in hardware mode
119
+
120
+ REMINDER: PWM peripherals used in hardware mode need some specific [configuration](#Configuration).
121
+
122
+ ![PWM servo motor connected to Raspberry Pi GPIO XYZ](https://rpisquare.com/img/rpi-io-servo-motor.png)
123
+
124
+
125
+ ```javascript
126
+ // Import rpi-io module
127
+ import {Rio} from "rpi-io";
128
+
129
+ // Define instance for PWM operation on some PWM GPIO
130
+ const servoMotor = new Rio(13, "pwm");
131
+
132
+ // PWM servo motor parameters depends motor specifications.
133
+ // - period = 20,000,000 nano-seconds = 20 milli-seconds ~ frequency = 50 Hz
134
+ // - duty cycle on start = 500,000 nano-seconds = 0.5 milli-seconds
135
+ // - dutyMin = 0.5 ms ~ 0°
136
+ // - dutyMax = 2.5 ms ~ 180°
137
+ servoMotor.pwmInit(20000000, 500000, {
138
+ dutyMin: 500000,
139
+ dutyMax: 2500000
140
+ });
141
+ console.log("servo position = 0°");
142
+
143
+ // Wait 2s then change duty cyle to 1.5 ms
144
+ setTimeout(() => {
145
+ servo.pwmDuty(1500000);
146
+ console.log("servo position = 90°");
147
+ }, 2000);
148
+ ```
149
+
150
+ ## API
151
+
152
+ The **rpi-io** API is based on the class `Rio` with instance methods and static functions.
153
+
154
+ ### Constructor and generic methods
155
+
156
+ #### Constructor - Rio(gpio, type)
157
+
158
+ ##### Example
159
+ ```javascript
160
+ import {Rio} from "rpi-io";
161
+ const myOutput = new Rio(17, 'out');
162
+ ```
163
+ ##### Parameters
164
+ - **gpio** *{Number}* Must be one of the GPIO number as defined in [pinout.xyz](pinout.xyz).
165
+ - **type** *{String}* Must be one of the following values: 'in', 'out', 'pwm'.
166
+
167
+ ##### Detected errors
168
+ - Operating system version is not supported
169
+ - *libgpiod* version is not supported
170
+ - **gpio** is not defined as GPIO in [pinout.xyz](pinout.xyz).
171
+ - **gpio** is already used by another instance.
172
+ - **type** value is not recognized.
173
+ - **type**='pwm' and **gpio** is not properly configured.
174
+
175
+
176
+
177
+ #### Method - disable()
178
+ To remove an instance from the list.
179
+
180
+ ##### Example
181
+ ```javascript
182
+ myOutput.disable();
183
+ ```
184
+
185
+ ### Methods for instances where type='out'
186
+ #### set(value, duration)
187
+
188
+ To set some *out* GPIO to 0 or 1.
189
+
190
+ ##### Example
191
+ ```javascript
192
+ import {Rio} from "rpi-io";
193
+ const myOutput = new Rio(17, 'out');
194
+ myOutput.set(1, 3000);
195
+ ```
196
+ ##### Parameters
197
+ - **value** *{Number}* 0 or 1
198
+ - **duration** *{Number}* Time in ms. When defined, at the end of the defined time the output is inverted.
199
+
200
+ ##### Detected errors
201
+ - Instance type is not 'out'.
202
+ - Instance is disabled or undefined.
203
+
204
+
205
+
206
+ ### Methods for instances where type='in'
207
+
208
+ #### get()
209
+
210
+ To read instant value of some *in* GPIO.
211
+
212
+ ##### Example
213
+
214
+ ```javascript
215
+ import {Rio} from "rpi-io";
216
+ const myInput = new Rio(18, 'in');
217
+ let currentValue = myInput.get();
218
+ ```
219
+
220
+ ##### Returned value
221
+
222
+ 0 or 1
223
+
224
+ ##### Detected errors
225
+
226
+ - Instance type is not 'in'.
227
+ - Instance is disabled or undefined.
228
+
229
+
230
+
231
+ #### monitor(edge, callback)
232
+
233
+ To monitor activities on some *in* GPIO and receive events in a callback function.
234
+
235
+ ##### Example
236
+
237
+ ```javascript
238
+ import {Rio} from "rpi-io";
239
+ const myButton = new Rio(18, 'in');
240
+ myButton.monitor("both", event => {
241
+ console.log("event:", event.edge, event.time)
242
+ });
243
+ ```
244
+
245
+ ##### Parameters
246
+
247
+ - **edge** *{String}* 'rising', 'falling' or 'both'. Any other value stops instance monitoring.
248
+ - **callback** *{Function}* Callback function with one Object parameter for input events on instance e.g. `event = {edge: 'rising,' time: 'Thu Oct 30 2025 18:12:35 GMT+0100'}`
249
+
250
+ ##### Detected errors
251
+
252
+ - Instance type is not 'in'.
253
+ - Instance is disabled or undefined.
254
+
255
+
256
+
257
+ ### Methods for instances where type='pwm'
258
+ #### pwmInit(period, duty, options)
259
+
260
+ To initialize some *pwm* GPIO according to connected device specifications.
261
+
262
+ ##### Example
263
+
264
+ ```javascript
265
+ import {Rio} from "rpi-io";
266
+ const servoMotor = new Rio(13, "pwm");
267
+ servoMotor.pwmInit(20000000, 500000, {
268
+ dutyMin: 500000,
269
+ dutyMax: 2500000
270
+ });
271
+ console.log("servo position = 0°");
272
+ ```
273
+
274
+ ##### Parameters
275
+
276
+ All time parameters are defined in nano-seconds (ns).
277
+
278
+ - **period** *{Number}* Time period in which [duty cycle](https://en.wikipedia.org/wiki/Pulse-width_modulation) varies.
279
+ - **duty** *{Number}* Initial duty cycle.
280
+ - **options** *{Object}*
281
+ - **dutyMin** *{Number}* Default = 0. Minimum duty cycle (ns) as defined in connected device specifications.
282
+ - **dutyMax** *{Number}* Default = 1,000,000,000. Maximum duty cycle (ns) as defined in connected device specifications.
283
+
284
+ ##### Detected errors
285
+
286
+ - Instance type is not 'pwm'.
287
+ - Instance is disabled or undefined.
288
+ - **period** is out-of-range [100, 1,000,000,000]
289
+ - **duty** < **options.dutyMin**
290
+ - **duty** > **options.dutyMax**
291
+
292
+
293
+
294
+ #### pwmStop()
295
+
296
+ To stop PWM modulation on some *pwm* GPIO.
297
+
298
+ ##### Example
299
+
300
+ ```javascript
301
+ servoMotor.pwmStop()
302
+ ```
303
+
304
+ ##### Detected errors
305
+
306
+ - Instance type is not 'pwm'.
307
+ - Instance is disabled or undefined.
308
+
309
+
310
+
311
+ #### pwmDuty(time)
312
+
313
+ To update duty cycle of some *pwm* GPIO.
314
+
315
+ ##### Example
316
+
317
+ ```javascript
318
+ servo.pwmDuty(1500000)
319
+ ```
320
+
321
+ ##### Parameters
322
+
323
+ - **time** *{Number}* Duty cycle (ns)
324
+
325
+ ##### Detected errors
326
+
327
+ - Instance type is not 'pwm'.
328
+ - PWM instance is not properly initialized.
329
+ - **time** is out of range [dutyMin, dutyMax]
330
+
331
+
332
+
333
+ ### Utility functions
334
+ #### Rio.config(gpio)
335
+
336
+ To return configuration of some GPIO
337
+
338
+ ##### Example
339
+
340
+ ```javascript
341
+ import {Rio} from "rpi-io";
342
+ console.log(Rio.config(13))
343
+ // '13': 'pwm0_chan1'
344
+ ```
345
+
346
+ #### Rio.info()
347
+
348
+ Javascript equivalent of command `gpioinfo`.
349
+
350
+ ##### Example
351
+
352
+ ```javascript
353
+ import {Rio} from "rpi-io";
354
+ console.log(Rio.info())
355
+ ```
356
+
357
+ #### Rio.detect()
358
+
359
+ Javascript equivalent of command `gpiodetect`.
360
+
361
+ ##### Example
362
+
363
+ ```javascript
364
+ import {Rio} from "rpi-io";
365
+ console.log(Rio.detect())
366
+ ```
367
+
368
+ #### Rio.version()
369
+
370
+ To retrieve libgpiod version.
371
+
372
+ ##### Example
373
+
374
+ ```javascript
375
+ import {Rio} from "rpi-io";
376
+ console.log(Rio.version())
377
+ // 'v1.6.3',
378
+ ```
379
+
380
+ #### Rio.os()
381
+
382
+ To retrieve Rapsberry Pi OS info.
383
+
384
+ ##### Example
385
+
386
+ ```javascript
387
+ import {Rio} from "rpi-io";
388
+ console.log(Rio.os())
389
+ // {id: '12', name: 'Bookworm'}
390
+ ```
391
+
392
+ #### Rio.isSystemSupported()
393
+
394
+ To check if device is supported by **rpi-io** (device, model, os, middleware). It returns *true/false* and displays details in console.
395
+
396
+ #### Rio.log(level)
397
+ To set the level of console used in the module:
398
+
399
+ - 0: no console
400
+ - 1: console only for errors and warnings (default).
401
+ - 2: more logged data
402
+
403
+ ##### Example
404
+
405
+ ```javascript
406
+ import {Rio} from "rpi-io";
407
+ Rio.log(0)
408
+ Rio.isSystemSupported() // Nothing displayed in console
409
+ Rio.log(2)
410
+ Rio.isSystemSupported() // See console below
411
+ // 08:15:16.506 🔎 👍 Hardware is Raspberry Pi
412
+ // 08:15:16.539 🔎 👍 Raspberry Pi 5 Model B Rev 1.0 is supported
413
+ // 08:15:16.543 🔎 👍 Bookworm OS is supported
414
+ // 08:15:16.545 🔎 👍 libgpiod version v1.6.x is supported
415
+ // 08:15:16.545 🔎 👍 Systems requirements for rpi-io are met
416
+ ```
417
+
418
+
419
+
420
+ ## Additional information
421
+
422
+ ### Benchmark
423
+
424
+ The following table summarizes GPIO *set* average times with rpi-io v1.1.0 and various hardwares and OS.
425
+
426
+ | | RPi 5B | RPi 4B | RPi Zero2 |
427
+ | -------- | ------- | ---------- | ----------- |
428
+ | Bookworm | 1.37 ms | 4.56 ms | 9.07 ms |
429
+ | Trixie | 3.82 ms | not tested | not tested. |
430
+
431
+
432
+
433
+ ### Keywords
434
+
435
+ **raspberry pi rpi gpio pwm nodejs esm**
436
+
437
+ ---
438
+
package/esm/dev.mjs ADDED
@@ -0,0 +1,59 @@
1
+ // -------------------------------------------------------------------
2
+ // RPi GPIO CONTROL - DEV TOOLS
3
+ // -------------------------------------------------------------------
4
+ let debug = {
5
+ log: false,
6
+ trap: true
7
+ }
8
+
9
+ // -------------------------------------------------------------------
10
+ // FUNCTIONS
11
+ /** ------------------------------------------------------------------
12
+ * @function now
13
+ * @description Return timestamp as formatted string
14
+ * @return {String}
15
+ */
16
+ export const now = () => {
17
+ const date = new Date()
18
+ return date.toLocaleTimeString() + "." + date.getMilliseconds().toLocaleString('en', {
19
+ minimumIntegerDigits: 3,
20
+ minimumFractionDigits: 0,
21
+ useGrouping: false
22
+ })
23
+ }
24
+
25
+ /** ------------------------------------------------------------------
26
+ * @function log
27
+ * @description Global customized timestamped console.log
28
+ * Requires global variable 'debug'
29
+ */
30
+ export const log = function () {
31
+ if (debug.log)
32
+ console.log.apply(console, Array.prototype.concat.apply([now(), "🔎 "], arguments))
33
+ }
34
+
35
+ /** ------------------------------------------------------------------
36
+ * @function trap
37
+ * @description Colored log for error messages ⚠️ nodejs only ~ CSS color #F50
38
+ */
39
+ export const trap = function () {
40
+ if (debug.trap)
41
+ console.log.apply(console, Array.prototype.concat.apply([now(), "⚠️", "\x1b[38;2;255;80;0m", ...arguments, "\x1b[0m"]))
42
+ }
43
+
44
+ /** ------------------------------------------------------------------
45
+ * @function cfgLog
46
+ * @description Set log configuration
47
+ * @param {Number} level 0: no log, 1: trap only, 2: trap and log
48
+ */
49
+ export const cfgLog = level => {
50
+ debug = {
51
+ log: false,
52
+ trap: false
53
+ }
54
+ level > 1 ? debug.log = true : false
55
+ level > 0 ? debug.trap = true : false
56
+ }
57
+ // -------------------------------------------------------------------
58
+ // EoF
59
+ // -------------------------------------------------------------------
package/esm/rio.mjs ADDED
@@ -0,0 +1,553 @@
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 {cfgLog, 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
+ // List of used GPIOs
37
+ static used_gpios = []
38
+ // Object list of monitored inputs
39
+ static monitored = {}
40
+ // Current monitoring process
41
+ static monitoringProcess = null
42
+
43
+ // ---------------------------------------------------------------
44
+ // GENERIC METHODS: constructor, disable
45
+ /** --------------------------------------------------------------
46
+ * @function constructor
47
+ * @description
48
+ * @param {Number} gpio
49
+ * @param {String} type - {'in', 'out', 'pwm'}
50
+ * @return {Object}
51
+ */
52
+ constructor(gpio, type) {
53
+
54
+ this.chip = Rio.RPI_CHIP
55
+ this.gpio = gpio
56
+ this.gpioStr = this.gpio.toString()
57
+ this.gpioName = "GPIO" + this.gpioStr
58
+ this.type = type
59
+ this.os = Rio.os().name
60
+ this.versionLib = Rio.version().substring(0, 4)
61
+
62
+ // OS is not supported
63
+ if (Rio.TESTED_OS.indexOf(this.os) === -1)
64
+ throw new Error("This OS is not supported: " + this.os)
65
+
66
+ // libgpiod is not supported
67
+ const foundLib = Rio.TESTED_LIBGPIOD.find(testedVersion => {
68
+ return this.versionLib === testedVersion.substring(0, 4)
69
+ })
70
+ if (!foundLib)
71
+ throw new Error("This version of libgpiod is not supported: " + this.versionLib)
72
+
73
+ // Unknown type
74
+ if (Rio.TYPES.indexOf(type) === -1)
75
+ throw new Error("This type is not supported: " + gpio)
76
+
77
+ // gpio is not supported
78
+ if (Rio.RPI_GPIOS.indexOf(gpio) === -1)
79
+ throw new Error("This gpio is not supported: " + gpio)
80
+
81
+ // gpio is already used
82
+ if (Rio.used_gpios.indexOf(gpio) !== -1)
83
+ throw new Error("This gpio is already used: " + gpio)
84
+
85
+ // this.read, this.write, monArgs depending on gpiolib version
86
+ switch (this.versionLib) {
87
+ case "v1.6":
88
+ this.write = (value, time = 0) => {
89
+ exeFile("gpioset", [Rio.RPI_CHIP, this.gpioStr + "=" + value])
90
+ }
91
+ this.read = () => {
92
+ return exeFile("gpioget", [Rio.RPI_CHIP, this.gpioStr])
93
+ }
94
+ this.monArgs = gpios => {
95
+ let args = []
96
+ args.push("-b") // line-buffered: output is flushed after every line
97
+ args.push("--format=%o %e") // gpio, edge. Others: seconds(%s) and nanoseconds (%n)
98
+ args.push(Rio.RPI_CHIP)
99
+ args = args.concat(gpios)
100
+ return args
101
+ }
102
+ this.monData = data => {
103
+ const [gpio, edge] = data.toString().trim().split(/\s+/)
104
+ return {
105
+ gpio: gpio,
106
+ edge: edge
107
+ }
108
+ }
109
+ break
110
+ case "v2.2":
111
+ this.write = (value, time = 0) => {
112
+ exeFile("gpioset", ["-t" + time, this.gpioName + "=" + value])
113
+ }
114
+ this.read = () => {
115
+ return exeFile("gpioget", ["--numeric", this.gpioName])
116
+ }
117
+ this.monArgs = gpios => {
118
+ gpios = gpios.map(gpio => "GPIO" + gpio)
119
+ let args = []
120
+ args = args.concat(gpios)
121
+ return args
122
+ }
123
+ this.monData = data => {
124
+ const [time, edge, gpio] = data.toString().trim().split(/\s+/)
125
+ return {
126
+ gpio: gpio.substring(5,7),
127
+ edge: edge
128
+ }
129
+ }
130
+ break
131
+ }
132
+
133
+ // pwm specific cases
134
+ if (type === "pwm") {
135
+
136
+ // gpio is not supported for PWM
137
+ if (Rio.RPi_GPIO_PWM.indexOf(gpio) === -1) {
138
+ throw new Error("PWM gpio must be one of " + Rio.RPi_GPIO_PWM)
139
+ }
140
+
141
+ // gpio is not setup for PWM
142
+ const thisConfig = Rio.config(gpio)
143
+ if (thisConfig.indexOf("pwm") === -1)
144
+ throw new Error("This gpio is not setup as PWM (see README)")
145
+
146
+ this.channel = thisConfig.slice(-1)
147
+ this.pwm = Rio.PWM_CHIP
148
+ this.pathPwm = "/sys/class/pwm/" + this.pwm + "/"
149
+ this.pathChannel = this.pathPwm + "pwm" + this.channel + "/"
150
+ }
151
+ }
152
+
153
+ /** --------------------------------------------------------------
154
+ * @function disable
155
+ * @description Remove instance from Rio.used_gpios
156
+ * Set this.gpio = -1
157
+ */
158
+ disable() {
159
+ const i = Rio.used_gpios.indexOf(this.gpio)
160
+ if (this.gpio === -1 || i === -1) {
161
+ log("gpio", this.gpio, "is already disabled")
162
+ } else {
163
+ Rio.used_gpios.splice(i, 1)
164
+ log("gpio", this.gpio, "has been disabled")
165
+ this.gpio = -1
166
+ }
167
+ }
168
+
169
+ // ---------------------------------------------------------------
170
+ // OUTPUT METHODS: set
171
+ /** --------------------------------------------------------------
172
+ * @function set
173
+ * @description Set gpio to 1 or 0
174
+ * Then invert when duration time is <> 0
175
+ * @param {Number} value
176
+ * @param {Number} duration ms
177
+ */
178
+ set(value = 0, duration = 0) {
179
+
180
+ if (this.gpio === -1)
181
+ throw new Error("This instance is disabled")
182
+
183
+ if (this.type !== "out")
184
+ throw new Error("Set is dedicated to 'out' instances")
185
+
186
+ if ([0, 1].indexOf(value) === -1)
187
+ throw new Error("Value must be either 0 or 1")
188
+
189
+ if (duration === 0) {
190
+ this.write(value)
191
+ log("set gpio", this.gpio, "to", value)
192
+ }
193
+ // Optional timer
194
+ else {
195
+ this.write(value)
196
+ log("set gpio", this.gpio, "to", value)
197
+ setTimeout(() => {
198
+ const valueStop = Math.abs(value - 1)
199
+ this.write(valueStop)
200
+ log("set gpio", this.gpio, "to", valueStop)
201
+ }, duration)
202
+ }
203
+ }
204
+
205
+ // ---------------------------------------------------------------
206
+ // INPUT METHODS
207
+ /** --------------------------------------------------------------
208
+ * @function get
209
+ * @description Get gpio value (0/1)
210
+ */
211
+ get() {
212
+ if (this.gpio === -1)
213
+ throw new Error("This instance is disabled")
214
+
215
+ if (this.type !== "in")
216
+ throw new Error("Get is dedicated to 'in' instances")
217
+
218
+ const str = this.read()
219
+
220
+ let result
221
+ str.length === 0 ? result = -1 : result = parseInt(str.replace(/\r?\n|\r/, ""))
222
+ log("get gpio", this.gpio, "value:", result)
223
+ return result
224
+ }
225
+
226
+ /** --------------------------------------------------------------
227
+ * @function monitor
228
+ * @description Wait for edge events
229
+ * @param {String} edge {'stop', 'rising', 'falling' 'both'}
230
+ * @param {Function} callback (edge, time)
231
+ */
232
+ monitor(edge, callback) {
233
+
234
+ if (this.gpio === -1)
235
+ throw new Error("This instance is disabled")
236
+
237
+ if (this.type !== "in")
238
+ throw new Error("Monitor is dedicated to 'in' instances")
239
+
240
+ // Update monitoring list
241
+ Rio.monitored[this.gpio] = {
242
+ edge: edge,
243
+ callback: callback
244
+ }
245
+
246
+ // Suspend current monitoring if any
247
+ if (Rio.monitoringProcess !== null) {
248
+ Rio.monitoringProcess.kill("SIGTERM")
249
+ Rio.monitoringProcess = null
250
+ }
251
+
252
+ // Default stop command => Remove current gpio from monitoring list
253
+ if (["rising", "falling", "both"].indexOf(edge) === -1) {
254
+ delete Rio.monitored[this.gpio]
255
+ log("gpio", this.gpio, "is no longer being monitored")
256
+ }
257
+
258
+ // If monitored list is not empty => relaunch gpiomon
259
+ const list = Object.keys(Rio.monitored)
260
+ if (list.length > 0) {
261
+ Rio.monitoringProcess = spawn("gpiomon", this.monArgs(list))
262
+ Rio.monitoringProcess.stdout.on("data", data => {
263
+ // Retrieve event data
264
+ const evd = this.monData(data)
265
+ const that = Rio.monitored[parseInt(evd.gpio)]
266
+ if (typeof that.callback === "function") {
267
+ if (evd.edge === "1") {
268
+ if (that.edge === "rising" || that.edge === "both")
269
+ that.callback("rising", new Date())
270
+ } else {
271
+ if (that.edge === "falling" || that.edge === "both")
272
+ that.callback("falling", new Date())
273
+ }
274
+ }
275
+ })
276
+ Rio.monitoringProcess.stderr.on("data", data => {
277
+ trap("gpiomon stderr:", Buffer.from(data).toString())
278
+ })
279
+ } else {
280
+ log("no more gpio to monitor")
281
+ }
282
+ }
283
+
284
+ // ---------------------------------------------------------------
285
+ // PWM METHODS
286
+ // PLEASE NOTE - Commands to unlock a channel:
287
+ // pwm channel 0: echo 0 > /sys/class/pwm/pwmchip0/unexport
288
+ // pwm channel 0: echo 1 > /sys/class/pwm/pwmchip0/unexport
289
+ /** --------------------------------------------------------------
290
+ * @function pwmInit
291
+ * @description Start PWM modulation
292
+ * @param {Number} period in ns
293
+ * @param {Number} duty cycle in ns
294
+ * @param {Object} opt
295
+ */
296
+ pwmInit(period = 20000000, duty = 0, opt) {
297
+
298
+ // Stop on wrong type
299
+ if (this.type !== "pwm")
300
+ throw new Error("This GPIO is not configured as PWM")
301
+
302
+ // Stop disabled device
303
+ if (this.gpio === -1)
304
+ throw new Error("This instance is disabled")
305
+
306
+ // Set default options
307
+ const defopt = {
308
+ dutyMin: 0,
309
+ dutyMax: 1000000000, // 1s
310
+ }
311
+ opt = {...defopt, ...opt}
312
+
313
+ this.period = period
314
+ this.duty = duty
315
+ this.dutyMin = Math.max(0, opt.dutyMin)
316
+ this.dutyMax = Math.min(period, opt.dutyMax)
317
+
318
+ // Check period
319
+ if (this.period < 100 || this.period > 1000000000)
320
+ throw new Error("PWM period is out of range (100ns - 1s")
321
+
322
+ // Check duty cycle
323
+ if (duty < this.dutyMin)
324
+ throw new Error("PWM duty", duty, " is lower than duty min", this.dutyMin)
325
+ if (duty > this.dutyMax)
326
+ throw new Error("PWM duty", duty, " is upper than duty max", this.dutyMax)
327
+
328
+ log("channel", this.channel, "normalized PWM data (period, duty, duty min, duty max):", this.period, this.duty, this.dutyMin, this.dutyMax)
329
+
330
+ // Export channel, then set period, duty cycle and enable
331
+ writeFileSync(this.pathPwm + "export", this.channel)
332
+ this.exported = true
333
+
334
+ try {
335
+ wait(100)
336
+ writeFileSync(this.pathChannel + "period", String(this.period))
337
+ writeFileSync(this.pathChannel + "duty_cycle", String(this.duty))
338
+ writeFileSync(this.pathChannel + "enable", "1")
339
+ this.enabled = true
340
+
341
+ } catch (err) {
342
+ trap("pwm start error:", err)
343
+ this.pwmStop()
344
+ }
345
+ }
346
+
347
+ /** --------------------------------------------------------------
348
+ * @function pwmStop
349
+ * @description Stop PWM modulation
350
+ */
351
+ pwmStop() {
352
+
353
+ if (this.type !== "pwm")
354
+ throw new Error("This GPIO is not configured as PWM")
355
+
356
+ // Disable signal then unexport channel
357
+ writeFileSync(this.pathChannel + "enable", "0")
358
+ this.enabled = false
359
+ writeFileSync(this.pathPwm + "unexport", this.channel)
360
+ this.exported = true
361
+ }
362
+
363
+ /** --------------------------------------------------------------
364
+ * @function pwmDuty
365
+ * @description Change PWM duty cycle
366
+ * @parameter {Number} time
367
+ */
368
+ pwmDuty(time) {
369
+
370
+ // Not PWM
371
+ if (this.type !== "pwm")
372
+ throw new Error("This GPIO is not configured as PWM")
373
+
374
+ // Not initialized
375
+ if (!this.enabled || !this.exported) {
376
+ trap("PWM duty cycle cannot be update if port is not exported and enabled")
377
+ return
378
+ }
379
+
380
+ // Out of range
381
+ this.duty = time
382
+ if (typeof this.duty !== "number" || this.duty < this.dutyMin) {
383
+ this.duty = this.dutyMin
384
+ trap("duty cycle forced to", this.dutyMin)
385
+ }
386
+ if (typeof this.duty !== "number" || this.duty > this.dutyMax) {
387
+ this.duty = this.dutyMax
388
+ trap("duty cycle forced to", this.dutyMax)
389
+ }
390
+
391
+ writeFileSync(this.pathChannel + "duty_cycle", this.duty.toString())
392
+ }
393
+ }
394
+
395
+ // -------------------------------------------------------------------
396
+ // STATIC METHODS
397
+ /** ------------------------------------------------------------------
398
+ * @function Rio.config
399
+ * @description Return GPIO configuration
400
+ * @param {Number} gpio
401
+ * @return {String}
402
+ */
403
+ Rio.config = gpio => {
404
+ const a = exeShell("pinctrl get " + gpio).trim().split(/\s+/)
405
+ return a[a.length - 1].toLowerCase()
406
+ }
407
+ /** ------------------------------------------------------------------
408
+ * @function Rio.configs
409
+ * @description Return GPIO configuration for all supported GPIO
410
+ * @return {Object}
411
+ */
412
+ Rio.configs = () => {
413
+ let configs = {}
414
+ Rio.RPI_GPIOS.forEach(gpio => {
415
+ configs[gpio] = Rio.config(gpio)
416
+ })
417
+ log("gpio configs:", configs)
418
+ return configs
419
+ }
420
+
421
+ /** ------------------------------------------------------------------
422
+ * @function Rio.info
423
+ * @description Exec gpioinfo
424
+ * @return {Object}
425
+ */
426
+ Rio.info = () => {
427
+ return exeFile("gpioinfo", [Rio.RPI_CHIP])
428
+ }
429
+
430
+ /** ------------------------------------------------------------------
431
+ * @function Rio.detect
432
+ * @description Exec gpiodetect
433
+ * @return {Object}
434
+ */
435
+ Rio.detect = () => {
436
+ return exeFile("gpiodetect")
437
+ }
438
+
439
+ /** ------------------------------------------------------------------
440
+ * @function Rio.version
441
+ * @description Return libgpiod version
442
+ * @return {Object}
443
+ */
444
+ Rio.version = () => {
445
+ return exeFile("gpiodetect", ["-v"]).split("\n").shift().substring(22).trim()
446
+ }
447
+
448
+ /** ------------------------------------------------------------------
449
+ * @function Rio.stopMonitoring
450
+ * @description Stop monitoring of all inputs
451
+ */
452
+ Rio.stopMonitoring = () => {
453
+ Rio.monitoringProcess !== null ? Rio.monitoringProcess.kill("SIGTERM") : false
454
+ Rio.monitored = {}
455
+ Rio.monitoringProcess = null
456
+ log("gpio monitoring stopped")
457
+ }
458
+
459
+ /** ------------------------------------------------------------------
460
+ * @function Rio.cpuInfo
461
+ * @description Read '/proc/cpuinfo' and store it for further call
462
+ * @return {String}
463
+ */
464
+ Rio.cpuInfo = () => {
465
+ if (typeof Rio.cpuInfo.value === "undefined") {
466
+ try {
467
+ Rio.cpuInfo.value = readFileSync("/proc/cpuinfo", "utf8")
468
+ } catch (err) {
469
+ trap("catched error opening '/proc/cpuinfo:", err)
470
+ Rio.cpuInfo.value = ""
471
+ }
472
+ }
473
+ return Rio.cpuInfo.value
474
+ }
475
+
476
+ /** ------------------------------------------------------------------
477
+ * @function Rio.os
478
+ * @description Return OS ID and name
479
+ * @return {Object} {id: '12', name: 'Bookworm'}
480
+ */
481
+ Rio.os = () => {
482
+ const strOs = exeShell("cat /etc/os-release")
483
+ if (strOs.length > 0) {
484
+ const lines = strOs.split("\n")
485
+ const info = {}
486
+ lines.forEach(line => {
487
+ const [key, value] = line.split("=")
488
+ if (key && value) {
489
+ info[key] = value.replace(/"/g, "");
490
+ }
491
+ })
492
+ let res = {
493
+ id: info["VERSION_ID"],
494
+ name: info["VERSION_CODENAME"]
495
+ }
496
+ res.name = res.name.substring(0, 1).toUpperCase() + res.name.substring(1);
497
+ return res
498
+ }
499
+ }
500
+ /** ------------------------------------------------------------------
501
+ * @function Rio.isSystemSupported
502
+ * @description Check if all hardware and software requirements are
503
+ * satisfied to run the RPI-IO module
504
+ * @return {Boolean}
505
+ */
506
+ Rio.isSystemSupported = () => {
507
+
508
+ const info = Rio.cpuInfo()
509
+
510
+ // Test hardware
511
+ if (/Raspberry Pi/i.test(Rio.cpuInfo()) === false) {
512
+ trap("not a Raspberry Pi hardware")
513
+ return false
514
+ }
515
+ log("👍 Hardware is Raspberry Pi")
516
+
517
+ // Test model
518
+ const line = Rio.cpuInfo().split("\n").find(str => str.indexOf("Model") !== -1)
519
+ const model = line.split(":")[1].trim()
520
+ if (Rio.TESTED_MODELS.indexOf(model) === -1) {
521
+ trap("model not tested:", model)
522
+ return false
523
+ }
524
+ log("👍 ", model, "is supported")
525
+
526
+ // Test OS
527
+ const thisOs = Rio.os()
528
+ if (Rio.TESTED_OS.indexOf(thisOs.name) === -1) {
529
+ trap("OS not tested:", thisOs.name, thisOs.id)
530
+ return false
531
+ }
532
+ log("👍 ", thisOs.name, "OS is supported")
533
+
534
+ // Test libgpiod version x.y
535
+ const gpiodVersion = Rio.version().substring(0, 4)
536
+ const foundLib = Rio.TESTED_LIBGPIOD.find(testedVersion => {
537
+ return gpiodVersion === testedVersion.substring(0, 4)
538
+ })
539
+ if (!foundLib) {
540
+ trap("libgpiod version not tested:", gpiodVersion)
541
+ return false
542
+ }
543
+ log("👍 libgpiod version", gpiodVersion + ".x", "is supported")
544
+ log("👍 Systems requirements for rpi-io are met")
545
+ return true
546
+ }
547
+ /** ------------------------------------------------------------------
548
+ * @function Rio.log
549
+ */
550
+ Rio.log = cfgLog
551
+ // -------------------------------------------------------------------
552
+ // EoF
553
+ // -------------------------------------------------------------------
package/esm/utl.mjs ADDED
@@ -0,0 +1,100 @@
1
+ // -------------------------------------------------------------------
2
+ // RPi GPIO CONTROL - UTILITIES
3
+ // -------------------------------------------------------------------
4
+ import {log,trap} from "./dev.mjs"
5
+ import {execSync, execFileSync, spawnSync} from "child_process"
6
+
7
+ // -------------------------------------------------------------------
8
+ // JS UTILITIES
9
+ /** ------------------------------------------------------------------
10
+ * @function sleep
11
+ * @description Wait before continuing
12
+ * @param {Number} ms
13
+ */
14
+ export const sleep = ms => {
15
+ log("sleeping", ms, "ms")
16
+ return new Promise(resolve => setTimeout(resolve, ms));
17
+ }
18
+
19
+ /** ------------------------------------------------------------------
20
+ * @function ctrlC
21
+ * @description Intercept ctrl+c then exec callback
22
+ * @param {Function} callback
23
+ */
24
+ export const ctrlC = callback => {
25
+ process.on("SIGINT", () => {
26
+ log("ctrl+c pressed")
27
+ typeof callback === "function" ? callback() : false
28
+ process.exit(0)
29
+ })
30
+ }
31
+
32
+ // -------------------------------------------------------------------
33
+ // NODEJS UTILITIES
34
+ /** ------------------------------------------------------------------
35
+ * @function exeShell
36
+ * @description Async run command in Linux shell
37
+ * @param {String} cmd
38
+ * @return {String} stdout or ""
39
+ */
40
+ export const exeShell = cmd => {
41
+ try {
42
+ return execSync(cmd, {
43
+ stdio: "pipe",
44
+ encoding: "utf8",
45
+ })
46
+ } catch (err) {
47
+ if (err.code) {
48
+ // Spawning child process failed
49
+ trap("execFileSync error code", err.code)
50
+ } else {
51
+ // Child was spawned but exited with non-zero exit code
52
+ // Error contains any stdout and stderr from the child
53
+ const {stdout, stderr} = err
54
+ trap({stdout, stderr})
55
+ }
56
+ return ""
57
+ }
58
+ }
59
+
60
+ /** ------------------------------------------------------------------
61
+ * @function exeFile
62
+ * @description Async run executable file
63
+ * @param {String} file
64
+ * @param {Array} args
65
+ * @return {String} stdout or ""
66
+ */
67
+ export const exeFile = (file, args) => {
68
+ try {
69
+ return execFileSync(file, args, {
70
+ stdio: "pipe",
71
+ encoding: "utf8",
72
+ })
73
+ } catch (err) {
74
+ if (err.code) {
75
+ // Spawning child process failed
76
+ trap("execFileSync error code", err.code)
77
+ } else {
78
+ // Child was spawned but exited with non-zero exit code
79
+ // Error contains any stdout and stderr from the child
80
+ const {stdout, stderr} = err
81
+ trap({stdout, stderr})
82
+ }
83
+ return ""
84
+ }
85
+ }
86
+
87
+ /** ------------------------------------------------------------------
88
+ * @function wait
89
+ * @description Wait before continuing (sync blocking mode)
90
+ * @param {Number} ms
91
+ */
92
+ export const wait = ms => {
93
+ log("waiting", ms, "ms")
94
+ spawnSync("sleep", [ms / 1000])
95
+ }
96
+
97
+
98
+ // -------------------------------------------------------------------
99
+ // EoF
100
+ // -------------------------------------------------------------------
package/history.md ADDED
@@ -0,0 +1,8 @@
1
+ 1.0.0 - Oct 23 2025
2
+ ===================
3
+ Initial release: *Bookworm*, libgpiod 1.63
4
+
5
+ 1.1.0 - Oct 31 2025
6
+ ===================
7
+ * Added support libgpiod 2.2.1
8
+ * Tested with *Trixie*
package/package.json CHANGED
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "name": "rpi-io",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Nodejs module to control Raspbbery Pi GPIO",
5
5
  "keywords": [
6
6
  "raspberry",
7
7
  "gpio",
8
- "nodejs"
8
+ "nodejs",
9
+ "esm",
10
+ "pwm"
9
11
  ],
10
12
  "homepage": "https://github.com/gdorbes/rpi-io#readme",
11
13
  "bugs": {
@@ -15,11 +17,16 @@
15
17
  "type": "git",
16
18
  "url": "git+https://github.com/gdorbes/rpi-io.git"
17
19
  },
18
- "license": "ISC",
20
+ "license": "MIT",
19
21
  "author": "Guillaume DORBES",
20
22
  "type": "module",
21
- "main": "index.mjs",
23
+ "engines" : {
24
+ "npm" : ">=10.0.0",
25
+ "node" : ">=23.0.0"
26
+ },
27
+ "main": "./esm/rio.mjs",
28
+ "exports": "./esm/rio.mjs",
22
29
  "scripts": {
23
- "test": "test.js"
30
+ "test":"node ./scr/test.js"
24
31
  }
25
32
  }
package/scr/test.js ADDED
@@ -0,0 +1,162 @@
1
+ // -------------------------------------------------------------------
2
+ // RPi GPIO CONTROL TEST SCRIPT
3
+ // -------------------------------------------------------------------
4
+ import {log, trap, now} from "../esm/dev.mjs"
5
+ import {ctrlC} from "../esm/utl.mjs"
6
+ import {Rio} from "../esm/rio.mjs"
7
+
8
+ /** ------------------------------------------------------------------
9
+ * @function info
10
+ * @description Colored log for info messages Node.js only ~ CSS color #F50
11
+ * @param {String} cmd
12
+ * @param {String} txt
13
+ */
14
+ function info(cmd, txt) {
15
+ console.log(now(), "🔎", "\x1b[38;2;0;160;16m", cmd, "\x1b[0m", txt)
16
+ }
17
+
18
+ /** ------------------------------------------------------------------
19
+ * @function isNumber
20
+ * @description Test if param is number
21
+ * @param {String} str
22
+ * @return {Number}
23
+ */
24
+ function isNumber(str) {
25
+ const num = parseInt(str)
26
+ if (typeof num !== "number" || num !== num) {
27
+ trap("<gpio_number> is not a number or not defined")
28
+ return -1
29
+ } else {
30
+ return num
31
+ }
32
+ }
33
+
34
+ // -------------------------------------------------------------------
35
+ // TOP LEVEL TEST
36
+ // -------------------------------------------------------------------
37
+ (function () {
38
+ Rio.log(2)
39
+ ctrlC(Rio.stopMonitoring)
40
+
41
+ if (process.argv.length < 3) {
42
+ trap("Arguments are missing! Type 'help' to display list of commands.")
43
+ return
44
+ }
45
+ let gpio
46
+ switch (process.argv[2]) {
47
+ case "help":
48
+ info("help", "To display this list.")
49
+ info("detect", "To display gpio chipsets")
50
+ info("lines", "To display gpiochip0 lines")
51
+ info("version", "To display libgpiod version")
52
+ info("system", "To test if your system (hw,sw, mw) meets the tested requirements.")
53
+ info("config", "To display gpio system configuration.")
54
+ info("out <gpio_number>", "To define a gpio as 'out' and set it to 1 for 5s.")
55
+ info("in <gpio_number>", "To define a gpio as 'in' to display its value ")
56
+ info("monitor <gpio_number>", "To define a gpio as 'in' to monitor events until ctrl+c.")
57
+ info("pwm <gpio_number>", "Test SG90~like servo from 0° to 180° i.e. period=20ms, duty-cycle-range=0.5-2.5ms")
58
+ info("benchmark <gpio_out> <gpio_in>", "Displays elapsed time of 1000 write + read operations")
59
+ break
60
+ case "version":
61
+ log("libgpiod version:", Rio.version())
62
+ break
63
+ case "detect":
64
+ log("chipsets:", Rio.detect())
65
+ break
66
+ case "lines":
67
+ log("lines:", Rio.info())
68
+ break
69
+ case "system":
70
+ Rio.isSystemSupported()
71
+ break
72
+ case "config":
73
+ Rio.configs()
74
+ break
75
+ case "out":
76
+ gpio = isNumber(process.argv[3])
77
+ if (gpio > 0) {
78
+ const led = new Rio(gpio, "out")
79
+ led.set(1, 5000)
80
+ }
81
+ break
82
+ case "in":
83
+ gpio = isNumber(process.argv[3])
84
+ if (gpio > 0) {
85
+ const btn = new Rio(gpio, "in")
86
+ btn.get()
87
+ /* btn.monitor("both", (event, time) => {
88
+ log("event", event)
89
+ }) */
90
+ }
91
+ break
92
+ case "monitor":
93
+ gpio = isNumber(process.argv[3])
94
+ if (gpio > 0) {
95
+ const btn = new Rio(gpio, "in")
96
+ btn.monitor("both", (event) => {
97
+ log("event", event)
98
+ })
99
+ }
100
+ break
101
+ case "benchmark":
102
+ const gpioOut = isNumber(process.argv[3])
103
+ const gpioIn = isNumber(process.argv[4])
104
+ let len = 1000
105
+ if (gpioOut > 0) {
106
+ const led = new Rio(gpioOut, "out")
107
+ const start1 = new Date()
108
+ while (len--) {
109
+ led.write(1)
110
+ }
111
+ const stop1 = new Date()
112
+ const time1 = Math.abs(stop1 - start1)
113
+ log("Elapsed time for 1000 write ops:", time1, "ms")
114
+ }
115
+ len = 1000
116
+ if (gpioIn > 0) {
117
+ const btn = new Rio(gpioIn, "in")
118
+ const start2 = new Date()
119
+ while (len--) {
120
+ btn.read()
121
+ }
122
+ const stop2 = new Date()
123
+ const time2 = Math.abs(stop2 - start2)
124
+ log("Elapsed time for 1000 read ops:", time2, "ms")
125
+ }
126
+ break
127
+ case "pwm":
128
+ const servo = new Rio(isNumber(process.argv[3]), "pwm")
129
+
130
+ // 50hz, 0.5 ms
131
+ servo.pwmInit(20000000, 500000, {
132
+ dutyMin: 500000,
133
+ dutyMax: 2500000
134
+ })
135
+ log("servo position 0°")
136
+
137
+ // after 2s -> duty=1.5 ms
138
+ setTimeout(() => {
139
+ servo.pwmDuty(1500000)
140
+ log("servo position = 90°")
141
+ },2000)
142
+
143
+ // after 4s -> duty=1.5 ms
144
+ setTimeout(() => {
145
+ servo.pwmDuty(2500000)
146
+ log("servo position = 180°")
147
+ }, 4000)
148
+
149
+ // after 5s -> stop
150
+ setTimeout(() => {
151
+ servo.pwmStop()
152
+ log("servo stopped")
153
+ }, 5000)
154
+ break
155
+ default:
156
+ trap("Unknown command: Type 'test help' to display list of commands.")
157
+ }
158
+ })()
159
+
160
+ // -------------------------------------------------------------------
161
+ // EoF
162
+ // -------------------------------------------------------------------
package/rpi-io-1.0.0.tgz DELETED
Binary file