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