import pify from 'pify' import to from 'await-to-js' import i2c from 'i2c-bus' import PQueue from 'p-queue' import Base from '@uci/base' import logger from '@uci-utils/logger' let log = {} class I2CBus extends Base { constructor(opts) { opts.path =opts.path || 'i2c-bus' // always a pipe by default if (typeof opts.port === 'boolean') opts.port=1776 opts.name = opts.name || 'i2c-bus' super(opts) log = logger({ name: 'i2c-bus', id: this.id, file: 'src/bus.js', class: 'Bus' }) this.busnum = opts.busnum || 1 this.i2cbus = i2c.open(this.busnum, () => {}) this._funcs = bus_funcs this.bus = {} this.addBusFuncs() this.addNamespace('bus') this.bus.busFuncs = async (packet) => { packet.functions = this.busFuncs packet.cmd='reply' return packet } this.bus.ack = async function (){ // let bus = await this.bus.scan() if (bus.error || !bus.response.length) bus.ack =false else bus.ack = true return bus }, this.ackInterval = opts.ackInterval!=null ? opts.ackInterval : 5 this._queue = new PQueue({concurrency: opts.concurrency || 1}) this.ready.addObserver('bus:ack') } // constructor async init() { await super.init() let count = 0 const ack = (await this.bus.ack.call(this)).ack this.emit('bus:ack',ack) // will be picked up by ready observer this.autoAck(this.ackInterval) // if auto on then will notice when there is issue/connection with i2cbus this._queue.on('active', () => { log.debug(`Queue: working on item #${++count}. Size: ${this._queue.size} Pending: ${this._queue.pending}`) }) } get busFuncs() { return this._funcs} autoAck(interval) { if (interval>.9) this._autoAck = setInterval(async ()=> { let ack = (await this.bus.ack.call(this)).ack this.emit('bus:ack',ack) // will be picked up by ready observer },interval*1000) else clearInterval(this._autoAck) } addBusFuncs() { for (let alias in this.busFuncs) { let func = this.busFuncs[alias] this.addBusFunc(alias,func.name || alias, func.args) } } addBusFunc(alias,name, args) { if (!name) name=alias if (Array.isArray(name)) { args=name name=alias } this.bus[alias] = busFunction.bind(this, alias, pify(this.i2cbus[name]), args ||[]) } } // end of Bus Packet Class export default I2CBus const validateArg = function (arg,value) { let valid = false switch (arg) { case 'address': valid = Number.isInteger(value) && value >=0 && value <= 119 break case 'length': case 'cmd': valid = Number.isInteger(value) break case 'byte': valid = Number.isInteger(value) && value >= 0 && value <= 255 break case 'word': valid = Number.isInteger(value) && value >= 0 && value <= 65535 break case 'bit': valid = Number.isInteger(value) && (value === 0 || value === 1) break case 'buffer': valid = Buffer.isBuffer(value) } return valid } async function busFunction (name, func, args=[],packet={}) { let argsV = [] args.some(arg => { if (packet.args) { let argv = packet.args[arg] if (argv != null) { if(validateArg(arg,argv)) argsV.push(argv) else { packet.error = `argument ${arg} has an invalid value ${argv} for function ${name}` return true } } else { packet.error = `missing argument, ${arg}, for function ${name}` return true } } }) // this.emit('log', {level:'i2c', packet:packet, error:packet.error, msg:'packet going to bus'}) if (!packet.error) { log.trace({msg:'adding to queue', function:name,arguments:argsV}) let [err,res] = await to(this._queue.add(() => func.call(this.i2cbus,...argsV))) if (err) packet.error = `error during call to ${name} with arguments ${JSON.stringify(packet.args)}: ${err}` else packet.response = res } packet.cmd = 'reply' return packet } // default function set const bus_funcs = { scan: {}, close: {}, methods: {name:'i2cFuncs'}, deviceId: {args:['address']}, readRaw: {name:'i2cRead', args:['address','length','buffer']}, writeRaw: {name:'i2cWrite', args:['address','length','buffer']}, read: {name:'readByte', args:['address','cmd']}, write: {name:'writeByte', args:['address','cmd','byte']}, read2: {name:'readWord', args:['address','cmd']}, write2: {name:'writeWord', args:['address','cmd','word']}, receive: {name:'receiveByte', args:['address']}, send: {name:'sendByte', args:['address','byte']} }