├── .gitignore ├── .jshintrc ├── .npmignore ├── .npmrc ├── .travis.yml ├── History.md ├── LICENSE ├── README.md ├── examples ├── accessible.js ├── blink-led-async.js ├── blink-led-promises.js ├── blink-led.js ├── debounce-button.js ├── light-switch.js ├── light-switch.png ├── mygpio-overlay.dts ├── run-examples └── wait-for-interrupt.js ├── integration-test ├── blink-led-promises.js ├── blink-led.js ├── change-configuration.js ├── configure-and-check-active-low-defaults.js ├── configure-and-check-active-low.js ├── configure-and-check-input.js ├── configure-and-check-output.js ├── debounce.js ├── dont-reconfigure-direction-part1.js ├── dont-reconfigure-direction-part2.js ├── export-many-times.js ├── high-low.js ├── many-interrupts.js ├── output-with-edge-bug.js ├── performance-async.js ├── performance-interrupt.js ├── performance-sync.js ├── run-performance-tests ├── run-tests ├── wait-for-interrupt.js └── wait-for-many-interrupts.js ├── onoff.d.ts ├── onoff.js ├── package.json ├── test ├── accessible.js ├── activeLow.js ├── constructor-fails.js ├── constructor.js ├── direction.js ├── edge.js ├── mocks │ ├── epoll.d.ts │ ├── epoll.js │ ├── linux.d.ts │ └── linux.js ├── read.js ├── readSync.js ├── setActiveLow.js ├── setDirection.js ├── setEdge.js ├── typedefinition.ts ├── utils │ └── test-promise.js ├── watch-callbacks.js ├── watch-listeners.js ├── write.js └── writeSync.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .nyc_output 3 | coverage 4 | node_modules 5 | 6 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "esversion": 6, 4 | "eqeqeq": true, 5 | "latedef": false, 6 | "noarg": true, 7 | "node": true, 8 | "quotmark": "single", 9 | "strict": "global", 10 | "undef": true, 11 | "varstmt": true, 12 | "globals": { 13 | "afterEach": false, 14 | "beforeEach": false, 15 | "describe": false, 16 | "it": false 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .jshintrc 3 | .nyc_output/ 4 | .travis.yml 5 | coverage/ 6 | node_modules/ 7 | test/ 8 | tsconfig.json 9 | 10 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: linux 2 | arch: 3 | - arm64 4 | - ppc64le 5 | - s390x 6 | language: node_js 7 | node_js: 8 | - "16" 9 | - "15" 10 | - "14" 11 | - "12" 12 | - "10" 13 | env: 14 | - CXX=g++-6 15 | addons: 16 | apt: 17 | sources: 18 | - ubuntu-toolchain-r-test 19 | packages: 20 | - g++-6 21 | script: 22 | - npm run lint 23 | - npm test 24 | after_success: 25 | - npm run codecov 26 | 27 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 6.0.3 - Apr 26 2021 2 | =================== 3 | 4 | * update performance numbers 5 | * add support for node.js 16 6 | * update dependencies 7 | 8 | 6.0.2 - Apr 07 2021 9 | =================== 10 | 11 | * fix accessible property (thank you [@mildsunrise](https://github.com/mildsunrise)) 12 | * update dependencies 13 | * add support for node.js 15 14 | 15 | 6.0.1 - Oct 10 2020 16 | =================== 17 | 18 | * switch from coveralls to codecov 19 | * update dependencies 20 | * drop support for node.js 8 21 | * drop support for node.js 13 22 | 23 | 6.0.0 - Apr 23 2020 24 | =================== 25 | 26 | * document the potential of EPERM errors when invoking write methods (fixes [#167](https://github.com/fivdi/onoff/issues/167)) 27 | * drop support for node.js 6, add support for node.js 14 28 | * avoid calling fs.writeFileSync with numeric data (fixes [#170](https://github.com/fivdi/onoff/issues/170)) 29 | * update dependencies 30 | * use g++-6 on travis 31 | 32 | 5.0.1 - Dec 24 2019 33 | =================== 34 | 35 | * document node 11 support 36 | * update benchmark results for pi 1, 2, 3 and 4 37 | 38 | 5.0.0 - Sep 22 2019 39 | =================== 40 | 41 | * drop support for node.js v4 42 | * update dependencies (epoll v3.0.0, ts-node v8.4.1, typescript v3.6.3) 43 | 44 | 4.1.4 - Sep 07 2019 45 | =================== 46 | 47 | * update dependencies (epoll v2.0.10, coveralls v3.0.6, mocha v6.2.0, typescript v3.6.2) 48 | 49 | 4.1.3 - Jul 05 2019 50 | =================== 51 | 52 | * avoid recursion in read and write methods (fixes [#156](https://github.com/fivdi/onoff/issues/156)) 53 | 54 | 4.1.2 - Jun 16 2019 55 | =================== 56 | 57 | * fix export 58 | * refactor promises (thank you [@pizzaisdavid](https://github.com/pizzaisdavid)) 59 | * update npm keywords 60 | * update dependencies 61 | 62 | 4.1.1 - Mar 14 2019 63 | =================== 64 | 65 | * simplify constructor 66 | * update dependencies (epoll v2.0.9, jshint v2.10.2, ts-node v8.0.3) 67 | 68 | 4.1.0 - Mar 03 2019 69 | =================== 70 | 71 | * add type definitions for TypeScript (thank you [@saenglert](https://github.com/saenglert)) 72 | 73 | 4.0.0 - Feb 28 2019 74 | =================== 75 | 76 | * added Promises to async read/write operations (thank you [@saenglert](https://github.com/saenglert)) - breaking change 77 | * update dependencies (mocha@6.0.2, nyc@13.3.0) 78 | 79 | 3.2.9 - Feb 24 2019 80 | =================== 81 | 82 | * post lcov to coveralls.io 83 | 84 | 3.2.8 - Feb 21 2019 85 | =================== 86 | 87 | * prevent EACCES errors from occurring while waiting for file access permission [#131](https://github.com/fivdi/onoff/issues/131) 88 | 89 | 3.2.7 - Feb 17 2019 90 | =================== 91 | 92 | * add code coverage to build 93 | * add more unit tests 94 | * document node 11 support 95 | * only reconfigure direction if needed [#128](https://github.com/fivdi/onoff/issues/128) 96 | 97 | 3.2.6 - Feb 09 2019 98 | =================== 99 | 100 | * add travis build 101 | 102 | 3.2.5 - Feb 09 2019 103 | =================== 104 | 105 | * lint with jshint 106 | 107 | 3.2.4 - Feb 09 2019 108 | =================== 109 | 110 | * add .npmignore 111 | 112 | 3.2.3 - Feb 09 2019 113 | =================== 114 | 115 | * update dependencies 116 | 117 | 3.2.2 - Sep 30 2018 118 | =================== 119 | 120 | * add unittests for reading and writing (thank you [@pizzaisdavid](https://github.com/pizzaisdavid)) 121 | * update dependencies (epoll v2.0.4, mocha v4.7.0) 122 | 123 | 3.2.1 - Jul 28 2018 124 | =================== 125 | 126 | * code style 127 | * update dependencies (epoll v2.0.3) 128 | 129 | 3.2.0 - Jul 24 2018 130 | =================== 131 | 132 | * add test to ensure HIGH and LOW have the expected values 133 | * add unittests (thank you [@pizzaisdavid](https://github.com/pizzaisdavid)) 134 | * set active_low before setting direction in constructor 135 | * add constructor reconfigureDirection option 136 | 137 | 3.1.0 - May 13 2018 138 | =================== 139 | 140 | * replace new Buffer with Buffer.from or Buffer.alloc 141 | * add accessebile property to Gpio class (thank you [@johntalton](https://github.com/johntalton)) 142 | * add HIGH and LOW properties to Gpio class (thank you [@johntalton](https://github.com/johntalton)) 143 | 144 | 3.0.2 - Apr 07 2018 145 | =================== 146 | 147 | * update dependencies (epoll v2.0.1) 148 | * improve performance tests 149 | 150 | 3.0.1 - Apr 01 2018 151 | =================== 152 | 153 | * create poller for both inputs and outputs 154 | * add test to verify that gpio direction can be changed 155 | 156 | 3.0.0 - Mar 31 2018 157 | =================== 158 | 159 | * add effective debouncing support 160 | * codebase modernized 161 | * remove link to outdated tutorial 162 | * remove undocumented options method 163 | 164 | 2.0.0 - Feb 26 2018 165 | =================== 166 | 167 | * update dependencies (epoll v2.0.0) 168 | * drop support for node.js v0.10, v0.12, v5 and v7 169 | 170 | 1.2.0 - Feb 11 2018 171 | =================== 172 | 173 | * ignore edge argument when instantiating a Gpio for an output 174 | 175 | 1.1.9 - Dec 24 2017 176 | =================== 177 | 178 | * document node 9 support 179 | * update BeagleBone performance numbers 180 | * many documentation improvements 181 | * update BeagleBone Black performance numbers 182 | * update dependencies 183 | 184 | 1.1.8 - Oct 15 2017 185 | =================== 186 | 187 | * update dependencies (epoll v1.0.0) 188 | 189 | 1.1.7 - Aug 26 2017 190 | =================== 191 | 192 | * only check permissions for edge file if edge specified [#77](https://github.com/fivdi/onoff/issues/77) 193 | 194 | 1.1.5 - Jul 30 2017 195 | =================== 196 | 197 | * wait until unprivileged file access allowed 198 | 199 | 1.1.4 - Jul 15 2017 200 | =================== 201 | 202 | * improve examples 203 | 204 | 1.1.3 - Jun 18 2017 205 | =================== 206 | * upgrade to epoll v0.1.22 207 | * document related packages 208 | 209 | 1.1.2 - Feb 12 2017 210 | =================== 211 | * documentation improved 212 | * upgrade to epoll v0.1.21 213 | 214 | 1.1.1 - Jun 05 2016 215 | =================== 216 | * avoid exceptions when cape_universal is enabled on the bbb [#50](https://github.com/fivdi/onoff/issues/50) 217 | 218 | 1.1.0 - May 04 2016 219 | =================== 220 | * activeLow option 221 | * documentation improved 222 | 223 | 1.0.4 - Jan 29 2016 224 | =================== 225 | * documentation improved 226 | * epoll v0.1.17 227 | 228 | 1.0.3 - Oct 10 2015 229 | =================== 230 | * documentation improved 231 | * epoll v0.1.16 232 | 233 | 1.0.2 - Feb 18 2015 234 | =================== 235 | * documentation improved 236 | 237 | 1.0.1 - Feb 15 2015 238 | =================== 239 | * refactored tests to avoid relying in interrupt generating outputs as linux 3.13 and above no longer supports them 240 | * new wiring for tests and examples 241 | * pullup and pulldown resistor configuration documented 242 | 243 | 1.0.0 - Jan 10 2015 244 | =================== 245 | * use strict mode 246 | * jslint improvements 247 | * updated dependencies: epoll 0.1.4 -> 0.1.10 248 | * new wiring for tests on pi 249 | * GPIO access without superuser privileges on Raspbian 250 | 251 | 0.3.2 - Apr 18 2014 252 | =================== 253 | * Documented BeagleBone Ångström prerequisites 254 | * Updated dependencies: epoll 0.1.2 -> 0.1.4 255 | 256 | 0.3.1 - Mar 22 2014 257 | =================== 258 | * Added setDirection functionality [#19](https://github.com/fivdi/onoff/pull/19) 259 | * Added setEdge functionality 260 | * Updated dependencies: epoll 0.1.0 -> 0.1.2 261 | 262 | 0.3.0 - Nov 18 2013 263 | =================== 264 | * Updated dependencies: epoll 0.0.8 -> 0.1.0 265 | * Removed persistentWatch option 266 | 267 | 0.2.3 - Oct 14 2013 268 | =================== 269 | 270 | * Use epoll 0.0.8 271 | * onoff now plays well with the quick2wire gpio-admin and the WiringPi gpio utilities on the Pi [#14](https://github.com/fivdi/onoff/issues/14) 272 | * Documentation improved 273 | * New test to monitor interrupt performance 274 | * New light switch example 275 | 276 | 0.2.2 - Oct 05 2013 277 | =================== 278 | 279 | * Use epoll 0.0.7 280 | * Removed timeout hack in many-interrupts test 281 | 282 | 0.2.1 - Sep 25 2013 283 | =================== 284 | 285 | * Use epoll 0.0.3 286 | * Improved five-inputs test 287 | 288 | 0.2.0 - Sep 22 2013 289 | =================== 290 | 291 | * Use epoll module for interrupt detection [#15](https://github.com/fivdi/onoff/issues/15) 292 | * 0.11.4+ compatability [#11](https://github.com/fivdi/onoff/issues/10) 293 | * One thread for watching all GPIOs rather than one thread per GPIO [#5](https://github.com/fivdi/onoff/issues/5) 294 | * Unwatch API added [#4](https://github.com/fivdi/onoff/issues/4) 295 | 296 | 0.1.7 - Sep 17 2013 297 | =================== 298 | 299 | * Remove OS limitations for installing [#12](https://github.com/fivdi/onoff/issues/12) 300 | 301 | 0.1.6 - July 15 2013 302 | =================== 303 | 304 | * Fixed typos 305 | * Documented how to watch five or more inputs 306 | 307 | 0.1.5 - May 26 2013 308 | =================== 309 | 310 | * Added test with five inputs 311 | 312 | 0.1.0 - Nov 11 2012 313 | =================== 314 | 315 | * Added Gpio objects 316 | * Removed functions, use Gpio objects instead 317 | * Performance improvements 318 | * Synchronous or asynchronous access to a GPIOs value 319 | * Allow applications to handle superuser issues 320 | 321 | 0.0.1 - Oct 28 2012 322 | =================== 323 | 324 | * Initial release 325 | 326 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | 'Software'), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://app.travis-ci.com/fivdi/onoff.svg?branch=master)](https://app.travis-ci.com/github/fivdi/onoff) 2 | [![codecov](https://codecov.io/gh/fivdi/onoff/branch/master/graph/badge.svg)](https://codecov.io/gh/fivdi/onoff) 3 | [![npm Version](http://img.shields.io/npm/v/onoff.svg)](https://www.npmjs.com/package/onoff) 4 | [![Downloads Per Month](http://img.shields.io/npm/dm/onoff.svg)](https://www.npmjs.com/package/onoff) 5 | [![Mentioned in Awesome Node.js](https://awesome.re/mentioned-badge.svg)](https://github.com/sindresorhus/awesome-nodejs#hardware) 6 | 7 | # onoff 8 | 9 | GPIO access and interrupt detection with **Node.js** on Linux boards like the 10 | Raspberry Pi or BeagleBone. 11 | 12 | onoff supports Node.js versions 10, 12, 14, 15 and 16. 13 | 14 | ## Contents 15 | 16 | * [Installation](#installation) 17 | * [Usage](#usage) 18 | * [LEDs and Buttons](#leds-and-buttons) 19 | * [Debouncing Buttons](#debouncing-buttons) 20 | * [Blink an LED Using the Synchronous API](#blink-an-led-using-the-synchronous-api) 21 | * [Blink an LED Using the Asynchronous API and Completion Callbacks](#blink-an-led-using-the-asynchronous-api-and-completion-callbacks) 22 | * [Blink an LED Using the Asynchronous API and Promises](#blink-an-led-using-the-asynchronous-api-and-promises) 23 | * [API](#api) 24 | * [How Does onoff Work?](#how-does-onoff-work) 25 | * [Configuring Pullup and Pulldown Resistors](#configuring-pullup-and-pulldown-resistors) 26 | * [Benchmarks](#benchmarks) 27 | * [Related Packages](#related-packages) 28 | * [Additional Information](#additional-information) 29 | 30 | ## Installation 31 | 32 | ``` 33 | npm install onoff 34 | ``` 35 | 36 | Note that although it's possible to install onoff on non-Linux systems the 37 | functionality offered by onoff is only available on Linux systems. 38 | 39 | ## Usage 40 | 41 | #### LEDs and Buttons 42 | Assume that there's an LED connected to GPIO17 and a momentary push button 43 | connected to GPIO4. 44 | 45 | 46 | 47 | When the button is pressed the LED should turn on, when it's released the LED 48 | should turn off. This can be achieved with the following code: 49 | 50 | ```js 51 | const Gpio = require('onoff').Gpio; 52 | const led = new Gpio(17, 'out'); 53 | const button = new Gpio(4, 'in', 'both'); 54 | 55 | button.watch((err, value) => led.writeSync(value)); 56 | ``` 57 | 58 | Here two Gpio objects are being created. One called led for the LED connected 59 | to GPIO17 which is an output, and one called button for the momentary push 60 | button connected to GPIO4 which is an input. In addition to specifying that 61 | the button is an input, the constructors optional third argument is used to 62 | specify that 'both' rising and falling interrupt edges should be configured 63 | for the button GPIO as both button presses and releases should be handled. 64 | 65 | After everything has been setup correctly, the buttons watch method is used to 66 | specify a callback function to execute every time the button is pressed or 67 | released. The value argument passed to the callback function represents the 68 | state of the button which will be 1 for pressed and 0 for released. This value 69 | is used by the callback to turn the LED on or off using its writeSync method. 70 | 71 | When the above program is running it can be terminated with ctrl-c. However, 72 | it doesn't free its resources. It also ignores the err argument passed to 73 | the callback. Here's a slightly modified variant of the program that handles 74 | ctrl-c gracefully and bails out on error. The resources used by the led and 75 | button Gpio objects are released by invoking their unexport method. 76 | 77 | ```js 78 | const Gpio = require('onoff').Gpio; 79 | const led = new Gpio(17, 'out'); 80 | const button = new Gpio(4, 'in', 'both'); 81 | 82 | button.watch((err, value) => { 83 | if (err) { 84 | throw err; 85 | } 86 | 87 | led.writeSync(value); 88 | }); 89 | 90 | process.on('SIGINT', _ => { 91 | led.unexport(); 92 | button.unexport(); 93 | }); 94 | ``` 95 | 96 | #### Debouncing Buttons 97 | When working with buttons there will often be button bounce issues which 98 | result in the hardware thinking that a button was pressed several times 99 | although it was only pressed once. onoff provides a software debouncing 100 | solution for resolving bounce issues. 101 | 102 | Assume again that there's an LED connected to GPIO17 and a momentary push 103 | button connected to GPIO4. 104 | 105 | When the button is pressed the LED should toggle its state. This is a typical 106 | example of a situation where there will be button bounce issues. The issue can 107 | be resolved by using the debounceTimeout option when creating the Gpio object 108 | for the button. In the below program the debounceTimeout is set to 10 109 | milliseconds. This delays invoking the watch callback for the button while the 110 | button is bouncing. The watch callback will not be invoked until the button 111 | stops bouncing and has been in a stable state for 10 milliseconds. 112 | 113 | ```js 114 | const Gpio = require('onoff').Gpio; 115 | const led = new Gpio(17, 'out'); 116 | const button = new Gpio(4, 'in', 'rising', {debounceTimeout: 10}); 117 | 118 | button.watch((err, value) => { 119 | if (err) { 120 | throw err; 121 | } 122 | 123 | led.writeSync(led.readSync() ^ 1); 124 | }); 125 | 126 | process.on('SIGINT', _ => { 127 | led.unexport(); 128 | button.unexport(); 129 | }); 130 | ``` 131 | 132 | #### Blink an LED Using the Synchronous API 133 | 134 | Blink an LED connected to GPIO17 for 5 seconds using the synchronous readSync 135 | and writeSync methods. 136 | 137 | ```js 138 | const Gpio = require('../onoff').Gpio; // Gpio class 139 | const led = new Gpio(17, 'out'); // Export GPIO17 as an output 140 | 141 | // Toggle the state of the LED connected to GPIO17 every 200ms 142 | const iv = setInterval(_ => led.writeSync(led.readSync() ^ 1), 200); 143 | 144 | // Stop blinking the LED after 5 seconds 145 | setTimeout(_ => { 146 | clearInterval(iv); // Stop blinking 147 | led.unexport(); // Unexport GPIO and free resources 148 | }, 5000); 149 | ``` 150 | 151 | #### Blink an LED Using the Asynchronous API and Completion Callbacks 152 | 153 | Blink an LED connected to GPIO17 for 5 seconds using the asynchronous read and 154 | write methods and completion callbacks. 155 | 156 | ```js 157 | const Gpio = require('../onoff').Gpio; // Gpio class 158 | const led = new Gpio(17, 'out'); // Export GPIO17 as an output 159 | let stopBlinking = false; 160 | 161 | // Toggle the state of the LED connected to GPIO17 every 200ms 162 | const blinkLed = _ => { 163 | if (stopBlinking) { 164 | return led.unexport(); 165 | } 166 | 167 | led.read((err, value) => { // Asynchronous read 168 | if (err) { 169 | throw err; 170 | } 171 | 172 | led.write(value ^ 1, err => { // Asynchronous write 173 | if (err) { 174 | throw err; 175 | } 176 | }); 177 | }); 178 | 179 | setTimeout(blinkLed, 200); 180 | }; 181 | 182 | blinkLed(); 183 | 184 | // Stop blinking the LED after 5 seconds 185 | setTimeout(_ => stopBlinking = true, 5000); 186 | ``` 187 | 188 | #### Blink an LED Using the Asynchronous API and Promises 189 | 190 | Blink an LED connected to GPIO17 for 5 seconds using the asynchronous read and 191 | write methods and Promises. 192 | 193 | ```js 194 | const Gpio = require('../onoff').Gpio; // Gpio class 195 | const led = new Gpio(17, 'out'); // Export GPIO17 as an output 196 | let stopBlinking = false; 197 | 198 | // Toggle the state of the LED connected to GPIO17 every 200ms 199 | const blinkLed = _ => { 200 | if (stopBlinking) { 201 | return led.unexport(); 202 | } 203 | 204 | led.read() 205 | .then(value => led.write(value ^ 1)) 206 | .then(_ => setTimeout(blinkLed, 200)) 207 | .catch(err => console.log(err)); 208 | }; 209 | 210 | blinkLed(); 211 | 212 | // Stop blinking the LED after 5 seconds 213 | setTimeout(_ => stopBlinking = true, 5000); 214 | ``` 215 | 216 | #### Check accessibility 217 | 218 | Sometimes it may be necessary to determine if the current system supports 219 | GPIOs programmatically and mock functionality if it doesn't. `Gpio.accessible` 220 | can be used to achieve this. 221 | 222 | ```js 223 | const Gpio = require('onoff').Gpio; 224 | 225 | const useLed = (led, value) => led.writeSync(value); 226 | 227 | let led; 228 | 229 | if (Gpio.accessible) { 230 | led = new Gpio(17, 'out'); 231 | // more real code here 232 | } else { 233 | led = { 234 | writeSync: value => { 235 | console.log('virtual led now uses value: ' + value); 236 | } 237 | }; 238 | } 239 | 240 | useLed(led, 1); 241 | ``` 242 | 243 | ## API 244 | 245 | ### Class Gpio 246 | 247 | * [Gpio(gpio, direction [, edge] [, options]) - Constructor](#gpiogpio-direction--edge--options) 248 | * [read([callback]) - Read GPIO value asynchronously](#readcallback) 249 | * [readSync() - Read GPIO value synchronously](#readsync) 250 | * [write(value[, callback]) - Write GPIO value asynchronously](#writevalue-callback) 251 | * [writeSync(value) - Write GPIO value synchronously](#writesyncvalue) 252 | * [watch(callback) - Watch for hardware interrupts on the GPIO](#watchcallback) 253 | * [unwatch([callback]) - Stop watching for hardware interrupts on the GPIO](#unwatchcallback) 254 | * [unwatchAll() - Remove all watchers for the GPIO](#unwatchall) 255 | * [direction() - Get GPIO direction](#direction) 256 | * [setDirection(direction) - Set GPIO direction](#setdirectiondirection) 257 | * [edge() - Get GPIO interrupt generating edge](#edge) 258 | * [setEdge(edge) - Set GPIO interrupt generating edge](#setedgeedge) 259 | * [activeLow() - Get GPIO activeLow setting](#activelow) 260 | * [setActiveLow(invert) - Set GPIO activeLow setting](#setactivelowinvert) 261 | * [unexport() - Reverse the effect of exporting the GPIO to userspace](#unexport) 262 | * [static accessible - Determine whether or not GPIO access is possible](#static-accessible) 263 | * [HIGH / LOW - Constants used when reading or writing a GPIO value](#static-high--low) 264 | 265 | ##### Gpio(gpio, direction [, edge] [, options]) 266 | - gpio - An unsigned integer specifying the GPIO number. 267 | - direction - A string specifying whether the GPIO should be configured as an 268 | input or output. The valid values are: 'in', 'out', 'high', and 'low'. If 'out' 269 | is specified the GPIO will be configured as an output and the value of the GPIO 270 | will be set to 0. 'high' and 'low' are variants of 'out' that configure the 271 | GPIO as an output with an initial level of 1 or 0 respectively. 272 | - [edge] - An optional string specifying the interrupt generating edge or 273 | edges for an input GPIO. The valid values are: 'none', 'rising', 'falling' or 274 | 'both'. The default value is 'none' indicating that the GPIO will not generate 275 | interrupts. Whether or not interrupts are supported by an input GPIO is GPIO 276 | specific. If interrupts are not supported by a GPIO the edge argument should 277 | not be specified. The edge argument is ignored for output GPIOs. 278 | - [options] - An optional options object. 279 | 280 | Configures the GPIO based on the passed arguments and returns a new Gpio 281 | object that can be used to access the GPIO. 282 | 283 | The following options are supported: 284 | - debounceTimeout - An unsigned integer specifying a millisecond delay. Delays 285 | invoking the watch callback for an interrupt generating input GPIO while the 286 | input is bouncing. The watch callback will not be invoked until the input 287 | stops bouncing and has been in a stable state for debounceTimeout 288 | milliseconds. Optional, if unspecified the input GPIO will not be debounced. 289 | - activeLow - A boolean value specifying whether the values read from or 290 | written to the GPIO should be inverted. The interrupt generating edge for the 291 | GPIO also follow this this setting. The valid values for activeLow are true 292 | and false. Setting activeLow to true inverts. Optional, the default value is 293 | false. 294 | - reconfigureDirection - A boolean value specifying whether the direction for 295 | the GPIO should be reconfigured even though the direction is already 296 | configured correctly. When an application starts, the direction of a GPIO used 297 | by that application may already be configured correctly, for example, from a 298 | previous run of the application. Reconfiguring the direction of that GPIO can 299 | result in unwanted side effects. For example, if a GPIO is already configured 300 | as an output and it is reconfigured as an output by passing 'out' to the 301 | constructor, the value of that output will be set to 0. In some applications 302 | this is not desirable and the value of the output should not be modified. The 303 | reconfigureDirection option can help here. If reconfigureDirection is set to 304 | false the direction of a GPIO that is already correctly configured will not be 305 | reconfigured. Optional, the default value is true. 306 | 307 | GPIOs on Linux are identified by unsigned integers. These are the numbers that 308 | should be passed to the onoff Gpio constructor when exporting GPIOs to 309 | userspace. For example, pin 11 on the Raspberry Pi expansion header 310 | corresponds to GPIO17 in Raspbian Linux. 17 is therefore the number to pass 311 | to the onoff Gpio constructor when using pin 11 on the expansion header. 312 | 313 | ##### read([callback]) 314 | - [callback] - An optional completion callback that gets two arguments (err, 315 | value), where err is reserved for an Error object and value is the number 0 316 | or 1 and represents the state of the GPIO. 317 | 318 | Read GPIO value asynchronously. If no completion callback is specified read 319 | returns a Promise which resolves to the value of the GPIO on success or rejects 320 | with an Error object on failure. 321 | 322 | Note that most systems support readback of GPIOs configured as outputs. The 323 | read method can therefore be invoked for any GPIO, irrespective of whether it 324 | was configured as an input or an output. The Raspberry Pi and BeagleBone are 325 | examples of such systems. 326 | 327 | ##### readSync() 328 | Read GPIO value synchronously. Returns the number 0 or 1 to represent the 329 | state of the GPIO. 330 | 331 | Note that most systems support readback of GPIOs configured as outputs. The 332 | readSync method can therefore be invoked for any GPIO, irrespective of whether 333 | it was configured as an input or an output. The Raspberry Pi and BeagleBone 334 | are examples of such systems. 335 | 336 | ##### write(value[, callback]) 337 | - value - The number 0 or 1. 338 | - [callback] - An optional completion callback that gets one argument (err), 339 | where err is reserved for an error object. 340 | 341 | Write GPIO value asynchronously. If no completion callback is specified write 342 | returns a Promise that resolves with no value on success or rejects with an 343 | Error object on failure. 344 | 345 | Note that on most systems invoking write for a GPIO configured as an input 346 | will result in an EPERM error indicating that the operation is not permitted. 347 | The Raspberry Pi and BeagleBone are examples of such systems. 348 | 349 | ##### writeSync(value) 350 | - value - The number 0 or 1. 351 | 352 | Write GPIO value synchronously. 353 | 354 | Note that on most systems invoking writeSync for a GPIO configured as an input 355 | will result in an EPERM error indicating that the operation is not permitted. 356 | The Raspberry Pi and BeagleBone are examples of such systems. 357 | 358 | ##### watch(callback) 359 | - callback - A callback that gets two arguments (err, value), where err is 360 | reserved for an error object and value is the number 0 or 1 and represents the 361 | state of the GPIO. The value can also be used to determine whether the 362 | interrupt occurred on a rising or falling edge. A value of 0 implies a falling 363 | edge interrupt and a value of 1 implies a rising edge interrupt. 364 | 365 | Watch for hardware interrupts on the GPIO. The edge argument that was passed 366 | to the constructor determines which hardware interrupts to watch for. 367 | 368 | ##### unwatch([callback]) 369 | - [callback] - The callback to remove. 370 | 371 | Stop watching for hardware interrupts on the GPIO. If callback is specified, 372 | only that particular callback is removed. Otherwise all callbacks are removed. 373 | 374 | ##### unwatchAll() 375 | Remove all hardware interrupt watchers for the GPIO. 376 | 377 | ##### direction() 378 | Returns the string 'in' or 'out' indicating whether the GPIO is an input or 379 | output. 380 | 381 | ##### setDirection(direction) 382 | - direction - A string specifying whether the GPIO should be configured as an 383 | input or output. The valid values are 'in', 'out', 'high', and 'low'. If 'out' 384 | is specified the GPIO will be configured as an output and the value of the GPIO 385 | will be set to 0. 'high' and 'low' are variants of 'out' that configure the 386 | GPIO as an output with an initial level of 1 or 0 respectively. 387 | 388 | Set GPIO direction. 389 | 390 | ##### edge() 391 | Returns the string 'none', 'falling', 'rising', or 'both' indicating the 392 | interrupt generating edge or edges for the GPIO. Whether or not interrupts are 393 | supported by an input GPIO is GPIO specific. If interrupts are not supported 394 | the edge method should not be used. Interrupts are not supported by output 395 | GPIOs. 396 | 397 | ##### setEdge(edge) 398 | - edge - A string specifying the interrupt generating edge or edges for an 399 | input GPIO. The valid values are: 'none', 'rising', 'falling' or 'both'. 400 | Whether or not interrupts are supported by an input GPIO is GPIO specific. If 401 | interrupts are not supported the setEdge method should not be used. Interrupts 402 | are not supported by output GPIOs. 403 | 404 | Set GPIO interrupt generating edge. 405 | 406 | ##### activeLow() 407 | Returns true or false indicating whether or not the values read from or written 408 | to the GPIO are inverted. 409 | 410 | ##### setActiveLow(invert) 411 | - invert - A boolean value specifying whether the values read from or written 412 | to the GPIO should be inverted. The interrupt generating edge for the GPIO also 413 | follow this this setting. The valid values for invert are true and false. 414 | Setting activeLow to true inverts. 415 | 416 | Set GPIO activeLow setting. 417 | 418 | ##### unexport() 419 | Reverse the effect of exporting the GPIO to userspace. A Gpio object should not 420 | be used after invoking its unexport method. 421 | 422 | ##### static accessible 423 | Determine whether or not GPIO access is possible. true if the current process 424 | has the permissions required to export GPIOs to userspace. false otherwise. 425 | Loosely speaking, if this property is true it should be possible for the 426 | current process to create Gpio objects. 427 | 428 | It is notable that while this property may be false indicating that the 429 | current process does not have the permissions required to export GPIOs to 430 | userspace, existing exported GPIOs may still be accessible. 431 | 432 | This property is useful for mocking functionality on computers used for 433 | development that do not provide access to GPIOs. 434 | 435 | This is a static property and should be accessed as `Gpio.accessible`. 436 | 437 | ##### static HIGH / LOW 438 | Constants used when reading or writing a GPIO value. Gpio.HIGH and Gpio.LOW 439 | can be used in place of the numeric constants 1 and 0. 440 | 441 | 442 | ## How Does onoff Work? 443 | 444 | Internally onoff uses sysfs files located at /sys/class/gpio to access GPIOs 445 | and the [epoll package](https://github.com/fivdi/epoll) to detect hardware 446 | interrupts. The Linux GPIO sysfs interface for userspace is documented 447 | [here](https://www.kernel.org/doc/Documentation/gpio/sysfs.txt). 448 | It's a relatively simple interface which can be used to ask the Linux kernel 449 | to export control of a GPIO to userspace. After control of a GPIO has been 450 | exported to userspace, the GPIO can be configured as an input or output. 451 | Thereafter, the state of an input can be read, and the state of an output can 452 | be written. Some systems will also allow the state of a output to be read. 453 | The GPIO sysfs interface can also be used for interrupt detection. onoff can 454 | detect several thousand interrupts per second on both the BeagleBone and the 455 | Raspberry Pi. 456 | 457 | 458 | ## Configuring Pullup and Pulldown Resistors 459 | 460 | As mentioned in section [How Does onoff Work](#how-does-onoff-work) the sysfs 461 | interface is used to access GPIOs. The sysfs interface doesn't offer support 462 | for configuring pullup and pulldown resistors on GPIOs. 463 | 464 | There are however many platform specific mechanisms for configuring pullup and 465 | pulldown resistors that are compatible with onoff. onoff itself doesn't use 466 | these mechanisms as one of the goals of onoff is to be platform independent. 467 | 468 | Here we'll take a look at two mechanisms available on the Raspberry Pi for 469 | configuring pullup and pulldown resistors. 470 | 471 | The first point to be aware of is that most GPIOs on a Raspberry Pi have 472 | either their pullup or pulldown resistor activated by default. The defaults 473 | can be seen in Table 6-31 on pages 102 and 103 of the 474 | [BCM2835 ARM Peripherals](http://www.farnell.com/datasheets/1521578.pdf) 475 | documentation. 476 | 477 | #### Using the gpio Command in /boot/config.txt 478 | 479 | On Raspbian 2018-04-18 or later the `gpio` configuration command can be used 480 | in `/boot/config.txt` to configure pullup and pulldown resistors. Further 481 | information is available at 482 | [New "gpio" config command](https://www.raspberrypi.org/forums/viewtopic.php?f=117&t=208748). 483 | 484 | #### Using Device Tree Overlays 485 | 486 | Device tree overlays can also be used to configure pullup and pulldown 487 | resistors. The Wiki page 488 | [Enabling Pullup and Pulldown Resistors on The Raspberry Pi](https://github.com/fivdi/onoff/wiki/Enabling-Pullup-and-Pulldown-Resistors-on-The-Raspberry-Pi) 489 | describes this mechanism in more detail. 490 | 491 | ## Benchmarks 492 | 493 | Three of the onoff tests are used to monitor performance. 494 | 495 | * performance-async.js - determine max. no. of write ops per seconds 496 | * performance-sync.js - determine max. no. of writeSync ops per second 497 | * performance-interrupt.js - determine max. no. of interrupts per second 498 | 499 | The results of these tests are shown in the following tables. 500 | 501 | **Raspberry Pi 4 B, 1.5GHz, Raspberry Pi OS (March 4th 2021, Buster 10.8)** 502 | 503 | node | onoff | kernel | write / sec | writeSync / sec | interrupts / sec 504 | :---: | :---: | :---: | ---: | ---: | ---: 505 | v16.0.0 | v6.0.3 | 5.10.17-v7l+ | 25124 | 280417 | 20240 506 | v15.14.0 | v6.0.3 | 5.10.17-v7l+ | 24055 | 271149 | 20488 507 | v14.16.1 | v6.0.3 | 5.10.17-v7l+ | 21669 | 254705 | 19703 508 | v12.22.1 | v6.0.3 | 5.10.17-v7l+ | 22618 | 318417 | 21122 509 | v10.24.1 | v6.0.3 | 5.10.17-v7l+ | 22405 | 329927 | 19583 510 | 511 | **Raspberry Pi 3 B, 1.2GHz, Raspbian Buster 10.1** 512 | 513 | node | onoff | kernel | write / sec | writeSync / sec | interrupts / sec 514 | :---: | :---: | :---: | ---: | ---: | ---: 515 | v12.14.0 | v5.0.0 | 4.19.75-v7l+ | 21670 | 207222 | 18328 516 | v10.18.0 | v5.0.0 | 4.19.75-v7l+ | 23661 | 225758 | 20741 517 | 518 | **Raspberry Pi 2 B, 900MHz, Raspbian Buster 10.1** 519 | 520 | node | onoff | kernel | write / sec | writeSync / sec | interrupts / sec 521 | :---: | :---: | :---: | ---: | ---: | ---: 522 | v12.14.0 | v5.0.0 | 4.19.75-v7l+ | 10769 | 113107 | 10373 523 | v10.18.0 | v5.0.0 | 4.19.75-v7l+ | 11843 | 129086 | 10536 524 | 525 | **Raspberry Pi 1 B, 700MHz, Raspbian Buster 10.1** 526 | 527 | node | onoff | kernel | write / sec | writeSync / sec | interrupts / sec 528 | :---: | :---: | :---: | ---: | ---: | ---: 529 | v12.14.0 | v5.0.0 | 4.19.75+ | 2316 | 26696 | 2112 530 | v10.18.0 | v5.0.0 | 4.19.75+ | 2613 | 33129 | 2225 531 | 532 | **BeagleBone Black, 1GHz, Debian Buster 10.2** 533 | 534 | node | onoff | kernel | write / sec | writeSync / sec | interrupts / sec 535 | :---: | :---: | :---: | ---: | ---: | ---: 536 | v12.14.0 | v5.0.0 | 4.19.79-ti-r30 | 6855 | 70535 | 5911 537 | v10.18.0 | v5.0.0 | 4.19.79-ti-r30 | 7564 | 79133 | 5920 538 | 539 | **BeagleBone, 720MHz, Debian Buster 10.2** 540 | 541 | node | onoff | kernel | write / sec | writeSync / sec | interrupts / sec 542 | :---: | :---: | :---: | ---: | ---: | ---: 543 | v12.14.0 | v5.0.0 | 4.19.79-ti-r30 | 5013 | 49741 | 4297 544 | v10.18.0 | v5.0.0 | 4.19.79-ti-r30 | 5400 | 57157 | 4371 545 | 546 | ## Related Packages 547 | 548 | Here are a few links to other hardware specific Node.js packages that may be 549 | of interest. 550 | 551 | * [pigpio](https://github.com/fivdi/pigpio) - Fast GPIO, PWM, servo control, state change notification and interrupt handling on the Raspberry Pi 552 | * [i2c-bus](https://github.com/fivdi/i2c-bus) - I2C serial bus access 553 | * [spi-device](https://github.com/fivdi/spi-device) - SPI serial bus access 554 | * [mcp-spi-adc](https://github.com/fivdi/mcp-spi-adc) - Analog to digital conversion with the MCP3002/4/8, MCP3202/4/8 and MCP3304 555 | 556 | ## Additional Information 557 | 558 | onoff was tested on the following platforms: 559 | 560 | - Raspberry Pi 1, 2, 3 and 4 561 | - Raspbian or Raspberry Pi OS 562 | - BeagleBone, BeagleBone Black and PocketBeagle 563 | - Debian 564 | 565 | The suitability of onoff for a particular Linux board is highly dependent on 566 | how GPIO interfaces are made available on that board. The 567 | [GPIO interfaces](https://www.kernel.org/doc/Documentation/gpio/) 568 | documentation describes GPIO access conventions rather than standards that must 569 | be followed so GPIO can vary from platform to platform. For example, onoff 570 | relies on sysfs files located at /sys/class/gpio being available. However, 571 | these sysfs files for userspace GPIO are optional and may not be available on a 572 | particular platform. 573 | 574 | -------------------------------------------------------------------------------- /examples/accessible.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Gpio = require('../onoff').Gpio; // Gpio class 4 | 5 | console.log('Gpio functionality accessible on this computer?', Gpio.accessible); 6 | -------------------------------------------------------------------------------- /examples/blink-led-async.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Gpio = require('../onoff').Gpio; // Gpio class 4 | const led = new Gpio(17, 'out'); // Export GPIO17 as an output 5 | let stopBlinking = false; 6 | 7 | // Toggle the state of the LED connected to GPIO17 every 200ms 8 | const blinkLed = _ => { 9 | if (stopBlinking) { 10 | return led.unexport(); 11 | } 12 | 13 | led.read((err, value) => { // Asynchronous read 14 | if (err) { 15 | throw err; 16 | } 17 | 18 | led.write(value ^ 1, err => { // Asynchronous write 19 | if (err) { 20 | throw err; 21 | } 22 | }); 23 | }); 24 | 25 | setTimeout(blinkLed, 200); 26 | }; 27 | 28 | blinkLed(); 29 | 30 | // Stop blinking the LED after 5 seconds 31 | setTimeout(_ => stopBlinking = true, 5000); 32 | 33 | -------------------------------------------------------------------------------- /examples/blink-led-promises.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Gpio = require('../onoff').Gpio; // Gpio class 4 | const led = new Gpio(17, 'out'); // Export GPIO17 as an output 5 | let stopBlinking = false; 6 | 7 | // Toggle the state of the LED connected to GPIO17 every 200ms 8 | const blinkLed = _ => { 9 | if (stopBlinking) { 10 | return led.unexport(); 11 | } 12 | 13 | led.read() 14 | .then(value => led.write(value ^ 1)) 15 | .then(_ => setTimeout(blinkLed, 200)) 16 | .catch(err => console.log(err)); 17 | }; 18 | 19 | blinkLed(); 20 | 21 | // Stop blinking the LED after 5 seconds 22 | setTimeout(_ => stopBlinking = true, 5000); 23 | 24 | -------------------------------------------------------------------------------- /examples/blink-led.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Gpio = require('../onoff').Gpio; // Gpio class 4 | const led = new Gpio(17, 'out'); // Export GPIO17 as an output 5 | 6 | // Toggle the state of the LED connected to GPIO17 every 200ms 7 | const iv = setInterval(_ => led.writeSync(led.readSync() ^ 1), 200); 8 | 9 | // Stop blinking the LED after 5 seconds 10 | setTimeout(_ => { 11 | clearInterval(iv); // Stop blinking 12 | led.unexport(); // Unexport GPIO and free resources 13 | }, 5000); 14 | 15 | -------------------------------------------------------------------------------- /examples/debounce-button.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Gpio = require('../onoff').Gpio; 4 | const led = new Gpio(17, 'out'); 5 | const button = new Gpio(4, 'in', 'rising', {debounceTimeout: 10}); 6 | 7 | button.watch((err, value) => { 8 | if (err) { 9 | throw err; 10 | } 11 | 12 | led.writeSync(led.readSync() ^ 1); 13 | }); 14 | 15 | process.on('SIGINT', _ => { 16 | led.unexport(); 17 | button.unexport(); 18 | }); 19 | 20 | -------------------------------------------------------------------------------- /examples/light-switch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Gpio = require('../onoff').Gpio; 4 | const led = new Gpio(17, 'out'); 5 | const button = new Gpio(4, 'in', 'both'); 6 | 7 | button.watch((err, value) => { 8 | if (err) { 9 | throw err; 10 | } 11 | 12 | led.writeSync(value); 13 | }); 14 | 15 | process.on('SIGINT', _ => { 16 | led.unexport(); 17 | button.unexport(); 18 | }); 19 | 20 | -------------------------------------------------------------------------------- /examples/light-switch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fivdi/onoff/813da60dd1f3a842a29a8c630243d4f5b7523cc0/examples/light-switch.png -------------------------------------------------------------------------------- /examples/mygpio-overlay.dts: -------------------------------------------------------------------------------- 1 | /dts-v1/; 2 | /plugin/; 3 | 4 | / { 5 | compatible = "brcm,bcm2708"; 6 | 7 | fragment@0 { 8 | target = <&gpio>; 9 | __overlay__ { 10 | pinctrl-names = "default"; 11 | pinctrl-0 = <&my_pins>; 12 | 13 | my_pins: my_pins { 14 | brcm,pins = <7 8 9>; /* gpio no. */ 15 | brcm,function = <0 0 0>; /* 0:in, 1:out */ 16 | brcm,pull = <1 1 2>; /* 2:up 1:down 0:none */ 17 | }; 18 | }; 19 | }; 20 | }; 21 | 22 | -------------------------------------------------------------------------------- /examples/run-examples: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | node blink-led 3 | node blink-led-async 4 | node wait-for-interrupt 5 | node light-switch 6 | 7 | -------------------------------------------------------------------------------- /examples/wait-for-interrupt.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Gpio = require('../onoff').Gpio; // Gpio class 4 | 5 | // Export GPIO4 as an interrupt generating input with a debounceTimeout of 10 6 | // milliseconds 7 | const button = new Gpio(4, 'in', 'rising', {debounceTimeout: 10}); 8 | 9 | console.log('Please press the button on GPIO4...'); 10 | 11 | // The callback passed to watch will be invoked when the button connected to 12 | // GPIO4 is pressed 13 | button.watch((err, value) => { 14 | if (err) { 15 | throw err; 16 | } 17 | 18 | console.log('Button pressed!, its value was ' + value); 19 | 20 | button.unexport(); // Unexport GPIO and free resources 21 | }); 22 | 23 | -------------------------------------------------------------------------------- /integration-test/blink-led-promises.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Gpio = require('../onoff').Gpio; 4 | const led = new Gpio(17, 'out'); 5 | let stopBlinking = false; 6 | 7 | const blinkLed = _ => { 8 | if (stopBlinking) { 9 | led.unexport(); 10 | console.log('ok - ' + __filename); 11 | return; 12 | } 13 | 14 | led.read() 15 | .then(value => led.write(value ^ 1)) 16 | .then(_ => setTimeout(blinkLed, 50)) 17 | .catch(err => { 18 | console.log(err); 19 | }); 20 | }; 21 | 22 | blinkLed(); 23 | 24 | setTimeout(_ => stopBlinking = true, 2000); 25 | 26 | 27 | -------------------------------------------------------------------------------- /integration-test/blink-led.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Gpio = require('../onoff').Gpio; 4 | const led = new Gpio(17, 'out'); 5 | 6 | const iv = setInterval(_ => led.writeSync(led.readSync() ^ 1), 100); 7 | 8 | setTimeout(_ => { 9 | clearInterval(iv); 10 | 11 | led.writeSync(0); 12 | led.unexport(); 13 | 14 | console.log('ok - ' + __filename); 15 | }, 2000); 16 | 17 | -------------------------------------------------------------------------------- /integration-test/change-configuration.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const Gpio = require('../onoff').Gpio; 5 | 6 | let output = new Gpio(8, 'out'); 7 | let input = new Gpio(7, 'in', 'both'); 8 | 9 | const watchWithSecondConfiguration = _ => { 10 | input.watch((err, value) => { 11 | assert(!err, 'error during interrupt detection'); 12 | assert(value === 1, 'expected interrupt on rising edge'); 13 | 14 | setTimeout(_ => { 15 | input.unexport(); 16 | output.unexport(); 17 | 18 | console.log('ok - ' + __filename); 19 | }, 10); 20 | }); 21 | 22 | output.writeSync(1); 23 | }; 24 | 25 | const changeConfiguration = _ => { 26 | input.unwatchAll(); 27 | 28 | let temp = output; 29 | temp.setDirection('in'); 30 | output = input; 31 | input = temp; 32 | 33 | output.setEdge('none'); 34 | output.setDirection('out'); 35 | output.writeSync(0); 36 | assert(output.direction() === 'out', 'expected direction to be out'); 37 | assert(output.edge() === 'none', 'expected edge to be none'); 38 | assert(output.readSync() === 0, 'expected value to be 0'); 39 | 40 | input.setEdge('rising'); 41 | assert(input.direction() === 'in', 'expected direction to be in'); 42 | assert(input.edge() === 'rising', 'expected edge to be rising'); 43 | assert(input.readSync() === 0, 'expected value to be 0'); 44 | 45 | watchWithSecondConfiguration(); 46 | }; 47 | 48 | const watchWithFirstConfiguration = _ => { 49 | input.watch((err, value) => { 50 | assert(!err, 'error during interrupt detection'); 51 | assert(value === 1, 'expected interrupt on rising edge'); 52 | 53 | setTimeout(changeConfiguration, 10); 54 | }); 55 | 56 | output.writeSync(1); 57 | }; 58 | 59 | watchWithFirstConfiguration(); 60 | -------------------------------------------------------------------------------- /integration-test/configure-and-check-active-low-defaults.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * In this test, GPIO7 is connected to one end of a 1kΩ current limiting 5 | * resistor and GPIO8 is connected to the other end of the resistor. 6 | */ 7 | const Gpio = require('../onoff').Gpio; 8 | const assert = require('assert'); 9 | let input = new Gpio(7, 'in'); 10 | let output = new Gpio(8, 'low', {activeLow: true}); 11 | 12 | assert(input.readSync() === 0); 13 | assert(output.readSync() === 1); 14 | 15 | input.unexport(); 16 | output.unexport(); 17 | 18 | // 19 | 20 | input = new Gpio(7, 'in'); 21 | output = new Gpio(8, 'low', {activeLow: false}); 22 | 23 | assert(input.readSync() === 0); 24 | assert(output.readSync() === 0); 25 | 26 | input.unexport(); 27 | output.unexport(); 28 | 29 | // 30 | 31 | input = new Gpio(7, 'in'); 32 | output = new Gpio(8, 'high', {activeLow: true}); 33 | 34 | assert(input.readSync() === 1); 35 | assert(output.readSync() === 0); 36 | 37 | input.unexport(); 38 | output.unexport(); 39 | 40 | // 41 | 42 | input = new Gpio(7, 'in'); 43 | output = new Gpio(8, 'high', {activeLow: false}); 44 | 45 | assert(input.readSync() === 1); 46 | assert(output.readSync() === 1); 47 | 48 | input.unexport(); 49 | output.unexport(); 50 | 51 | // 52 | 53 | input = new Gpio(7, 'in'); 54 | output = new Gpio(8, 'out', {activeLow: true}); 55 | 56 | assert(input.readSync() === 0); 57 | assert(output.readSync() === 1); 58 | 59 | input.unexport(); 60 | output.unexport(); 61 | 62 | // 63 | 64 | input = new Gpio(7, 'in'); 65 | output = new Gpio(8, 'out', {activeLow: false}); 66 | 67 | assert(input.readSync() === 0); 68 | assert(output.readSync() === 0); 69 | 70 | input.unexport(); 71 | output.unexport(); 72 | 73 | console.log('ok - ' + __filename); 74 | 75 | -------------------------------------------------------------------------------- /integration-test/configure-and-check-active-low.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * In this test, GPIO7 is connected to one end of a 1kΩ current limiting 5 | * resistor and GPIO8 is connected to the other end of the resistor. 6 | */ 7 | const Gpio = require('../onoff').Gpio; 8 | const assert = require('assert'); 9 | const input = new Gpio(7, 'in'); 10 | const output = new Gpio(8, 'out', {activeLow: true}); 11 | 12 | assert(input.activeLow() === false); 13 | assert(output.activeLow() === true); 14 | 15 | assert(input.readSync() === 0); 16 | assert(output.readSync() === 1); 17 | 18 | output.writeSync(0); 19 | assert(input.readSync() === 1); 20 | assert(output.readSync() === 0); 21 | output.writeSync(1); 22 | assert(input.readSync() === 0); 23 | assert(output.readSync() === 1); 24 | 25 | output.setActiveLow(false); 26 | assert(input.activeLow() === false); 27 | assert(output.activeLow() === false); 28 | output.writeSync(0); 29 | assert(input.readSync() === 0); 30 | assert(output.readSync() === 0); 31 | output.writeSync(1); 32 | assert(input.readSync() === 1); 33 | assert(output.readSync() === 1); 34 | 35 | input.setActiveLow(true); 36 | assert(input.activeLow() === true); 37 | assert(output.activeLow() === false); 38 | output.writeSync(0); 39 | assert(input.readSync() === 1); 40 | assert(output.readSync() === 0); 41 | output.writeSync(1); 42 | assert(input.readSync() === 0); 43 | assert(output.readSync() === 1); 44 | 45 | input.unexport(); 46 | output.unexport(); 47 | 48 | console.log('ok - ' + __filename); 49 | 50 | -------------------------------------------------------------------------------- /integration-test/configure-and-check-input.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Gpio = require('../onoff').Gpio; 4 | const assert = require('assert'); 5 | const input = new Gpio(4, 'in', 'rising'); 6 | 7 | assert(input.direction() === 'in'); 8 | assert(input.edge() === 'rising'); 9 | 10 | input.unexport(); 11 | 12 | console.log('ok - ' + __filename); 13 | 14 | -------------------------------------------------------------------------------- /integration-test/configure-and-check-output.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Gpio = require('../onoff').Gpio; 4 | const assert = require('assert'); 5 | const output = new Gpio(17, 'out'); 6 | 7 | assert(output.direction() === 'out'); 8 | 9 | output.writeSync(1); 10 | assert(output.readSync() === 1); 11 | 12 | output.writeSync(0); 13 | assert(output.readSync() === 0); 14 | 15 | output.write(1, err => { 16 | if (err) { 17 | throw err; 18 | } 19 | 20 | output.read((err, value) => { 21 | if (err) { 22 | throw err; 23 | } 24 | 25 | assert(value === 1); 26 | 27 | output.writeSync(0); 28 | assert(output.readSync() === 0); 29 | 30 | output.unexport(); 31 | 32 | console.log('ok - ' + __filename); 33 | }); 34 | }); 35 | 36 | -------------------------------------------------------------------------------- /integration-test/debounce.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const Gpio = require('../onoff').Gpio; 5 | const output = new Gpio(8, 'out'); 6 | const button = new Gpio(7, 'in', 'both', {debounceTimeout: 10}); 7 | 8 | let buttonPressedCount = 0; 9 | let buttonReleasedCount = 0; 10 | 11 | const simulateToggleButtonStateWithBounce = cb => { 12 | let toggleCount = 0; 13 | 14 | const iv = setInterval(_ => { 15 | if (toggleCount === 19) { 16 | clearInterval(iv); 17 | return cb(); 18 | } 19 | 20 | output.writeSync(output.readSync() ^ 1); 21 | toggleCount += 1; 22 | }, 2); 23 | }; 24 | 25 | const simulatePressAndReleaseButtonWithBounce = _ => { 26 | simulateToggleButtonStateWithBounce(_ => { 27 | setTimeout(_ => { 28 | simulateToggleButtonStateWithBounce(_ => { 29 | setTimeout(_ => { 30 | assert(buttonPressedCount === 1); 31 | assert(buttonReleasedCount === 1); 32 | 33 | button.unexport(); 34 | output.unexport(); 35 | 36 | console.log('ok - ' + __filename); 37 | }, 20); 38 | }); 39 | }, 50); 40 | }); 41 | }; 42 | 43 | button.watch((err, value) => { 44 | if (err) { 45 | throw err; 46 | } 47 | 48 | if (value === 1) { 49 | buttonPressedCount += 1; 50 | } else if (value === 0) { 51 | buttonReleasedCount += 1; 52 | } 53 | }); 54 | 55 | simulatePressAndReleaseButtonWithBounce(); 56 | 57 | -------------------------------------------------------------------------------- /integration-test/dont-reconfigure-direction-part1.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * In this test, GPIO7 is connected to one end of a 1kΩ current limiting 5 | * resistor and GPIO8 is connected to the other end of the resistor. 6 | * 7 | * This test is part 1 of a two part test. 8 | * For part 2 see dont-reconfigure-direction-part2.js. 9 | * 10 | * Part 1 sets the ouput to 1 and expects to read 1 on the input. Part 1 11 | * doesn't unexport the GPIOs so that part 2 can can ensure that a Gpio output 12 | * object can be constructed without modifying the value of the output. Part 2 13 | * also expects to read one on the input. This is achieved by using the 14 | * reconfigureDirection option. 15 | */ 16 | const Gpio = require('../onoff').Gpio; 17 | const assert = require('assert'); 18 | const input = new Gpio(7, 'in'); 19 | const output = new Gpio(8, 'out'); 20 | 21 | output.writeSync(1); 22 | assert(input.readSync() === 1); 23 | assert(output.readSync() === 1); 24 | 25 | console.log('ok - ' + __filename); 26 | 27 | -------------------------------------------------------------------------------- /integration-test/dont-reconfigure-direction-part2.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * In this test, GPIO7 is connected to one end of a 1kΩ current limiting 5 | * resistor and GPIO8 is connected to the other end of the resistor. 6 | * 7 | * This test is part 2 of a two part test. 8 | * For part 1 see dont-reconfigure-direction-part1.js. 9 | * 10 | * Part 1 sets the ouput to 1 and expects to read 1 on the input. Part 1 11 | * doesn't unexport the GPIOs so that part 2 can can ensure that a Gpio output 12 | * object can be constructed without modifying the value of the output. Part 2 13 | * also expects to read one on the input. This is achieved by using the 14 | * reconfigureDirection option. 15 | */ 16 | const Gpio = require('../onoff').Gpio; 17 | const assert = require('assert'); 18 | const input = new Gpio(7, 'in'); 19 | const output = new Gpio(8, 'out', {reconfigureDirection: false}); 20 | 21 | assert(input.readSync() === 1); 22 | assert(output.readSync() === 1); 23 | 24 | input.unexport(); 25 | output.unexport(); 26 | 27 | console.log('ok - ' + __filename); 28 | 29 | -------------------------------------------------------------------------------- /integration-test/export-many-times.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Gpio = require('../onoff').Gpio; 4 | 5 | for (let i = 1; i <= 1000000; i += 1) { 6 | const led = new Gpio(17, 'out'); 7 | led.writeSync(led.readSync() ^ 1); 8 | led.unexport(); 9 | if (i % 10 === 0) { 10 | console.log(i); 11 | } 12 | } 13 | 14 | console.log('ok - ' + __filename); 15 | 16 | -------------------------------------------------------------------------------- /integration-test/high-low.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const Gpio = require('../onoff').Gpio; 5 | 6 | assert(Gpio.HIGH === 1, 'expected Gpio.HIGH to be 1'); 7 | assert(Gpio.LOW === 0, 'expected Gpio.LOW to be 0'); 8 | 9 | console.log('ok - ' + __filename); 10 | 11 | -------------------------------------------------------------------------------- /integration-test/many-interrupts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const Gpio = require('../onoff').Gpio; 5 | const input = new Gpio(7, 'in', 'both'); 6 | const output = new Gpio(8, 'out'); 7 | 8 | let toggleCount = 0; 9 | let falling = 0; 10 | let rising = 0; 11 | 12 | const toggleOutput = _ => { 13 | output.writeSync(output.readSync() ^ 1); 14 | toggleCount += 1; 15 | }; 16 | 17 | const interrupt = (err, value) => { 18 | if (err) { 19 | throw err; 20 | } 21 | 22 | if (value === 1) { 23 | rising += 1; 24 | } else if (value === 0) { 25 | falling += 1; 26 | } 27 | 28 | assert(output.readSync() === value); 29 | 30 | if (rising + falling < 2000) { 31 | toggleOutput(); 32 | } else { 33 | assert(toggleCount === 2000); 34 | assert(rising === falling); 35 | assert(rising + falling === toggleCount); 36 | 37 | input.unexport(); 38 | output.writeSync(0); 39 | output.unexport(); 40 | 41 | console.log('ok - ' + __filename); 42 | } 43 | }; 44 | 45 | input.watch(interrupt); 46 | toggleOutput(); 47 | 48 | -------------------------------------------------------------------------------- /integration-test/output-with-edge-bug.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Test for https://github.com/fivdi/onoff/issues/87 4 | // 5 | // If a Gpio is instantiated for an output GPIO and the edge parameter is 6 | // specified then the edge parameter should be ignored. Attempting to write 7 | // the sysfs edge file for an output GPIO results in an 8 | // "EIO: i/o error, write" 9 | 10 | const Gpio = require('../onoff').Gpio; 11 | const assert = require('assert'); 12 | 13 | const ensureGpio17Unexported = cb => { 14 | let led = new Gpio(17, 'out'); 15 | 16 | led.unexport(); 17 | 18 | setTimeout(_ => cb(), 100); 19 | }; 20 | 21 | ensureGpio17Unexported(_ => { 22 | let led; 23 | 24 | assert.doesNotThrow( 25 | _ => led = new Gpio(17, 'out', 'both'), 26 | 'can\'t instantiate a Gpio for an output with edge option specified' 27 | ); 28 | 29 | led.unexport(); 30 | 31 | console.log('ok - ' + __filename); 32 | }); 33 | 34 | -------------------------------------------------------------------------------- /integration-test/performance-async.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Gpio = require('../onoff').Gpio; 4 | 5 | const pulseLed = (led, pulseCount, cb) => { 6 | let time = process.hrtime(); 7 | 8 | const loop = count => { 9 | if (count === 0) { 10 | time = process.hrtime(time); 11 | const writesPerSecond = pulseCount * 2 / (time[0] + time[1] / 1E9); 12 | return cb(null, writesPerSecond); 13 | } 14 | 15 | led.write(1, err => { 16 | if (err) { 17 | return cb(err); 18 | } 19 | 20 | led.write(0, err => { 21 | if (err) { 22 | return cb(err); 23 | } 24 | 25 | loop(count - 1); 26 | }); 27 | }); 28 | }; 29 | 30 | loop(pulseCount); 31 | }; 32 | 33 | const asyncWritesPerSecond = cb => { 34 | const led = new Gpio(17, 'out'); 35 | let writes = 0; 36 | 37 | const loop = count => { 38 | if (count === 0) { 39 | led.unexport(); 40 | return cb(null, writes / 10); 41 | } 42 | 43 | pulseLed(led, 10000, (err, writesPerSecond) => { 44 | if (err) { 45 | return cb(err); 46 | } 47 | 48 | writes += writesPerSecond; 49 | 50 | loop(count - 1); 51 | }); 52 | }; 53 | 54 | // Do a dry run first to get the runtime primed 55 | pulseLed(led, 5000, (err, writesPerSecond) => { 56 | if (err) { 57 | return cb(err); 58 | } 59 | loop(10); 60 | }); 61 | }; 62 | 63 | asyncWritesPerSecond((err, averageWritesPerSecond) => { 64 | if (err) { 65 | throw err; 66 | } 67 | 68 | console.log('ok - ' + __filename); 69 | console.log( 70 | ' ' + Math.floor(averageWritesPerSecond) + ' async writes per second' 71 | ); 72 | }); 73 | 74 | -------------------------------------------------------------------------------- /integration-test/performance-interrupt.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * In this test, GPIO7 is connected to one end of a 1kΩ current limiting 5 | * resistor and GPIO8 is connected to the other end of the resistor. GPIO7 is 6 | * an interrupt generating input and GPIO8 is an output. By toggling the state 7 | * of the output an interrupt is generated. The output is toggled as often as 8 | * possible to determine the maximum rate at which interrupts can be handled. 9 | */ 10 | const Gpio = require('../onoff').Gpio; 11 | const input = new Gpio(7, 'in', 'both'); 12 | const output = new Gpio(8, 'out'); 13 | 14 | let irqCount = 0; 15 | let iv; 16 | 17 | // Exit handler 18 | const exit = _ => { 19 | input.unexport(); 20 | output.unexport(); 21 | 22 | clearInterval(iv); 23 | }; 24 | process.on('SIGINT', exit); 25 | 26 | // Interrupt handler 27 | input.watch((err, value) => { 28 | if (err) { 29 | exit(); 30 | } 31 | 32 | irqCount += 1; 33 | 34 | // Trigger next interrupt by toggling output. 35 | output.writeSync(value === 0 ? 1 : 0); 36 | }); 37 | 38 | // Print number of interrupts once a second. 39 | iv = setInterval(_ => { 40 | console.log(irqCount); 41 | irqCount = 0; 42 | }, 1000); 43 | 44 | // Trigger first interrupt by toggling output. 45 | output.writeSync(output.readSync() === 0 ? 1 : 0); 46 | 47 | -------------------------------------------------------------------------------- /integration-test/performance-sync.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Gpio = require('../onoff').Gpio; 4 | 5 | const pulseLed = (led, pulseCount) => { 6 | let time = process.hrtime(); 7 | 8 | for (let i = 0; i !== pulseCount; i += 1) { 9 | led.writeSync(1); 10 | led.writeSync(0); 11 | } 12 | 13 | time = process.hrtime(time); 14 | 15 | const writesPerSecond = pulseCount * 2 / (time[0] + time[1] / 1E9); 16 | 17 | return writesPerSecond; 18 | }; 19 | 20 | const syncWritesPerSecond = _ => { 21 | const led = new Gpio(17, 'out'); 22 | let writes = 0; 23 | 24 | // Do a dry run first to get the runtime primed 25 | pulseLed(led, 50000); 26 | 27 | for (let i = 0; i !== 10; i += 1) { 28 | writes += pulseLed(led, 100000); 29 | } 30 | 31 | led.unexport(); 32 | 33 | return writes / 10; 34 | }; 35 | 36 | console.log('ok - ' + __filename); 37 | console.log( 38 | ' ' + Math.floor(syncWritesPerSecond()) + ' sync writes per second' 39 | ); 40 | 41 | -------------------------------------------------------------------------------- /integration-test/run-performance-tests: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | node performance-async 3 | node performance-sync 4 | node performance-interrupt 5 | 6 | -------------------------------------------------------------------------------- /integration-test/run-tests: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | node blink-led 3 | node blink-led-promises 4 | node change-configuration 5 | node configure-and-check-active-low 6 | node configure-and-check-active-low-defaults 7 | node configure-and-check-input 8 | node configure-and-check-output 9 | node debounce 10 | node dont-reconfigure-direction-part1 11 | node dont-reconfigure-direction-part2 12 | node high-low 13 | node many-interrupts 14 | node output-with-edge-bug 15 | node wait-for-interrupt 16 | node wait-for-many-interrupts 17 | 18 | -------------------------------------------------------------------------------- /integration-test/wait-for-interrupt.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Gpio = require('../onoff').Gpio; 4 | const assert = require('assert'); 5 | const button = new Gpio(4, 'in', 'both'); 6 | 7 | assert(button.direction() === 'in'); 8 | assert(button.edge() === 'both'); 9 | 10 | console.info('Please press button connected to GPIO #4...'); 11 | 12 | button.watch((err, value) => { 13 | if (err) { 14 | throw err; 15 | } 16 | 17 | assert(value === 0 || value === 1); 18 | 19 | button.unexport(); 20 | 21 | console.log('ok - ' + __filename); 22 | console.log(' button pressed, value was ' + value); 23 | }); 24 | 25 | -------------------------------------------------------------------------------- /integration-test/wait-for-many-interrupts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Gpio = require('../onoff').Gpio; 4 | const assert = require('assert'); 5 | const button = new Gpio(4, 'in', 'rising', { 6 | debounceTimeout : 10 7 | }); 8 | let count = 0; 9 | 10 | assert(button.direction() === 'in'); 11 | assert(button.edge() === 'rising'); 12 | 13 | console.info('Please press button connected to GPIO4 5 times...'); 14 | 15 | button.watch((err, value) => { 16 | if (err) { 17 | throw err; 18 | } 19 | 20 | count += 1; 21 | 22 | console.log('button pressed ' + count + ' times, value was ' + value); 23 | 24 | if (count === 5) { 25 | button.unexport(); 26 | console.log('ok - ' + __filename); 27 | } 28 | }); 29 | 30 | -------------------------------------------------------------------------------- /onoff.d.ts: -------------------------------------------------------------------------------- 1 | export type High = 1; 2 | export type Low = 0; 3 | export type Direction = "in" | "out" | "high" | "low"; 4 | export type Edge = "none" | "rising" | "falling" | "both"; 5 | 6 | export type Options = { 7 | debounceTimeout?: number, 8 | activeLow?: boolean, 9 | reconfigureDirection?: boolean, 10 | } 11 | 12 | export type BinaryValue = High | Low; 13 | export type ValueCallback = (err: Error | null | undefined, value: BinaryValue) => void; 14 | 15 | export class Gpio { 16 | static HIGH: High; 17 | static LOW: Low; 18 | static accessible: boolean; 19 | 20 | constructor(gpio: number, direction: Direction, edge?: Edge, options?: Options); 21 | 22 | read(callback: ValueCallback): void; 23 | read(): Promise; 24 | 25 | readSync(): BinaryValue; 26 | 27 | write(value: BinaryValue, callback: (err: Error | null | undefined) => void): void; 28 | write(value: BinaryValue): Promise; 29 | 30 | writeSync(value: BinaryValue): void; 31 | 32 | watch(callback: ValueCallback): void; 33 | unwatch(callback?: ValueCallback): void; 34 | unwatchAll(): void; 35 | 36 | direction(): Direction; 37 | setDirection(direction: Direction): void; 38 | 39 | edge(): Edge; 40 | setEdge(edge: Edge): void; 41 | 42 | activeLow(): boolean; 43 | setActiveLow(invert: boolean): void; 44 | 45 | unexport(): void 46 | } 47 | -------------------------------------------------------------------------------- /onoff.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const debounce = require('lodash.debounce'); 5 | const Epoll = require('epoll').Epoll; 6 | 7 | const GPIO_ROOT_PATH = '/sys/class/gpio/'; 8 | 9 | const HIGH_BUF = Buffer.from('1'); 10 | const LOW_BUF = Buffer.from('0'); 11 | 12 | const HIGH = 1; 13 | const LOW = 0; 14 | 15 | const exportGpio = gpio => { 16 | if (!fs.existsSync(gpio._gpioPath)) { 17 | // The GPIO hasn't been exported yet so export it 18 | fs.writeFileSync(GPIO_ROOT_PATH + 'export', '' + gpio._gpio); 19 | 20 | return false; 21 | } 22 | 23 | // The GPIO has already been exported, perhaps by onoff itself, perhaps 24 | // by quick2wire gpio-admin on the Pi, perhaps by the WiringPi gpio 25 | // utility on the Pi, or perhaps by something else. In any case, an 26 | // attempt is made to set the direction and edge to the requested 27 | // values here. If quick2wire gpio-admin was used for the export, the 28 | // user should have access to both direction and edge files. This is 29 | // important as gpio-admin sets niether direction nor edge. If the 30 | // WiringPi gpio utility was used, the user should have access to edge 31 | // file, but not the direction file. This is also ok as the WiringPi 32 | // gpio utility can set both direction and edge. If there are any 33 | // errors while attempting to perform the modifications, just keep on 34 | // truckin'. 35 | return true; 36 | }; 37 | 38 | // Avoid the access permission issue described here: 39 | // https://github.com/raspberrypi/linux/issues/553 40 | // On some syetems udev rules are used to set access permissions on the GPIO 41 | // sysfs files enabling those files to be accessed without root privileges. 42 | // This takes a while so wait for it to complete. 43 | const waitForGpioAccessPermission = ( 44 | gpio, direction, edge, gpioPreviouslyExported 45 | ) => { 46 | let permissionRequiredPaths = [ 47 | gpio._gpioPath + 'value', 48 | ]; 49 | 50 | if (gpioPreviouslyExported === false) { 51 | permissionRequiredPaths.push(gpio._gpioPath + 'direction'); 52 | permissionRequiredPaths.push(gpio._gpioPath + 'active_low'); 53 | 54 | // On some systems the edge file will not exist if the GPIO does not 55 | // support interrupts 56 | // https://github.com/fivdi/onoff/issues/77#issuecomment-321980735 57 | if (edge && direction === 'in') { 58 | permissionRequiredPaths.push(gpio._gpioPath + 'edge'); 59 | } 60 | } 61 | 62 | permissionRequiredPaths.forEach(path => { 63 | let tries = 0; 64 | 65 | while (true) { 66 | try { 67 | tries += 1; 68 | const fd = fs.openSync(path, 'r+'); 69 | fs.closeSync(fd); 70 | break; 71 | } catch (e) { 72 | if (tries === 10000) { 73 | throw e; 74 | } 75 | } 76 | } 77 | }); 78 | }; 79 | 80 | const configureGpio = ( 81 | gpio, direction, edge, options, gpioPreviouslyExported 82 | ) => { 83 | const throwIfNeeded = err => { 84 | if (gpioPreviouslyExported === false) { 85 | throw err; 86 | } 87 | }; 88 | 89 | try { 90 | if (typeof options.activeLow === 'boolean') { 91 | gpio.setActiveLow(options.activeLow); 92 | } 93 | } catch (err) { 94 | throwIfNeeded(err); 95 | } 96 | 97 | try { 98 | const reconfigureDirection = 99 | typeof options.reconfigureDirection === 'boolean' ? 100 | options.reconfigureDirection : true; 101 | 102 | const requestedDirection = 103 | direction === 'high' || direction === 'low' ? 'out' : direction; 104 | 105 | if (reconfigureDirection || gpio.direction() !== requestedDirection) { 106 | gpio.setDirection(direction); 107 | } 108 | } catch (err) { 109 | throwIfNeeded(err); 110 | } 111 | 112 | try { 113 | // On some systems writing to the edge file for an output GPIO will 114 | // result in an "EIO, i/o error" 115 | // https://github.com/fivdi/onoff/issues/87 116 | if (edge && direction === 'in') { 117 | gpio.setEdge(edge); 118 | } 119 | } catch (err) { 120 | throwIfNeeded(err); 121 | } 122 | }; 123 | 124 | const configureInterruptHandler = gpio => { 125 | // A poller is created for both inputs and outputs. A poller isn't 126 | // actually needed for an output but the setDirection method can be 127 | // invoked to change the direction of a GPIO from output to input and 128 | // then a poller may be needed. 129 | const pollerEventHandler = (err, fd, events) => { 130 | const value = gpio.readSync(); 131 | 132 | if ((value === LOW && gpio._fallingEnabled) || 133 | (value === HIGH && gpio._risingEnabled)) { 134 | gpio._listeners.slice(0).forEach(callback => { 135 | callback(err, value); 136 | }); 137 | } 138 | }; 139 | 140 | // Read GPIO value before polling to prevent an initial unauthentic 141 | // interrupt 142 | gpio.readSync(); 143 | 144 | if (gpio._debounceTimeout > 0) { 145 | const db = debounce(pollerEventHandler, gpio._debounceTimeout); 146 | 147 | gpio._poller = new Epoll((err, fd, events) => { 148 | gpio.readSync(); // Clear interrupt 149 | db(err, fd, events); 150 | }); 151 | } else { 152 | gpio._poller = new Epoll(pollerEventHandler); 153 | } 154 | }; 155 | 156 | class Gpio { 157 | constructor(gpio, direction, edge, options) { 158 | if (typeof edge === 'object' && !options) { 159 | options = edge; 160 | edge = undefined; 161 | } 162 | 163 | options = options || {}; 164 | 165 | this._gpio = gpio; 166 | this._gpioPath = GPIO_ROOT_PATH + 'gpio' + this._gpio + '/'; 167 | this._debounceTimeout = options.debounceTimeout || 0; 168 | this._readBuffer = Buffer.alloc(16); 169 | this._readSyncBuffer = Buffer.alloc(16); 170 | this._listeners = []; 171 | 172 | const gpioPreviouslyExported = exportGpio(this); 173 | 174 | waitForGpioAccessPermission( 175 | this, direction, edge, gpioPreviouslyExported 176 | ); 177 | 178 | configureGpio(this, direction, edge, options, gpioPreviouslyExported); 179 | 180 | this._valueFd = fs.openSync(this._gpioPath + 'value', 'r+'); 181 | 182 | configureInterruptHandler(this); 183 | } 184 | 185 | read(callback) { 186 | const readValue = callback => { 187 | fs.read(this._valueFd, this._readBuffer, 0, 1, 0, (err, bytes, buf) => { 188 | if (typeof callback === 'function') { 189 | if (err) { 190 | return callback(err); 191 | } 192 | 193 | callback(null, convertBufferToBit(buf)); 194 | } 195 | }); 196 | }; 197 | 198 | if (callback) { 199 | readValue(callback); 200 | } else { 201 | return new Promise((resolve, reject) => { 202 | readValue((err, value) => { 203 | if (err) { 204 | reject(err); 205 | } else { 206 | resolve(value); 207 | } 208 | }); 209 | }); 210 | } 211 | } 212 | 213 | readSync() { 214 | fs.readSync(this._valueFd, this._readSyncBuffer, 0, 1, 0); 215 | return convertBufferToBit(this._readSyncBuffer); 216 | } 217 | 218 | write(value, callback) { 219 | const writeValue = (value, callback) => { 220 | const writeBuffer = convertBitToBuffer(value); 221 | fs.write( 222 | this._valueFd, writeBuffer, 0, writeBuffer.length, 0, callback 223 | ); 224 | }; 225 | 226 | if (callback) { 227 | writeValue(value, callback); 228 | } else { 229 | return new Promise((resolve, reject) => { 230 | writeValue(value, err => { 231 | if (err) { 232 | reject(err); 233 | } else { 234 | resolve(); 235 | } 236 | }); 237 | }); 238 | } 239 | } 240 | 241 | writeSync(value) { 242 | const writeBuffer = convertBitToBuffer(value); 243 | fs.writeSync(this._valueFd, writeBuffer, 0, writeBuffer.length, 0); 244 | } 245 | 246 | watch(callback) { 247 | this._listeners.push(callback); 248 | 249 | if (this._listeners.length === 1) { 250 | this._poller.add(this._valueFd, Epoll.EPOLLPRI); 251 | } 252 | } 253 | 254 | unwatch(callback) { 255 | if (this._listeners.length > 0) { 256 | if (typeof callback !== 'function') { 257 | this._listeners = []; 258 | } else { 259 | this._listeners = this._listeners.filter(listener => { 260 | return callback !== listener; 261 | }); 262 | } 263 | 264 | if (this._listeners.length === 0) { 265 | this._poller.remove(this._valueFd); 266 | } 267 | } 268 | } 269 | 270 | unwatchAll() { 271 | this.unwatch(); 272 | } 273 | 274 | direction() { 275 | return fs.readFileSync(this._gpioPath + 'direction').toString().trim(); 276 | } 277 | 278 | setDirection(direction) { 279 | fs.writeFileSync(this._gpioPath + 'direction', direction); 280 | } 281 | 282 | edge() { 283 | return fs.readFileSync(this._gpioPath + 'edge').toString().trim(); 284 | } 285 | 286 | setEdge(edge) { 287 | fs.writeFileSync(this._gpioPath + 'edge', edge); 288 | 289 | this._risingEnabled = edge === 'both' || edge === 'rising'; 290 | this._fallingEnabled = edge === 'both' || edge === 'falling'; 291 | } 292 | 293 | activeLow() { 294 | return convertBufferToBoolean( 295 | fs.readFileSync(this._gpioPath + 'active_low') 296 | ); 297 | } 298 | 299 | setActiveLow(invert) { 300 | fs.writeFileSync( 301 | this._gpioPath + 'active_low', convertBooleanToBuffer(!!invert) 302 | ); 303 | } 304 | 305 | unexport() { 306 | this.unwatchAll(); 307 | fs.closeSync(this._valueFd); 308 | try { 309 | fs.writeFileSync(GPIO_ROOT_PATH + 'unexport', '' + this._gpio); 310 | } catch (ignore) { 311 | // Flow of control always arrives here when cape_universal is enabled on 312 | // the bbb. 313 | } 314 | } 315 | 316 | static get accessible() { 317 | let fd; 318 | 319 | try { 320 | fd = fs.openSync(GPIO_ROOT_PATH + 'export', fs.constants.O_WRONLY); 321 | } catch(e) { 322 | // e.code === 'ENOENT' / 'EACCES' are most common 323 | // though any failure to open will also result in a gpio 324 | // failure to export. 325 | return false; 326 | } finally { 327 | if (fd) { 328 | fs.closeSync(fd); 329 | } 330 | } 331 | 332 | return true; 333 | } 334 | } 335 | 336 | const convertBitToBuffer = bit => convertBooleanToBuffer(bit === HIGH); 337 | const convertBufferToBit = 338 | buffer => convertBufferToBoolean(buffer) ? HIGH : LOW; 339 | 340 | const convertBooleanToBuffer = boolean => boolean ? HIGH_BUF : LOW_BUF; 341 | const convertBufferToBoolean = buffer => buffer[0] === HIGH_BUF[0]; 342 | 343 | Gpio.HIGH = HIGH; 344 | Gpio.LOW = LOW; 345 | 346 | module.exports.Gpio = Gpio; 347 | 348 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "onoff", 3 | "version": "6.0.3", 4 | "description": "GPIO access and interrupt detection with Node.js", 5 | "main": "onoff.js", 6 | "types": "onoff.d.ts", 7 | "directories": { 8 | "example": "examples", 9 | "test": "test" 10 | }, 11 | "scripts": { 12 | "lint": "jshint onoff.js examples/*.js integration-test/*.js test/*.js test/*/*.js", 13 | "test": "nyc mocha", 14 | "codecov": "nyc report --reporter=text-lcov > coverage.lcov && codecov", 15 | "coverage-report": "nyc report --reporter=html", 16 | "integration-test": "cd integration-test && ./run-tests && cd .." 17 | }, 18 | "mocha": { 19 | "extension": [ 20 | "js", 21 | "ts" 22 | ], 23 | "require": "ts-node/register" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/fivdi/onoff.git" 28 | }, 29 | "homepage": "https://github.com/fivdi/onoff", 30 | "bugs": { 31 | "url": "https://github.com/fivdi/onoff/issues" 32 | }, 33 | "engines": { 34 | "node": ">=10.0.0" 35 | }, 36 | "dependencies": { 37 | "epoll": "^4.0.1", 38 | "lodash.debounce": "^4.0.8" 39 | }, 40 | "devDependencies": { 41 | "@types/mocha": "^8.2.2", 42 | "@types/mock-require": "^2.0.0", 43 | "codecov": "^3.8.1", 44 | "jshint": "^2.12.0", 45 | "mocha": "^8.3.2", 46 | "mock-fs": "^4.13.0", 47 | "mock-require": "^3.0.3", 48 | "nyc": "^15.1.0", 49 | "ts-node": "^9.1.1", 50 | "typescript": "^4.2.4" 51 | }, 52 | "keywords": [ 53 | "gpio", 54 | "iot", 55 | "interrupt", 56 | "raspberry", 57 | "raspi", 58 | "rpi", 59 | "pi", 60 | "beaglebone", 61 | "beaglebone-black", 62 | "linux" 63 | ], 64 | "author": "fivdi", 65 | "license": "MIT" 66 | } 67 | -------------------------------------------------------------------------------- /test/accessible.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const mockRequire = require('mock-require'); 5 | const MockLinux = require('./mocks/linux'); 6 | const MockEpoll = require('./mocks/epoll'); 7 | 8 | mockRequire('epoll', MockEpoll); 9 | const Gpio = require('../onoff').Gpio; 10 | 11 | 12 | describe('accessible', () => { 13 | beforeEach(() => { 14 | }); 15 | 16 | 17 | it('is accessible', () => { 18 | MockLinux.makeGpioAccessible(); 19 | assert.deepEqual(Gpio.accessible, true); 20 | }); 21 | 22 | it('is inaccessible', () => { 23 | MockLinux.makeGpioInaccessible(); 24 | assert.deepEqual(Gpio.accessible, false); 25 | }); 26 | 27 | 28 | afterEach(() => { 29 | MockLinux.restore(); 30 | }); 31 | }); 32 | 33 | -------------------------------------------------------------------------------- /test/activeLow.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const mockRequire = require('mock-require'); 5 | const MockLinux = require('./mocks/linux'); 6 | const MockEpoll = require('./mocks/epoll'); 7 | 8 | mockRequire('epoll', MockEpoll); 9 | const Gpio = require('../onoff').Gpio; 10 | 11 | 12 | describe('activeLow', () => { 13 | let gpio; 14 | let pin; 15 | 16 | beforeEach(() => { 17 | pin = 4; 18 | MockLinux.gpio(pin); 19 | gpio = new Gpio(pin, 'out'); 20 | }); 21 | 22 | 23 | it('is active high', () => { 24 | MockLinux.writeActiveLow(pin, '1'); 25 | const actual = gpio.activeLow(); 26 | assert.deepEqual(actual, true); 27 | }); 28 | 29 | it('is active low', () => { 30 | MockLinux.writeActiveLow(pin, '0'); 31 | const actual = gpio.activeLow(); 32 | assert.deepEqual(actual, false); 33 | }); 34 | 35 | 36 | afterEach(() => { 37 | gpio.unexport(); 38 | MockLinux.restore(); 39 | }); 40 | }); 41 | 42 | -------------------------------------------------------------------------------- /test/constructor-fails.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const mockRequire = require('mock-require'); 5 | const MockLinux = require('./mocks/linux'); 6 | const MockEpoll = require('./mocks/epoll'); 7 | 8 | mockRequire('epoll', MockEpoll); 9 | const Gpio = require('../onoff').Gpio; 10 | 11 | 12 | describe('constructor fails', function () { 13 | let pin; 14 | 15 | this.timeout(10000); 16 | 17 | beforeEach(() => { 18 | pin = 4; 19 | }); 20 | 21 | 22 | it('fails to construct input while waiting for access permission', () => { 23 | const expected = 'ENOENT'; 24 | let actual; 25 | 26 | MockLinux.gpioWithoutPinFiles(); 27 | 28 | try { 29 | const gpio = new Gpio(pin, 'in', 'both'); 30 | } catch (err) { 31 | actual = err.code; 32 | } 33 | 34 | assert.deepEqual(actual, expected); 35 | }); 36 | 37 | it('fails to construct output while waiting for access permission', () => { 38 | const expected = 'ENOENT'; 39 | let actual; 40 | 41 | MockLinux.gpioWithoutPinFiles(); 42 | 43 | try { 44 | const gpio = new Gpio(pin, 'out'); 45 | } catch (err) { 46 | actual = err.code; 47 | } 48 | 49 | assert.deepEqual(actual, expected); 50 | }); 51 | 52 | 53 | afterEach(() => { 54 | MockLinux.restore(); 55 | }); 56 | }); 57 | 58 | -------------------------------------------------------------------------------- /test/constructor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const mockRequire = require('mock-require'); 5 | const MockLinux = require('./mocks/linux'); 6 | const MockEpoll = require('./mocks/epoll'); 7 | 8 | mockRequire('epoll', MockEpoll); 9 | const Gpio = require('../onoff').Gpio; 10 | 11 | 12 | describe('constructor', () => { 13 | let gpio; 14 | let pin; 15 | 16 | beforeEach(() => { 17 | pin = 4; 18 | MockLinux.gpio(pin); 19 | }); 20 | 21 | 22 | it('creates input', () => { 23 | gpio = new Gpio(pin, 'in'); 24 | 25 | const expected = 'in'; 26 | const actual = gpio.direction(); 27 | assert.deepEqual(actual, expected); 28 | }); 29 | 30 | it('creates output', () => { 31 | gpio = new Gpio(pin, 'out'); 32 | 33 | const expected = 'out'; 34 | const actual = gpio.direction(); 35 | assert.deepEqual(actual, expected); 36 | }); 37 | 38 | it('creates active low input', () => { 39 | gpio = new Gpio(pin, 'in', {activeLow: true}); 40 | 41 | const expected = true; 42 | const actual = gpio.activeLow(); 43 | assert.deepEqual(actual, expected); 44 | }); 45 | 46 | it('creates active high input', () => { 47 | gpio = new Gpio(pin, 'in', {activeLow: false}); 48 | 49 | const expected = false; 50 | const actual = gpio.activeLow(); 51 | assert.deepEqual(actual, expected); 52 | }); 53 | 54 | it('creates input with debounce timeout', () => { 55 | gpio = new Gpio(pin, 'in', {debounceTimeout: 10}); 56 | 57 | const expected = 10; 58 | const actual = gpio._debounceTimeout; 59 | assert.deepEqual(actual, expected); 60 | }); 61 | 62 | it('ignores exceptions thrown by setActiveLow', () => { 63 | const setActiveLow = Gpio.prototype.setActiveLow; 64 | Gpio.prototype.setActiveLow = () => { 65 | throw new Error(); 66 | }; 67 | 68 | const expected = ''; 69 | let actual; 70 | 71 | try { 72 | gpio = new Gpio(pin, 'out', {activeLow: true}); 73 | actual = ''; 74 | } catch (err) { 75 | actual = 'error thrown by setActiveLow was not ignored'; 76 | } 77 | 78 | Gpio.prototype.setActiveLow = setActiveLow; 79 | 80 | assert.deepEqual(actual, expected); 81 | }); 82 | 83 | it('ignores exceptions thrown by setDirection', () => { 84 | const setDirection = Gpio.prototype.setDirection; 85 | Gpio.prototype.setDirection = () => { 86 | throw new Error(); 87 | }; 88 | 89 | const expected = ''; 90 | let actual; 91 | 92 | try { 93 | gpio = new Gpio(pin, 'out'); 94 | actual = ''; 95 | } catch (err) { 96 | actual = 'error thrown by setDirection was not ignored'; 97 | } 98 | 99 | Gpio.prototype.setDirection = setDirection; 100 | 101 | assert.deepEqual(actual, expected); 102 | }); 103 | 104 | it('ignores exceptions thrown by setEdge', () => { 105 | const setEdge = Gpio.prototype.setEdge; 106 | Gpio.prototype.setEdge = () => { 107 | throw new Error(); 108 | }; 109 | 110 | const expected = ''; 111 | let actual; 112 | 113 | try { 114 | gpio = new Gpio(pin, 'in', 'both'); 115 | actual = ''; 116 | } catch (err) { 117 | actual = 'error thrown by setEdge was not ignored'; 118 | } 119 | 120 | Gpio.prototype.setEdge = setEdge; 121 | 122 | assert.deepEqual(actual, expected); 123 | }); 124 | 125 | it('does not unnecessarily reconfigure direction', () => { 126 | let setDirectionCalled = false; 127 | 128 | const setDirection = Gpio.prototype.setDirection; 129 | Gpio.prototype.setDirection = () => { 130 | setDirectionCalled = true; 131 | }; 132 | 133 | gpio = new Gpio(pin, 'in', {reconfigureDirection: false}); 134 | 135 | Gpio.prototype.setDirection = setDirection; 136 | 137 | assert(!setDirectionCalled); 138 | }); 139 | 140 | it('reconfigures direction if necessary', () => { 141 | let setDirectionCalled = false; 142 | 143 | const setDirection = Gpio.prototype.setDirection; 144 | Gpio.prototype.setDirection = () => { 145 | setDirectionCalled = true; 146 | }; 147 | 148 | gpio = new Gpio(pin, 'high', {reconfigureDirection: false}); 149 | 150 | Gpio.prototype.setDirection = setDirection; 151 | 152 | assert(setDirectionCalled); 153 | }); 154 | 155 | 156 | afterEach(() => { 157 | gpio.unexport(); 158 | MockLinux.restore(); 159 | }); 160 | }); 161 | 162 | -------------------------------------------------------------------------------- /test/direction.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const mockRequire = require('mock-require'); 5 | const MockLinux = require('./mocks/linux'); 6 | const MockEpoll = require('./mocks/epoll'); 7 | 8 | mockRequire('epoll', MockEpoll); 9 | const Gpio = require('../onoff').Gpio; 10 | 11 | 12 | describe('direction', () => { 13 | let gpio; 14 | let pin; 15 | 16 | beforeEach(() => { 17 | pin = 4; 18 | MockLinux.gpio(pin); 19 | }); 20 | 21 | 22 | it('is input', () => { 23 | gpio = new Gpio(pin, 'out'); 24 | 25 | const expected = 'in'; 26 | MockLinux.writeDirection(pin, expected); 27 | const actual = gpio.direction(); 28 | assert.deepEqual(actual, expected); 29 | }); 30 | 31 | it('is output', () => { 32 | gpio = new Gpio(pin, 'in'); 33 | 34 | const expected = 'out'; 35 | MockLinux.writeDirection(pin, expected); 36 | const actual = gpio.direction(); 37 | assert.deepEqual(actual, expected); 38 | }); 39 | 40 | 41 | afterEach(() => { 42 | gpio.unexport(); 43 | MockLinux.restore(); 44 | }); 45 | }); 46 | 47 | -------------------------------------------------------------------------------- /test/edge.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const mockRequire = require('mock-require'); 5 | const MockLinux = require('./mocks/linux'); 6 | const MockEpoll = require('./mocks/epoll'); 7 | 8 | mockRequire('epoll', MockEpoll); 9 | const Gpio = require('../onoff').Gpio; 10 | 11 | 12 | describe('edge', () => { 13 | let gpio; 14 | let pin; 15 | 16 | beforeEach(() => { 17 | pin = 4; 18 | MockLinux.gpio(pin); 19 | gpio = new Gpio(pin, 'in'); 20 | }); 21 | 22 | 23 | it('is rising', () => { 24 | const expected = 'rising'; 25 | MockLinux.writeEdge(pin, expected); 26 | const actual = gpio.edge(); 27 | assert.deepEqual(actual, expected); 28 | }); 29 | 30 | it('is falling', () => { 31 | const expected = 'falling'; 32 | MockLinux.writeEdge(pin, expected); 33 | const actual = gpio.edge(); 34 | assert.deepEqual(actual, expected); 35 | }); 36 | 37 | it('is both', () => { 38 | const expected = 'both'; 39 | MockLinux.writeEdge(pin, expected); 40 | const actual = gpio.edge(); 41 | assert.deepEqual(actual, expected); 42 | }); 43 | 44 | 45 | afterEach(() => { 46 | gpio.unexport(); 47 | MockLinux.restore(); 48 | }); 49 | }); 50 | 51 | -------------------------------------------------------------------------------- /test/mocks/epoll.d.ts: -------------------------------------------------------------------------------- 1 | export class Epoll { 2 | static EPOLLPRI: number; 3 | 4 | constructor(callback: (args: any) => void); 5 | 6 | add(fd: any, events: any): void; 7 | 8 | remove(fd: any): void; 9 | } -------------------------------------------------------------------------------- /test/mocks/epoll.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class Epoll { 4 | static get EPOLLPRI() { return 2; } 5 | 6 | constructor(callback) { 7 | this._callback = callback; 8 | this._timeout = null; 9 | } 10 | 11 | add(fd, events) { 12 | this._timeout = setTimeout(_ => { 13 | this._callback(null, fd, events); 14 | }, 10); 15 | } 16 | 17 | remove(fd) { 18 | if (this._timeout !== null) { 19 | clearTimeout(this._timeout); 20 | } 21 | } 22 | } 23 | 24 | exports.Epoll = Epoll; 25 | 26 | -------------------------------------------------------------------------------- /test/mocks/linux.d.ts: -------------------------------------------------------------------------------- 1 | 2 | export function gpio(pin: any): void; 3 | 4 | export function gpioWithoutPinFiles(): void; 5 | 6 | export function makeGpioAccessible(): void; 7 | 8 | export function makeGpioInaccessible(): void; 9 | 10 | export function read(pin: any): any; 11 | 12 | export function write(pin: any, value: any): void; 13 | 14 | export function readDirection(pin: any): any; 15 | 16 | export function writeDirection(pin: any, direction: any): void; 17 | 18 | export function readEdge(pin: void): any; 19 | 20 | export function writeEdge(pin: any, edge: any): void; 21 | 22 | export function readActiveLow(pin: any): any; 23 | 24 | export function writeActiveLow(pin: any, value: any): void; 25 | 26 | export function restore(): void; 27 | -------------------------------------------------------------------------------- /test/mocks/linux.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mockFs = require('mock-fs'); 4 | const fs = require('fs'); 5 | 6 | function gpio(pin) { 7 | const name = `gpio${pin}`; 8 | mockFs({ 9 | '/sys/class/gpio': { 10 | 'export': '', 11 | 'unexport': '', 12 | [name]: { 13 | 'direction': 'in', 14 | 'edge': 'none', 15 | 'active_low': '0', 16 | 'value': '0' 17 | } 18 | } 19 | }); 20 | } 21 | 22 | function gpioWithoutPinFiles() { 23 | mockFs({ 24 | '/sys/class/gpio': { 25 | 'export': '' 26 | } 27 | }); 28 | } 29 | 30 | function makeGpioAccessible() { 31 | mockFs({ 32 | '/sys/class/gpio': { 33 | 'export': '' 34 | } 35 | }); 36 | } 37 | 38 | function makeGpioInaccessible() { 39 | mockFs({ 40 | '/sys/class/gpio': { 41 | } 42 | }); 43 | } 44 | 45 | function read(pin) { 46 | return fs.readFileSync('/sys/class/gpio/gpio4/value', { encoding: 'UTF-8' }); 47 | } 48 | 49 | function write(pin, value) { 50 | fs.writeFileSync(`/sys/class/gpio/gpio${pin}/value`, value); 51 | } 52 | 53 | function readDirection(pin) { 54 | return fs.readFileSync(`/sys/class/gpio/gpio${pin}/direction`, { encoding: 'UTF-8' }); 55 | } 56 | 57 | function writeDirection(pin, direction) { 58 | fs.writeFileSync(`/sys/class/gpio/gpio${pin}/direction`, direction); 59 | } 60 | 61 | function readEdge(pin) { 62 | return fs.readFileSync(`/sys/class/gpio/gpio${pin}/edge`, { encoding: 'UTF-8' }); 63 | } 64 | 65 | function writeEdge(pin, edge) { 66 | fs.writeFileSync(`/sys/class/gpio/gpio${pin}/edge`, edge); 67 | } 68 | 69 | function readActiveLow(pin) { 70 | return fs.readFileSync(`/sys/class/gpio/gpio${pin}/active_low`, { encoding: 'UTF-8' }); 71 | } 72 | 73 | function writeActiveLow(pin, value) { 74 | fs.writeFileSync(`/sys/class/gpio/gpio${pin}/active_low`, value); 75 | } 76 | 77 | function restore() { 78 | mockFs.restore(); 79 | } 80 | 81 | exports.gpio = gpio; 82 | exports.gpioWithoutPinFiles = gpioWithoutPinFiles; 83 | exports.makeGpioAccessible = makeGpioAccessible; 84 | exports.makeGpioInaccessible = makeGpioInaccessible; 85 | exports.read = read; 86 | exports.write = write; 87 | exports.readDirection = readDirection; 88 | exports.writeDirection = writeDirection; 89 | exports.readEdge = readEdge; 90 | exports.writeEdge = writeEdge; 91 | exports.readActiveLow = readActiveLow; 92 | exports.writeActiveLow = writeActiveLow; 93 | exports.restore = restore; 94 | 95 | -------------------------------------------------------------------------------- /test/read.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const mockRequire = require('mock-require'); 5 | const MockLinux = require('./mocks/linux'); 6 | const MockEpoll = require('./mocks/epoll'); 7 | const TestHelper = require('./utils/test-promise'); 8 | 9 | mockRequire('epoll', MockEpoll); 10 | const Gpio = require('../onoff').Gpio; 11 | 12 | 13 | describe('read', () => { 14 | let gpio; 15 | let pin; 16 | 17 | beforeEach(() => { 18 | pin = 4; 19 | MockLinux.gpio(pin); 20 | gpio = new Gpio(pin, 'in'); 21 | }); 22 | 23 | 24 | it('reads high', (done) => { 25 | const expected = 1; 26 | MockLinux.write(pin, '' + expected); 27 | gpio.read((err, actual) => { 28 | assert.deepEqual(actual, expected); 29 | done(); 30 | }); 31 | }); 32 | 33 | it('reads low', (done) => { 34 | const expected = 0; 35 | MockLinux.write(pin, '' + expected); 36 | gpio.read((err, actual) => { 37 | assert.deepEqual(actual, expected); 38 | done(); 39 | }); 40 | }); 41 | 42 | it('fails', (done) => { 43 | const expected = 'EBADF'; 44 | 45 | const valueFd = gpio._valueFd; 46 | gpio._valueFd = 1e6; 47 | 48 | gpio.read((err, value) => { 49 | gpio._valueFd = valueFd; 50 | 51 | const actual = err.code; 52 | assert.deepEqual(actual, expected); 53 | 54 | done(); 55 | }); 56 | }); 57 | 58 | 59 | afterEach(() => { 60 | gpio.unexport(); 61 | MockLinux.restore(); 62 | }); 63 | }); 64 | 65 | describe('read Promise', () => { 66 | let gpio; 67 | let pin; 68 | 69 | beforeEach(() => { 70 | pin = 4; 71 | MockLinux.gpio(pin); 72 | gpio = new Gpio(pin, 'in'); 73 | }); 74 | 75 | it('reads high', () => { 76 | const expected = 1; 77 | MockLinux.write(pin, '' + expected); 78 | return gpio.read() 79 | .then(actual => TestHelper.shouldEventuallyEqual(actual, expected)); 80 | }); 81 | 82 | it('reads low', () => { 83 | const expected = 0; 84 | MockLinux.write(pin, '' + expected); 85 | return gpio.read() 86 | .then(actual => TestHelper.shouldEventuallyEqual(actual, expected)); 87 | }); 88 | 89 | it('fails', () => { 90 | const expected = 'EBADF'; 91 | 92 | const valueFd = gpio._valueFd; 93 | gpio._valueFd = 1e6; 94 | 95 | return gpio.read().catch((err) => { 96 | gpio._valueFd = valueFd; 97 | 98 | const actual = err.code; 99 | return TestHelper.shouldEventuallyEqual(actual, expected); 100 | }); 101 | }); 102 | 103 | afterEach(() => { 104 | gpio.unexport(); 105 | MockLinux.restore(); 106 | }); 107 | }); 108 | 109 | -------------------------------------------------------------------------------- /test/readSync.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const mockRequire = require('mock-require'); 5 | const MockLinux = require('./mocks/linux'); 6 | const MockEpoll = require('./mocks/epoll'); 7 | 8 | mockRequire('epoll', MockEpoll); 9 | const Gpio = require('../onoff').Gpio; 10 | 11 | 12 | describe('readSync', () => { 13 | let gpio; 14 | let pin; 15 | 16 | beforeEach(() => { 17 | pin = 4; 18 | MockLinux.gpio(pin); 19 | gpio = new Gpio(pin, 'out'); 20 | }); 21 | 22 | 23 | it('reads high', () => { 24 | const expected = 1; 25 | MockLinux.write(pin, '' + expected); 26 | const actual = gpio.readSync(); 27 | assert.deepEqual(actual, expected); 28 | }); 29 | 30 | it('reads low', () => { 31 | const expected = 0; 32 | MockLinux.write(pin, '' + expected); 33 | const actual = gpio.readSync(); 34 | assert.deepEqual(actual, expected); 35 | }); 36 | 37 | 38 | afterEach(() => { 39 | gpio.unexport(); 40 | MockLinux.restore(); 41 | }); 42 | }); 43 | 44 | -------------------------------------------------------------------------------- /test/setActiveLow.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const mockRequire = require('mock-require'); 5 | const MockLinux = require('./mocks/linux'); 6 | const MockEpoll = require('./mocks/epoll'); 7 | 8 | mockRequire('epoll', MockEpoll); 9 | const Gpio = require('../onoff').Gpio; 10 | 11 | 12 | describe('setActiveLow', () => { 13 | let gpio; 14 | let pin; 15 | 16 | beforeEach(() => { 17 | pin = 4; 18 | MockLinux.gpio(pin); 19 | gpio = new Gpio(pin, 'in'); 20 | }); 21 | 22 | 23 | it('is active high', () => { 24 | gpio.setActiveLow(true); 25 | const actual = MockLinux.readActiveLow(pin); 26 | assert.deepEqual(actual, 1); 27 | }); 28 | 29 | it('is active low', () => { 30 | gpio.setActiveLow(false); 31 | const actual = MockLinux.readActiveLow(pin); 32 | assert.deepEqual(actual, 0); 33 | }); 34 | 35 | 36 | afterEach(() => { 37 | gpio.unexport(); 38 | MockLinux.restore(); 39 | }); 40 | }); 41 | 42 | -------------------------------------------------------------------------------- /test/setDirection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const mockRequire = require('mock-require'); 5 | const MockLinux = require('./mocks/linux'); 6 | const MockEpoll = require('./mocks/epoll'); 7 | 8 | mockRequire('epoll', MockEpoll); 9 | const Gpio = require('../onoff').Gpio; 10 | 11 | 12 | describe('setDirection', () => { 13 | let gpio; 14 | let pin; 15 | 16 | beforeEach(() => { 17 | pin = 4; 18 | MockLinux.gpio(pin); 19 | }); 20 | 21 | 22 | it('is input', () => { 23 | gpio = new Gpio(pin, 'out'); 24 | 25 | const expected = 'in'; 26 | gpio.setDirection(expected); 27 | const actual = MockLinux.readDirection(pin); 28 | assert.deepEqual(actual, expected); 29 | }); 30 | 31 | it('is output', () => { 32 | gpio = new Gpio(pin, 'in'); 33 | 34 | const expected = 'out'; 35 | gpio.setDirection(expected); 36 | const actual = MockLinux.readDirection(pin); 37 | assert.deepEqual(actual, expected); 38 | }); 39 | 40 | 41 | afterEach(() => { 42 | gpio.unexport(); 43 | MockLinux.restore(); 44 | }); 45 | }); 46 | 47 | -------------------------------------------------------------------------------- /test/setEdge.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const mockRequire = require('mock-require'); 5 | const MockLinux = require('./mocks/linux'); 6 | const MockEpoll = require('./mocks/epoll'); 7 | 8 | mockRequire('epoll', MockEpoll); 9 | const Gpio = require('../onoff').Gpio; 10 | 11 | 12 | describe('setEdge', () => { 13 | let gpio; 14 | let pin; 15 | 16 | beforeEach(() => { 17 | pin = 4; 18 | MockLinux.gpio(pin); 19 | gpio = new Gpio(pin, 'in'); 20 | }); 21 | 22 | 23 | it('is rising', () => { 24 | const expected = 'rising'; 25 | gpio.setEdge(expected); 26 | const actual = MockLinux.readEdge(pin); 27 | assert.deepEqual(actual, expected); 28 | }); 29 | 30 | it('is falling', () => { 31 | const expected = 'out'; 32 | gpio.setEdge(expected); 33 | const actual = MockLinux.readEdge(pin); 34 | assert.deepEqual(actual, expected); 35 | }); 36 | 37 | it('is both', () => { 38 | const expected = 'out'; 39 | gpio.setEdge(expected); 40 | const actual = MockLinux.readEdge(pin); 41 | assert.deepEqual(actual, expected); 42 | }); 43 | 44 | 45 | afterEach(() => { 46 | gpio.unexport(); 47 | MockLinux.restore(); 48 | }); 49 | }); 50 | 51 | -------------------------------------------------------------------------------- /test/typedefinition.ts: -------------------------------------------------------------------------------- 1 | import assert = require('assert'); 2 | import mockRequire = require('mock-require'); 3 | import MockLinux = require('./mocks/linux'); 4 | import MockEpoll = require('./mocks/epoll'); 5 | 6 | mockRequire('epoll', MockEpoll); 7 | import { Gpio } from '../onoff'; 8 | 9 | describe('definition', () => { 10 | let gpio: Gpio; 11 | let pin: number; 12 | 13 | beforeEach(() => { 14 | pin = 4; 15 | MockLinux.gpio(pin); 16 | }); 17 | 18 | it('fires rising', (done) => { 19 | gpio = new Gpio(pin, 'in', 'both'); 20 | 21 | const expected = 1; 22 | gpio.watch((err, actual) => { 23 | assert.deepEqual(actual, expected); 24 | done(); 25 | }); 26 | MockLinux.write(pin, '' + expected); 27 | }); 28 | 29 | afterEach(() => { 30 | gpio.unexport(); 31 | MockLinux.restore(); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/utils/test-promise.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | const shouldEventuallyEqual = (actual, expected) => { 6 | return new Promise((resolve, reject) => { 7 | if(actual === expected) { 8 | resolve(); 9 | } else { 10 | reject(new Error( 11 | 'Actual value does not equal expected value: Act ' + 12 | actual + ' ' + typeof actual + 13 | ' | Exp ' + 14 | expected + ' ' + typeof expected 15 | )); 16 | } 17 | }); 18 | }; 19 | 20 | exports.shouldEventuallyEqual = shouldEventuallyEqual; 21 | 22 | -------------------------------------------------------------------------------- /test/watch-callbacks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const mockRequire = require('mock-require'); 5 | const MockEpoll = require('./mocks/epoll'); 6 | const MockLinux = require('./mocks/linux'); 7 | 8 | mockRequire('epoll', MockEpoll); 9 | const Gpio = require('../onoff').Gpio; 10 | 11 | describe('watch callbacks', () => { 12 | let gpio; 13 | let pin; 14 | 15 | beforeEach(() => { 16 | pin = 4; 17 | MockLinux.gpio(pin); 18 | }); 19 | 20 | describe('watch', () => { 21 | 22 | it('fires rising', (done) => { 23 | gpio = new Gpio(pin, 'in', 'both'); 24 | 25 | const expected = 1; 26 | MockLinux.write(pin, '' + expected); 27 | gpio.watch((err, actual) => { 28 | assert.deepEqual(actual, expected); 29 | done(); 30 | }); 31 | }); 32 | 33 | it('fires falling', (done) => { 34 | gpio = new Gpio(pin, 'in', 'both'); 35 | 36 | const expected = 0; 37 | MockLinux.write(pin, '' + expected); 38 | gpio.watch((err, actual) => { 39 | assert.deepEqual(actual, expected); 40 | done(); 41 | }); 42 | }); 43 | 44 | }); 45 | 46 | describe('watch with debounce', () => { 47 | 48 | it('fires rising', (done) => { 49 | gpio = new Gpio(pin, 'in', 'both', {debounceTimeout: 10}); 50 | 51 | const expected = 1; 52 | MockLinux.write(pin, '' + expected); 53 | gpio.watch((err, actual) => { 54 | assert.deepEqual(actual, expected); 55 | done(); 56 | }); 57 | }); 58 | 59 | it('fires falling', (done) => { 60 | gpio = new Gpio(pin, 'in', 'both', {debounceTimeout: 10}); 61 | 62 | const expected = 0; 63 | MockLinux.write(pin, '' + expected); 64 | gpio.watch((err, actual) => { 65 | assert.deepEqual(actual, expected); 66 | done(); 67 | }); 68 | }); 69 | 70 | }); 71 | 72 | afterEach(() => { 73 | gpio.unexport(); 74 | MockLinux.restore(); 75 | }); 76 | }); 77 | 78 | -------------------------------------------------------------------------------- /test/watch-listeners.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const mockRequire = require('mock-require'); 5 | const MockEpoll = require('./mocks/epoll'); 6 | const MockLinux = require('./mocks/linux'); 7 | 8 | mockRequire('epoll', MockEpoll); 9 | const Gpio = require('../onoff').Gpio; 10 | 11 | 12 | describe('watch listeners', () => { 13 | let gpio; 14 | let pin; 15 | 16 | const listener1 = (err, value) => { 17 | if (err) { 18 | throw err; 19 | } 20 | console.log(`listener1: value is ${value}`); 21 | }; 22 | 23 | const listener2 = (err, value) => { 24 | if (err) { 25 | throw err; 26 | } 27 | console.log(`listener2: value is ${value}`); 28 | }; 29 | 30 | beforeEach(() => { 31 | pin = 4; 32 | MockLinux.gpio(pin); 33 | gpio = new Gpio(pin, 'in', 'both'); 34 | }); 35 | 36 | describe('watch', () => { 37 | 38 | it('no listeners', () => { 39 | assert.deepEqual(gpio._listeners.length, 0); 40 | }); 41 | 42 | it('one listener', () => { 43 | gpio.watch(listener1); 44 | assert.deepEqual(gpio._listeners.length, 1); 45 | }); 46 | 47 | it('one listener multiple times', () => { 48 | gpio.watch(listener1); 49 | gpio.watch(listener1); 50 | assert.deepEqual(gpio._listeners.length, 2); 51 | }); 52 | 53 | it('multiple listeners', () => { 54 | gpio.watch(listener1); 55 | gpio.watch(listener2); 56 | assert.deepEqual(gpio._listeners.length, 2); 57 | }); 58 | }); 59 | 60 | describe('unwatch', () => { 61 | 62 | it('no listeners', () => { 63 | gpio.unwatch(listener1); 64 | assert.deepEqual(gpio._listeners.length, 0); 65 | }); 66 | 67 | it('one listener', () => { 68 | gpio.watch(listener1); 69 | gpio.unwatch(listener1); 70 | assert.deepEqual(gpio._listeners.length, 0); 71 | }); 72 | 73 | it('one listener, all referneces', () => { 74 | gpio.watch(listener1); 75 | gpio.watch(listener1); 76 | gpio.unwatch(listener1); 77 | assert.deepEqual(gpio._listeners.length, 0); 78 | }); 79 | 80 | it('mutliple listeners, only remove one', () => { 81 | gpio.watch(listener1); 82 | gpio.watch(listener2); 83 | gpio.unwatch(listener1); 84 | assert.deepEqual(gpio._listeners.length, 1); 85 | }); 86 | }); 87 | 88 | describe('unwatchAll', () => { 89 | 90 | it('no listeners', () => { 91 | gpio.unwatchAll(); 92 | assert.deepEqual(gpio._listeners.length, 0); 93 | }); 94 | 95 | 96 | it('one listener multiple times', () => { 97 | gpio.watch(listener1); 98 | gpio.watch(listener1); 99 | gpio.unwatchAll(listener1); 100 | assert.deepEqual(gpio._listeners.length, 0); 101 | }); 102 | 103 | it('multiple listeners', () => { 104 | gpio.watch(listener1); 105 | gpio.watch(listener2); 106 | gpio.unwatchAll(); 107 | assert.deepEqual(gpio._listeners.length, 0); 108 | }); 109 | }); 110 | 111 | afterEach(() => { 112 | gpio.unexport(); 113 | MockLinux.restore(); 114 | }); 115 | }); 116 | 117 | -------------------------------------------------------------------------------- /test/write.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const MockLinux = require('./mocks/linux'); 5 | const mockRequire = require('mock-require'); 6 | const MockEpoll = require('./mocks/epoll'); 7 | const TestHelper = require('./utils/test-promise'); 8 | 9 | mockRequire('epoll', MockEpoll); 10 | const Gpio = require('../onoff').Gpio; 11 | 12 | 13 | describe('write', () => { 14 | let gpio; 15 | let pin; 16 | 17 | beforeEach(() => { 18 | pin = 4; 19 | MockLinux.gpio(pin); 20 | gpio = new Gpio(pin, 'in'); 21 | }); 22 | 23 | 24 | it('writes high', (done) => { 25 | const expected = 1; 26 | gpio.write(expected, (err) => { 27 | const actual = MockLinux.read(pin); 28 | assert.deepEqual(actual, expected); 29 | done(); 30 | }); 31 | }); 32 | 33 | it('writes low', (done) => { 34 | const expected = 0; 35 | gpio.write(expected, (err) => { 36 | const actual = MockLinux.read(pin); 37 | assert.deepEqual(actual, expected); 38 | done(); 39 | }); 40 | }); 41 | 42 | afterEach(() => { 43 | gpio.unexport(); 44 | MockLinux.restore(); 45 | }); 46 | }); 47 | 48 | describe('write Promise', () => { 49 | let gpio; 50 | let pin; 51 | 52 | beforeEach(() => { 53 | pin = 4; 54 | MockLinux.gpio(pin); 55 | gpio = new Gpio(pin, 'in'); 56 | }); 57 | 58 | it('writes high', () => { 59 | const expected = 1; 60 | return gpio.write(expected).then(() => { 61 | const actual = parseInt(MockLinux.read(pin)); 62 | return TestHelper.shouldEventuallyEqual(actual, expected); 63 | }); 64 | }); 65 | 66 | it('writes low', () => { 67 | const expected = 0; 68 | return gpio.write(expected).then(() => { 69 | const actual = parseInt(MockLinux.read(pin)); 70 | return TestHelper.shouldEventuallyEqual(actual, expected); 71 | }); 72 | }); 73 | 74 | it('write fail',() => { 75 | const expected = 'EBADF'; 76 | 77 | const valueFd = gpio._valueFd; 78 | gpio._valueFd = 1e6; 79 | 80 | return gpio.write(1) 81 | .catch((err) => { 82 | gpio._valueFd = valueFd; 83 | const actual = err.code; 84 | return TestHelper.shouldEventuallyEqual(actual, expected); 85 | }); 86 | }); 87 | 88 | afterEach(() => { 89 | gpio.unexport(); 90 | MockLinux.restore(); 91 | }); 92 | }); 93 | 94 | -------------------------------------------------------------------------------- /test/writeSync.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const MockLinux = require('./mocks/linux'); 5 | const mockRequire = require('mock-require'); 6 | const MockEpoll = require('./mocks/epoll'); 7 | 8 | mockRequire('epoll', MockEpoll); 9 | const Gpio = require('../onoff').Gpio; 10 | 11 | 12 | describe('writeSync', () => { 13 | let gpio; 14 | let pin; 15 | 16 | beforeEach(() => { 17 | pin = 4; 18 | MockLinux.gpio(pin); 19 | gpio = new Gpio(pin, 'in'); 20 | }); 21 | 22 | 23 | it('writes high', () => { 24 | const expected = 1; 25 | gpio.writeSync(expected); 26 | const actual = MockLinux.read(pin); 27 | assert.deepEqual(actual, expected); 28 | }); 29 | 30 | it('writes low', () => { 31 | const expected = 0; 32 | gpio.writeSync(expected); 33 | const actual = MockLinux.read(pin); 34 | assert.deepEqual(actual, expected); 35 | }); 36 | 37 | 38 | afterEach(() => { 39 | gpio.unexport(); 40 | MockLinux.restore(); 41 | }); 42 | }); 43 | 44 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es6", "dom"], 4 | "module": "commonjs", 5 | "sourceMap": true, 6 | "strict": true, 7 | "target": "es6", 8 | }, 9 | "exclude": [ 10 | "node_modules" 11 | ] 12 | } --------------------------------------------------------------------------------