Initial commit of on/off gpio initially forked from uci-interrupt
commit
54d021fbbd
|
@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
/node_modules/
|
||||||
|
/coverage/
|
||||||
|
*.lock
|
||||||
|
archive/
|
|
@ -0,0 +1,6 @@
|
||||||
|
tests/
|
||||||
|
test/
|
||||||
|
*.test.js
|
||||||
|
testing/
|
||||||
|
examples/
|
||||||
|
archive/
|
|
@ -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)
|
||||||
|
})
|
|
@ -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
|
|
@ -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')
|
||||||
|
})
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
// })
|
||||||
|
// }
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
||||||
|
import Gpio from './gpio'
|
||||||
|
import Gpios from './gpios'
|
||||||
|
|
||||||
|
export { Gpio, Gpios }
|
||||||
|
export default Gpio
|
|
@ -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() => {})
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue