1
0
Fork 0
mirror of https://github.com/chirimen-oh/node-web-gpio.git synced 2025-03-26 08:05:19 +00:00

create @notweb/gpio

This commit is contained in:
Nebel 2019-10-13 02:13:19 +09:00
commit aab3397bf9
9 changed files with 508 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/node_modules/

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Kohei Watanabe
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

27
README.md Normal file
View file

@ -0,0 +1,27 @@
# @notweb/gpio
GPIO access with Node.js
## Usage
```js
const { requestGPIOAccess } = require("@notweb/gpio");
async function main() {
const gpioAccess = await requestGPIOAccess();
const port = gpioAccess.ports.get(26);
await port.export("out");
for (;;) {
port.write(value);
await new Promise(resolve => setTimeout(resolve, 1e3));
}
}
main();
```
## Document
[Web GPIO API](http://browserobo.github.io/WebGPIO)

57
index.d.ts vendored Normal file
View file

@ -0,0 +1,57 @@
/// <reference types="node" />
import { EventEmitter } from "events";
declare type PortNumber = number;
declare type PortName = string;
declare type PinName = string;
declare type DirectionMode = "in" | "out";
declare type GPIOValue = 0 | 1;
interface GPIOChangeEvent {
readonly value: GPIOValue;
readonly port: GPIOPort;
}
interface GPIOChangeEventHandler {
(event: GPIOChangeEvent): void;
}
/**
* Not a specification in Web GPIO API.
*/
interface GPIOPortChangeEventHandler {
(event: GPIOChangeEvent["value"]): void;
}
export declare class GPIOAccess extends EventEmitter {
private readonly _ports;
onchange: GPIOChangeEventHandler | undefined;
constructor(ports?: GPIOPortMap);
readonly ports: GPIOPortMap;
/**
* Unexport all exported GPIO ports.
*/
unexportAll(): Promise<void>;
}
export declare class GPIOPortMap extends Map<PortNumber, GPIOPort> {
}
export declare class GPIOPort extends EventEmitter {
private readonly _portNumber;
private readonly _pollingInterval;
private _direction;
private _exported;
private _value;
private _timeout;
onchange: GPIOPortChangeEventHandler | undefined;
constructor(portNumber: PortNumber);
readonly portNumber: PortNumber;
readonly portName: PortName;
readonly pinName: PinName;
readonly direction: DirectionMode;
readonly exported: boolean;
export(direction: DirectionMode): Promise<void>;
unexport(): Promise<void>;
read(): Promise<GPIOValue>;
write(value: GPIOValue): Promise<void>;
}
export declare class InvalidAccessError extends Error {
}
export declare class OperationError extends Error {
}
export declare function requestGPIOAccess(): Promise<GPIOAccess>;
export {};

142
index.js Normal file
View file

@ -0,0 +1,142 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const events_1 = require("events");
const fs_1 = require("fs");
/**
* Interval of file system polling, in milliseconds.
*/
const PollingInterval = 100;
const Uint16Max = 65535;
function parseUint16(string) {
const n = Number.parseInt(string, 10);
if (0 <= n && n <= Uint16Max)
return n;
else
throw new RangeError(`Must be between 0 and ${Uint16Max}.`);
}
class GPIOAccess extends events_1.EventEmitter {
constructor(ports) {
super();
this._ports = ports == null ? new GPIOPortMap() : ports;
this._ports.forEach(port => port.on("change", value => {
const event = { value, port };
this.emit("change", event);
}));
this.on("change", (event) => {
if (this.onchange !== undefined)
this.onchange(event);
});
}
get ports() {
return this._ports;
}
/**
* Unexport all exported GPIO ports.
*/
async unexportAll() {
await Promise.all([...this.ports.values()].map(port => port.exported ? port.unexport() : undefined));
}
}
exports.GPIOAccess = GPIOAccess;
class GPIOPortMap extends Map {
}
exports.GPIOPortMap = GPIOPortMap;
class GPIOPort extends events_1.EventEmitter {
constructor(portNumber) {
super();
this._portNumber = portNumber;
this._pollingInterval = PollingInterval;
this._direction = new OperationError("Unknown direction.");
this._exported = new OperationError("Unknown export.");
this.on("change", (value) => {
if (this.onchange !== undefined)
this.onchange(value);
});
}
get portNumber() {
return this._portNumber;
}
get portName() {
// NOTE: Unknown portName.
return "";
}
get pinName() {
// NOTE: Unknown pinName.
return "";
}
get direction() {
if (this._direction instanceof OperationError)
throw this._direction;
return this._direction;
}
get exported() {
if (this._exported instanceof OperationError)
throw this._exported;
return this._exported;
}
async export(direction) {
if (!/^(in|out)$/.test(direction)) {
throw new InvalidAccessError(`Must be "in" or "out".`);
}
try {
clearInterval(this._timeout);
await fs_1.promises.writeFile(`/sys/class/gpio/export`, parseUint16(this.portNumber.toString()).toString());
await fs_1.promises.writeFile(`/sys/class/gpio/${parseUint16(this.portNumber.toString())}/direction`, direction);
if (direction === "in") {
this._timeout = setInterval(this.read.bind(this), this._pollingInterval);
}
}
catch (error) {
throw new OperationError(error);
}
this._direction = direction;
this._exported = true;
}
async unexport() {
clearInterval(this._timeout);
try {
await fs_1.promises.writeFile(`/sys/class/gpio/unexport`, parseUint16(this.portNumber.toString()).toString());
}
catch (error) {
throw new OperationError(error);
}
this._exported = false;
}
async read() {
try {
const buffer = await fs_1.promises.readFile(`/sys/class/gpio/${parseUint16(this.portNumber.toString())}/value`);
const value = parseUint16(buffer.toString());
if (this._value !== value) {
this._value = value;
this.emit("change", value);
}
return value;
}
catch (error) {
throw new OperationError(error);
}
}
async write(value) {
try {
await fs_1.promises.writeFile(`/sys/class/gpio/${parseUint16(this.portNumber.toString())}/value`, parseUint16(value.toString()).toString());
}
catch (error) {
throw new OperationError(error);
}
}
}
exports.GPIOPort = GPIOPort;
class InvalidAccessError extends Error {
}
exports.InvalidAccessError = InvalidAccessError;
class OperationError extends Error {
}
exports.OperationError = OperationError;
async function requestGPIOAccess() {
const ports = new GPIOPortMap([...Array(Uint16Max + 1).keys()].map(portNumber => [
portNumber,
new GPIOPort(portNumber)
]));
return new GPIOAccess(ports);
}
exports.requestGPIOAccess = requestGPIOAccess;

213
index.ts Normal file
View file

@ -0,0 +1,213 @@
import { EventEmitter } from "events";
import { promises as fs } from "fs";
/**
* Interval of file system polling, in milliseconds.
*/
const PollingInterval = 100;
const Uint16Max = 65535;
function parseUint16(string: string) {
const n = Number.parseInt(string, 10);
if (0 <= n && n <= Uint16Max) return n;
else throw new RangeError(`Must be between 0 and ${Uint16Max}.`);
}
type PortNumber = number;
type PortName = string;
type PinName = string;
type DirectionMode = "in" | "out";
type GPIOValue = 0 | 1;
interface GPIOChangeEvent {
readonly value: GPIOValue;
readonly port: GPIOPort;
}
interface GPIOChangeEventHandler {
(event: GPIOChangeEvent): void;
}
/**
* Not a specification in Web GPIO API.
*/
interface GPIOPortChangeEventHandler {
(event: GPIOChangeEvent["value"]): void;
}
export class GPIOAccess extends EventEmitter {
private readonly _ports: GPIOPortMap;
onchange: GPIOChangeEventHandler | undefined;
constructor(ports?: GPIOPortMap) {
super();
this._ports = ports == null ? new GPIOPortMap() : ports;
this._ports.forEach(port =>
port.on("change", value => {
const event: GPIOChangeEvent = { value, port };
this.emit("change", event);
})
);
this.on("change", (event: GPIOChangeEvent): void => {
if (this.onchange !== undefined) this.onchange(event);
});
}
get ports(): GPIOPortMap {
return this._ports;
}
/**
* Unexport all exported GPIO ports.
*/
async unexportAll() {
await Promise.all(
[...this.ports.values()].map(port =>
port.exported ? port.unexport() : undefined
)
);
}
}
export class GPIOPortMap extends Map<PortNumber, GPIOPort> {}
export class GPIOPort extends EventEmitter {
private readonly _portNumber: PortNumber;
private readonly _pollingInterval: number;
private _direction: DirectionMode | OperationError;
private _exported: boolean | OperationError;
private _value: GPIOValue | undefined;
private _timeout: ReturnType<typeof setInterval> | undefined;
onchange: GPIOPortChangeEventHandler | undefined;
constructor(portNumber: PortNumber) {
super();
this._portNumber = portNumber;
this._pollingInterval = PollingInterval;
this._direction = new OperationError("Unknown direction.");
this._exported = new OperationError("Unknown export.");
this.on("change", (value: GPIOChangeEvent["value"]): void => {
if (this.onchange !== undefined) this.onchange(value);
});
}
get portNumber(): PortNumber {
return this._portNumber;
}
get portName(): PortName {
// NOTE: Unknown portName.
return "";
}
get pinName(): PinName {
// NOTE: Unknown pinName.
return "";
}
get direction(): DirectionMode {
if (this._direction instanceof OperationError) throw this._direction;
return this._direction;
}
get exported(): boolean {
if (this._exported instanceof OperationError) throw this._exported;
return this._exported;
}
async export(direction: DirectionMode) {
if (!/^(in|out)$/.test(direction)) {
throw new InvalidAccessError(`Must be "in" or "out".`);
}
try {
clearInterval(this._timeout as any);
await fs.writeFile(
`/sys/class/gpio/export`,
parseUint16(this.portNumber.toString()).toString()
);
await fs.writeFile(
`/sys/class/gpio/${parseUint16(this.portNumber.toString())}/direction`,
direction
);
if (direction === "in") {
this._timeout = setInterval(
this.read.bind(this),
this._pollingInterval
);
}
} catch (error) {
throw new OperationError(error);
}
this._direction = direction;
this._exported = true;
}
async unexport() {
clearInterval(this._timeout as any);
try {
await fs.writeFile(
`/sys/class/gpio/unexport`,
parseUint16(this.portNumber.toString()).toString()
);
} catch (error) {
throw new OperationError(error);
}
this._exported = false;
}
async read() {
try {
const buffer = await fs.readFile(
`/sys/class/gpio/${parseUint16(this.portNumber.toString())}/value`
);
const value = parseUint16(buffer.toString()) as GPIOValue;
if (this._value !== value) {
this._value = value;
this.emit("change", value);
}
return value;
} catch (error) {
throw new OperationError(error);
}
}
async write(value: GPIOValue) {
try {
await fs.writeFile(
`/sys/class/gpio/${parseUint16(this.portNumber.toString())}/value`,
parseUint16(value.toString()).toString()
);
} catch (error) {
throw new OperationError(error);
}
}
}
export class InvalidAccessError extends Error {}
export class OperationError extends Error {}
export async function requestGPIOAccess(): Promise<GPIOAccess> {
const ports = new GPIOPortMap(
[...Array(Uint16Max + 1).keys()].map(portNumber => [
portNumber,
new GPIOPort(portNumber)
])
);
return new GPIOAccess(ports);
}

22
package.json Normal file
View file

@ -0,0 +1,22 @@
{
"name": "@notweb/gpio",
"version": "0.0.1",
"description": "GPIO access with Node.js",
"main": "index.js",
"repository": {
"type": "git",
"url": "https://github.com/kou029w/notweb-gpio.git"
},
"author": "Kohei Watanabe <kou029w@gmail.com>",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@types/node": "^12.7.12",
"typescript": "^3.6.4"
},
"scripts": {
"build": "tsc"
}
}

12
tsconfig.json Normal file
View file

@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"moduleResolution": "node",
"declaration": true,
"strict": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
}

13
yarn.lock Normal file
View file

@ -0,0 +1,13 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@types/node@^12.7.12":
version "12.7.12"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.7.12.tgz#7c6c571cc2f3f3ac4a59a5f2bd48f5bdbc8653cc"
integrity sha512-KPYGmfD0/b1eXurQ59fXD1GBzhSQfz6/lKBxkaHX9dKTzjXbK68Zt7yGUxUsCS1jeTy/8aL+d9JEr+S54mpkWQ==
typescript@^3.6.4:
version "3.6.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.4.tgz#b18752bb3792bc1a0281335f7f6ebf1bbfc5b91d"
integrity sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==