// boilerplate atkapi header const InOut = require('../../lib/jsunit.js') let Input = InOut.Input let Output = InOut.Output let State = InOut.State let Button = InOut.Button const SerialPort = require('serialport') function ATKSerialLink() { var atkSerialLink = { description: { isHardware: true, isLink: true, name: 'Serialport ATK Link', alt: 'window into hardware world' }, routes: new Array() } var state = State() atkSerialLink.state = state state.portName = '---' state.connect = Button('click to find and connect', findSerialPort) state.portStatus = 'closed' // or we hope it will be /* ------------------------------------------------------ HOOKING UP ------------------------------------------------------ */ atkSerialLink.attach = function(route) { console.log('PORT->LINK HOOKUP', route.route) this.routes.push(route) route.link = this } /* ------------------------------------------------------ SERIALPORT MANAGEMENT ------------------------------------------------------ */ // instance of SerialPort serialport = null function findSerialPort() { state.portStatus = 'searching for CP2102 device' var found = false SerialPort.list(function(err, ports) { ports.forEach(function(serialport) { if (serialport.manufacturer == 'Silicon Labs') { console.log('found cp2102 serialport') found = true state.portName = serialport.comName openSerialPort() } }) }) if(!found){ console.log('no CP2102 device found, try serialport-list to make sure it is available to the system') state.portStatus = 'no CP2102 device found' } } function openSerialPort() { if (state.portName == '---') { findSerialPort() } else { if (serialport == null) { serialport = new SerialPort(state.portName, { baudRate: 250000 }) serialport.on('open', function() { state.portStatus = 'open' }) serialport.on('error', function(err) { state.portStatus = err.message }) serialport.on('data', onSerialPortData) } } } /* ------------------------------------------------------ PACKETS TO HARDWARE ------------------------------------------------------ */ atkSerialLink.send = function(msg, route) { // it would be responsible to check this over now, but hey console.log('send', msg, 'on', route.route) // dereference this var pckt = JSON.parse(JSON.stringify(msg)) if (Array.isArray(pckt)) { pckt.unshift(255) // end delimiter pckt.unshift(254) // ptr var literalRoute = route.route.split(',') pckt = literalRoute.concat(pckt) // add route pckt.unshift(pckt.length + 1) // add length byte if (writeToSerialPort(pckt)) { console.log('PCKT OUT >>', pckt.toString(), '---------------') } else { // try to open ? openSerialPort() if (writeToSerialPort(pckt)) { console.log('PCKT OOT >>', pckt.toString()) } else { console.log('ERR: attempt to send to hardware, port not writable') } } } else { console.log('non-array on atkSerialLink input') } } // HERE this module is a hot mess function writeToSerialPort(pckt) { if (serialport != null && serialport.writable) { if (serialport.write(pckt)) { return true } else { console.log('------------------ !ACHTUNG! -------------------') console.log('------------------ !ACHTUNG! -------------------') console.log('---------- serialport.write(pckt) false --------------') // https://nodejs.org/api/stream.html#stream_event_drain // https://serialport.io/docs/api-stream return false } } else { return false } } /* ------------------------------------------------------ PACKETS FROM HARDWARE ------------------------------------------------------ */ var thisPacket = new Array() function onSerialPortData(data) { // we'll make sure it's what we think it will be // console.log("PORT DATA") var dtArray = new Array() if (Buffer.isBuffer(data)) { for (var i = 0; i < data.length; i++) { dtArray[i] = data[i] } } else { console.log("ERR: port data non-buffer") } //console.log('DATA IN', dtArray.toString()) thisPacket = thisPacket.concat(dtArray) if (thisPacket[0] <= 0) { thisPacket = [] console.log('throwing packet with leading zero') } while (thisPacket.length >= thisPacket[0]) { if (thisPacket.length == thisPacket[0]) { var packetCopy = thisPacket.slice(0) // copy, deref thisPacket = [] onPacket(packetCopy) } else { // rare case of two packets saddling break var fullPacket = thisPacket.slice(0, thisPacket[0]) onPacket(fullPacket) thisPacket = thisPacket.slice(thisPacket[0]) } } } function onPacket(pckt) { var debug = false // we're the last link, shift that pointer shiftPacketPointer(pckt) // log it console.log('PCKT IN <<', pckt.toString(), '-----------------------') // grab the routing header var incomingRoute = pckt.slice(2, pckt.indexOf(255)) // flip to match outgoing var returnRoute = new Array() for (i in incomingRoute) { returnRoute[i] = incomingRoute[incomingRoute.length - 1 - i] } // now we'll look for a reciprocal port from our list var match = false for (key in atkSerialLink.routes) { if (returnRoute.toString() === atkSerialLink.routes[key].route.toString()) { // strip header and return message var msg = pckt.slice(pckt.indexOf(255) + 1) // this slices down to the keys ... doesn't take the keys away match = true atkSerialLink.routes[key].onMessage(msg) } } if (!match) { console.log("PACKET RETURN AND NO KEY FOUND") console.log(pckt) } } function shiftPacketPointer(pckt) { var end = 0 var i = 0 while (end == 0) { if (pckt[i] === 255) { end = i } else if (i >= pckt.length) { break } i++ } //array[1] = 254 for (var j = 1; j < end - 1; j++) { pckt[j] = pckt[j + 1] } pckt[end - 1] = 0 // console.log('shifted', pckt) } return atkSerialLink } module.exports = ATKSerialLink