Initial commit of on/off gpio initially forked from uci-interrupt

master
David Kebler 2019-06-17 12:14:33 -07:00
commit 54d021fbbd
13 changed files with 441 additions and 0 deletions

33
.eslintrc.js Normal file
View File

@ -0,0 +1,33 @@
module.exports = {
"ecmaFeatures": {
"modules": true,
"spread" : true,
"restParams" : true
},
"env": {
"es6": true,
"node": true,
"mocha": true
},
"parserOptions": {
"ecmaVersion": 2017,
"sourceType": "module"
},
"extends": "eslint:recommended",
"rules": {
"indent": [
"error",
2
],
"no-console": 0,
"semi": ["error", "never"],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
]
}
}

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/node_modules/
/coverage/
*.lock
archive/

6
.npmignore Normal file
View File

@ -0,0 +1,6 @@
tests/
test/
*.test.js
testing/
examples/
archive/

27
examples/client.js Normal file
View File

@ -0,0 +1,27 @@
import Base from '@uci/base'
// const HOST = 'localhost'
const HOST = 'pine64.kebler.net'
const PORT = 9075
let client = new Base({sockets:'client#c>t', client:{host:HOST, port:PORT}, id:'gpios-client'})
// client.c.reply = async function (packet) {
// return new Promise((resolve) => {
// console.log('reply from gpio with packet')
// console.dir(packet)
// resolve()
// })
// }
;
(async () => {
await client.init()
console.log('RETURN', await client.send({cmd: 'on', pins:'all'}))
// console.log('RETURN', await client.send({cmd: 'off', pins:'all'}))
process.exit()
})().catch(err => {
console.error('FATAL: UNABLE TO START SYSTEM!\n',err)
})

54
examples/homeassistant.js Normal file
View File

@ -0,0 +1,54 @@
// TODO make this an HA plugin to support HA convention ---
///***************** HOME ASSISTANT ************************
// uses main broker socket but formats incoming and outgoing packets for HA convention
// TODO Make sure subscribed topics includes 'lighting/set/#'
const TOPIC = 'relays'
function register(name) {
this.beforeProcessHook(async (packet) => {
console.log('incoming mqtt packet to modify')
console.dir(packet)
let cmd = packet.cmd.split('/')
packet.cmd = `${packet.data.toLowerCase()}`
packet.pins = cmd[2]
delete packet.data
console.log('translated to uci packet')
console.dir(packet)
return packet
},
{name:name})
this.afterProcessHook(async (packet) => {
console.log('processed packet available to modify again', packet)
if (packet.error) {
let npacket = {}
npacket.cmd = 'error'
npacket.payload = JSON.stringify(packet)
// npacket.payload = packet.error
return npacket
}
return packet
},
{name:name})
this.beforeSendHook(async (packet) => {
console.log('beforeSendHook', packet)
if (packet.cmd === 'status') {
let num = Object.keys(packet.pins)[0]
packet.cmd = `${TOPIC}/status/${num}`
packet.payload = packet.pins[num] ? 'on' : 'off'
console.log('=============modified packet sent to broker================')
console.dir(packet)
console.log('================')
}
return packet
},
{name:name})
}
export default register

24
examples/multi.js Normal file
View File

@ -0,0 +1,24 @@
import Gpios from '../src/gpios'
import hahooks from './homeassistant.js'
const PINS = [80,73,69,230,229,361,75,74,70]
let relays = new Gpios(PINS, {pinOpts:{activeLow:true}})
const BROKER = 'nas.kebler.net'
const TCP_PORT = 9075
;
(async () => {
await relays.addSocket('mqs','s','m', {host:BROKER, topics:['relays/set/+']})
hahooks.call(relays,'mqs')
await relays.addSocket('tcp','s','t', {port:TCP_PORT})
await relays.addSocket('web','s','w')
await relays.init()
// await relays.getSocket('mqs').subscribe(['relays/#'])
})().catch(err => {
console.error('FATAL: UNABLE TO START SYSTEM!\n',err)
// process.kill(process.pid, 'SIGTERM')
})

41
package.json Normal file
View File

@ -0,0 +1,41 @@
{
"name": "@uci/gpio",
"main": "src",
"version": "0.1.10",
"description": "a class for adding simple on/off read of gpio pins on Raspberry Pi and Similar SBCs",
"scripts": {
"client": "node -r esm examples/client",
"multi": "node --require esm examples/multi",
"multilog": "UCI_ENV=dev node --require esm examples/multi"
},
"author": "David Kebler",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/uCOMmandIt/uci-interrrupt.git"
},
"keywords": [
"node.js",
"communication",
"serial",
"utilities",
"helpers"
],
"bugs": {
"url": "https://github.com/uCOMmandIt/uci-interrrupt/issues"
},
"homepage": "https://github.com/uCOMmandIt/uci-interrrupt#readme",
"dependencies": {
"@uci-utils/logger": "0.0.14",
"@uci/base": "^0.1.23",
"death": "^1.1.0",
"onoff": "^4.1.1",
"p-settle": "^3.1.0"
},
"devDependencies": {
"chai": "^4.1.2",
"esm": "^3.2.25",
"mocha": "^5.0.1",
"nodemon": "^1.14.3"
}
}

25
readme.md Normal file
View File

@ -0,0 +1,25 @@
# uCOMmandIt GPIO Package for SBC GPio Pins
<!-- find and replace the package name to match -->
[![Build Status](https://img.shields.io/travis/uCOMmandIt/uci-interrupt.svg?branch=master)](https://travis-ci.org/uCOMmandIt/uci-interrupt)
[![Inline docs](http://inch-ci.org/github/uCOMmandIt/uci-interrupt.svg?branch=master)](http://inch-ci.org/github/uCOMmandIt/uci-interrupt)
[![devDependencies](https://img.shields.io/david/dev/uCOMmandIt/uci-interrupt.svg)](https://david-dm.org/uCOMmandIt/uci-interrupt?type=dev)
[![codecov](https://img.shields.io/codecov/c/github/uCOMmandIt/uci-interrupt/master.svg)](https://codecov.io/gh/uCOMmandIt/uci-interrupt)
This module creates an instance of UCI Packet GPIO for a SBC gpio pin. This class does NOT include support for interrupt based input. See UCI-Interrupt package for that.
## Set up hardware GPio bus pins as interrupts for use with UCI Interrupt
### Enable access to GPios
create (if need be) a gpio group and make sure your user is in the `gpio` group
change group to gpio for /sys/class/gpio/export and unexport and mode to 770.
make a new udev rules file.
```
SUBSYSTEM=="gpio*", PROGRAM="/bin/sh -c 'find -L /sys/class/gpio/ -maxdepth 2 -exec chown root:gpio {} \; -exec chmod 770 {} \; || true'"
```
That should allow access via the on/off package which is a dependency.

54
src/commands.js Normal file
View File

@ -0,0 +1,54 @@
const pSettle = require('p-settle')
import logger from '@uci-utils/logger'
let log = logger({package:'@uci/gpio', file:'/src/commands.js'})
export default {
on: async function(packet) {
packet.pinCmd = 'on'
this.commands.set(packet)
return packet
},
off: async function(packet) {
packet.pinCmd = 'off'
await this.commands.set(packet)
return packet
},
toggle: async function(packet) {
packet.pinCmd = 'toggle'
await this.commands.set(packet)
return packet
},
set: async function(packet) {
packet.pinCmd = packet.pinCmd || 'set'
let pins = parsePins.call(this, packet.pins || packet.pin)
packet.res = await pSettle(
pins.map(pin => {
return this.pins[pin][packet.pinCmd](packet.value)
})
)
packet = await this.commands.get(packet)
this.emit('gpios',packet) // emit locally
this.push(packet) // will push on any socket
return packet
},
get: async function(packet) {
let pins = parsePins.call(this,packet.pins || packet.pin)
packet.pins = {}
pins.forEach(pin => packet.pins[pin] = this.pins[pin].value)
packet.cmd = 'status'
return packet
}
}
const parsePins = function(pins) {
if (typeof pins==='number') pins = [pins]
if (typeof pins==='string') {
if (pins==='all') pins = Object.keys(this.pins)
else {
pins = pins.split(/[,:\s]+/).map(Number).filter( (x) => !Number.isNaN(x))
}
}
// TODO plain object is pin and value pairs
return pins
}

73
src/gpio.js Normal file
View File

@ -0,0 +1,73 @@
import DeadJim from 'death'
import { Gpio } from 'onoff'
import to from 'await-to-js'
import logger from '@uci-utils/logger'
let log = logger({package:'@uci/gpio', file:'/src/gpio.js'})
class Pin extends Gpio {
constructor(pin, opts = {}) {
if (typeof pin !=='number') pin = parseInt(pin) // make sure pin is a number!
// TODO check against a set of known acceptable pin numbers based on SBC
log.debug({pin:pin, msg:'construtor - creating gpio pin'})
if (isNaN(pin)) throw new Error('not a valid gpio pin number')
super(pin,opts.direction || 'out',opts)
this.id = (opts.id || 'gpio') + ':' + pin
log.debug({ pin: pin, opts: opts, method:'constructor', line:15, msg:'created gpio with these opts'})
this.number = pin
this.direction = opts.direction || 'out'
this.value = opts.init || 0
} // end constructor
async init() {
this.set(this.value)
DeadJim((signal,err) => {
log.warn({signal:signal, method:'init', line:56, error:err, msg:'gpio process was killed, unexporting pin'})
this.unexport() // kill the kernel entry
})
}
async on() { return await this.set(1) }
async off() { return await this.set(0) }
async toggle() { return await this.set(!this.value) }
async set(value) {
let [err] = await to(this.write(parseInt(value) || 0))
if (err) {
log.warn({error:err, pin:this.number, msg:'error reading gpio pin'})
throw err
}
let curValue = await this.read()
if (value !== curValue) throw new Error(`pin ${this.number} was not set correctly: ${value}:${curValue}`)
return curValue
}
async read() {
let [err, res] = await to(super.read())
if (err) {
log.warn({error:err, pin:this.pin, msg:'error reading gpio pin'})
return err
}
this.value=res
this.state = res ? 'on' : 'off'
return res
}
async get() { return this.value }
} // end Class
export default Pin
//example hook
// async function defaultHook(packet) {
// return a promise or use await if anything async happens in hook
// new Promise((resolve) => {
// console.log('==========default hook =============')
// return packet
// resolve(packet)
// })
// }

49
src/gpios.js Normal file
View File

@ -0,0 +1,49 @@
import Base from '@uci/base'
import logger from '@uci-utils/logger'
import Gpio from './gpio'
import commands from './commands'
let log = {}
// pins is array of gpio pin numbers, options is object where pin number is a key for that pin's options (if needed)
// by default a pin is a simple output
// for single pin just use the Gpio Class itself
class Gpios extends Base {
constructor(pins, opts={}) {
super(opts)
this.id = this.id || 'gpios'
this.pins = {}
log = logger({ name: 'gpios', id: this.id, package:'@uci/gpios', file:'src/gpios.js'})
pins.forEach(pin => {
if (typeof pin !=='number') pin = parseInt(pin)
opts[pin] = opts[pin] ? opts[pin] : opts.pinOpts
log.debug({ opts: opts[pin], line:27, msg:`pin options for pin ${pin}`})
this.pins[pin] = new Gpio(pin, opts[pin])
})
this.commands = this.bindFuncs(commands)
this.addNamespace('commands', 's') // give access to these commands above if a socket/server is created
}
async init() {
await super.init()
return Promise.all(
Object.keys(this.pins).map(pin => {
return this.pins[pin].init()
})
)
}
// register same hook to all pins
registerHook(func) {
this.pins.forEach(async pin => {
this.pins[pin].registerHook(func)
})
}
} // end Class
export default Gpios

5
src/index.js Normal file
View File

@ -0,0 +1,5 @@
import Gpio from './gpio'
import Gpios from './gpios'
export { Gpio, Gpios }
export default Gpio

46
test/gpio.test.js Normal file
View File

@ -0,0 +1,46 @@
'use strict'
const
Gpio = require('../src/interrupt'),
expect = require('chai').expect
// TODO finish simple test of a pin as interrupt
describe(
'Testing ',
function () {
hooks()
interrupt()
someothertests()
})
//****************** TESTS **********************
function interrupt() {
it('==> tests an interrupt', async function () {
expect(result, 'test failed').to.equal('expectedresult')
})
}
function someothertests() {
it('==> test something', async function () {
let result = await someasyncfunction()
expect(result, 'test failed').to.equal('expectedresult')
await pause(1000)
})
}
function hooks() {
before(async() => {
})
beforeEach(async() => {})
after(async() => {})
}