#!/usr/bin/nodejs /* Simple Command-line app loader for Node.js =============================================== NOTE: This needs the '@abandonware/noble' library to be installed. However we don't want this in package.json (at least as a normal dependency) because we want `sanitycheck.js` to be able to run *quickly* in travis for every commit, and we don't want NPM pulling in (and compiling native modules) for Noble. */ var SETTINGS = { pretokenise : true }; var Utils = require("../core/js/utils.js"); var AppInfo = require("../core/js/appinfo.js"); var noble; try { noble = require('@abandonware/noble'); } catch (e) {} if (!noble) try { noble = require('noble'); } catch (e) { } if (!noble) { console.log("You need to:") console.log(" npm install @abandonware/noble") console.log("or:") console.log(" npm install noble") } var apps; function ERROR(msg) { console.error(msg); process.exit(1); } try { apps = JSON.parse(require("fs").readFileSync(__dirname+"/../apps.json")); } catch(e) { ERROR("'apps.json' could not be loaded"); } var args = process.argv; if (args.length==3 && args[2]=="list") cmdListApps(); else if (args.length==3 && args[2]=="devices") cmdListDevices(); else if (args.length==4 && args[2]=="install") cmdInstallApp(args[3]); else if (args.length==5 && args[2]=="install") cmdInstallApp(args[3], args[4]); else { console.log(`apploader.js ------------- USAGE: apploader.js list - list available apps apploader.js devices - list available device addresses apploader.js install appname [de:vi:ce:ad:dr:es] `); process.exit(0); } function cmdListApps() { console.log(apps.map(a=>a.id).join("\n")); } function cmdListDevices() { var foundDevices = []; noble.on('discover', function(dev) { if (!dev.advertisement) return; if (!dev.advertisement.localName) return; var a = dev.address.toString(); if (foundDevices.indexOf(a)>=0) return; foundDevices.push(a); console.log(a,dev.advertisement.localName); }); noble.startScanning([], true); setTimeout(function() { console.log("Stopping scan"); noble.stopScanning(); setTimeout(function() { process.exit(0); }, 500); }, 4000); } function cmdInstallApp(appId, deviceAddress) { var app = apps.find(a=>a.id==appId); if (!app) ERROR(`App ${JSON.stringify(appId)} not found`); if (app.custom) ERROR(`App ${JSON.stringify(appId)} requires HTML customisation`); return AppInfo.getFiles(app, { fileGetter:function(url) { console.log(__dirname+"/"+url); return Promise.resolve(require("fs").readFileSync(__dirname+"/../"+url).toString("binary")); }, settings : SETTINGS}).then(files => { //console.log(files); var command = files.map(f=>f.cmd).join("\n")+"\n"; bangleSend(command, deviceAddress).then(() => process.exit(0)); }); } function bangleSend(command, deviceAddress) { var log = function() { var args = [].slice.call(arguments); console.log("UART: "+args.join(" ")); } //console.log("Sending",JSON.stringify(command)); var RESET = true; var DEVICEADDRESS = ""; if (deviceAddress!==undefined) DEVICEADDRESS = deviceAddress; var complete = false; var foundDevices = []; var flowControlPaused = false; var btDevice; var txCharacteristic; var rxCharacteristic; return new Promise((resolve,reject) => { function foundDevice(dev) { if (btDevice!==undefined) return; log("Connecting to "+dev.address); noble.stopScanning(); connect(dev, function() { // Connected! function writeCode() { log("Writing code..."); write(command, function() { complete = true; btDevice.disconnect(); }); } if (RESET) { setTimeout(function() { log("Resetting..."); write("\x03\x10reset()\n", function() { setTimeout(writeCode, 1000); }); }, 500); } else setTimeout(writeCode, 1000); }); } function connect(dev, callback) { btDevice = dev; log("BT> Connecting"); btDevice.on('disconnect', function() { log("Disconnected"); setTimeout(function() { if (complete) resolve(); else reject("Disconnected but not complete"); }, 500); }); btDevice.connect(function (error) { if (error) { log("BT> ERROR Connecting",error); btDevice = undefined; return; } log("BT> Connected"); btDevice.discoverAllServicesAndCharacteristics(function(error, services, characteristics) { function findByUUID(list, uuid) { for (var i=0;i ERROR getting services/characteristics"); log("Service "+btUARTService); log("TX "+txCharacteristic); log("RX "+rxCharacteristic); btDevice.disconnect(); txCharacteristic = undefined; rxCharacteristic = undefined; btDevice = undefined; return openCallback(); } rxCharacteristic.on('data', function (data) { var s = ""; for (var i=0;i=10) { log("Writing "+amt+"/"+total); progress=0; } //log("Writing ",JSON.stringify(d)); amt += d.length; for (var i = 0; i < buf.length; i++) buf.writeUInt8(d.charCodeAt(i), i); txCharacteristic.write(buf, false, writeAgain); } writeAgain(); } function disconnect() { btDevice.disconnect(); } log("Discovering..."); noble.on('discover', function(dev) { if (!dev.advertisement) return; if (!dev.advertisement.localName) return; var a = dev.address.toString(); if (foundDevices.indexOf(a)>=0) return; foundDevices.push(a); log("Found device: ",a,dev.advertisement.localName); if (a == DEVICEADDRESS) return foundDevice(dev); else if (DEVICEADDRESS=="" && dev.advertisement.localName.indexOf("Bangle.js")==0) { return foundDevice(dev); } }); noble.startScanning([], true); }); }