Merge branch 'master' of https://github.com/sixteenmillimeter/intval3
# Please enter a commit message to explain why this merge is necessary, # especially if it merges an updated upstream into a topic branch. # # Lines starting with '#' will be ignored, and an empty message aborts # the commit. Merge server changes with more recent app changes.
This commit is contained in:
commit
a87e81f128
3
index.js
3
index.js
|
@ -655,6 +655,9 @@ async function index (req, res, next) {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return next(err)
|
return next(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res.end(data)
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
function init () {
|
function init () {
|
||||||
|
|
381
lib/ble/index.js
381
lib/ble/index.js
|
@ -1,163 +1,158 @@
|
||||||
'use strict'
|
'use strict';
|
||||||
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||||
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||||
|
};
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
/** @module ble */
|
/** @module ble */
|
||||||
/** Bluetooth Low Energy module */
|
/** Bluetooth Low Energy module */
|
||||||
|
const util_1 = require("util");
|
||||||
const util = require('util')
|
const os_1 = require("os");
|
||||||
const os = require('os')
|
const fs_extra_1 = require("fs-extra");
|
||||||
|
const log = require('../log')('ble');
|
||||||
const log = require('../log')('ble')
|
const wifi_1 = require("../wifi");
|
||||||
const wifi = require('../wifi')
|
const wifi = new wifi_1.Wifi();
|
||||||
|
const DEVICE_NAME = process.env.DEVICE_NAME || 'intval3';
|
||||||
const DEVICE_NAME = process.env.DEVICE_NAME || 'intval3'
|
const SERVICE_ID = process.env.SERVICE_ID || 'intval3_ble';
|
||||||
const SERVICE_ID = process.env.SERVICE_ID || 'intval3_ble'
|
const CHAR_ID = process.env.CHAR_ID || 'intval3char';
|
||||||
const CHAR_ID = process.env.CHAR_ID || 'intval3char'
|
const WIFI_ID = process.env.WIFI_ID || 'wifichar';
|
||||||
const WIFI_ID = process.env.WIFI_ID || 'wifichar'
|
const NETWORK = os_1.networkInterfaces(); //?type?
|
||||||
const NETWORK = os.networkInterfaces()
|
const MAC = getMac() || spoofMac();
|
||||||
const MAC = getMac() || spoofMac()
|
|
||||||
|
|
||||||
//Give the device a unique device name, needs to be in env
|
//Give the device a unique device name, needs to be in env
|
||||||
process.env.BLENO_DEVICE_NAME += '_' + MAC
|
process.env.BLENO_DEVICE_NAME += '_' + MAC;
|
||||||
const bleno = require('bleno')
|
const bleno_1 = __importDefault(require("bleno"));
|
||||||
|
const { Characteristic } = bleno_1.default;
|
||||||
|
let currentWifi = 'disconnected';
|
||||||
let currentWifi = 'disconnected'
|
let currentAddr = null;
|
||||||
let currentAddr = null
|
let getState;
|
||||||
let getState
|
const chars = [];
|
||||||
|
|
||||||
const chars = []
|
|
||||||
|
|
||||||
function createChar(name, uuid, prop, write, read) {
|
function createChar(name, uuid, prop, write, read) {
|
||||||
function characteristic () {
|
const characteristic = function () {
|
||||||
bleno.Characteristic.call(this, {
|
Characteristic.call(this, {
|
||||||
uuid : uuid,
|
uuid,
|
||||||
properties: prop
|
properties: prop
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
util.inherits(characteristic, bleno.Characteristic)
|
util_1.inherits(characteristic, Characteristic);
|
||||||
if (prop.indexOf('read') !== -1) {
|
if (prop.indexOf('read') !== -1) {
|
||||||
//data, offset, withoutResponse, callback
|
//data, offset, withoutResponse, callback
|
||||||
characteristic.prototype.onReadRequest = read
|
characteristic.prototype.onReadRequest = read;
|
||||||
}
|
}
|
||||||
if (prop.indexOf('write') !== -1) {
|
if (prop.indexOf('write') !== -1) {
|
||||||
characteristic.prototype.onWriteRequest = write
|
characteristic.prototype.onWriteRequest = write;
|
||||||
}
|
}
|
||||||
chars.push(new characteristic())
|
chars.push(new characteristic());
|
||||||
}
|
}
|
||||||
|
function createChars(onWrite, onRead) {
|
||||||
function createChars (onWrite, onRead) {
|
const permissions = ['read', 'write'];
|
||||||
createChar('intval3', CHAR_ID, ['read', 'write'], onWrite, onRead)
|
createChar('intval3', CHAR_ID, permissions, onWrite, onRead);
|
||||||
createChar('wifi', WIFI_ID, ['read', 'write'], onWifiWrite, onWifiRead)
|
createChar('wifi', WIFI_ID, permissions, onWifiWrite, onWifiRead);
|
||||||
}
|
}
|
||||||
|
async function onWifiWrite(data, offset) {
|
||||||
function onWifiWrite (data, offset, withoutResponse, callback) {
|
let result;
|
||||||
let result
|
let utf8;
|
||||||
let utf8
|
let obj = {};
|
||||||
let obj
|
let ssid;
|
||||||
let ssid
|
let pwd;
|
||||||
let pwd
|
let psk;
|
||||||
if (offset) {
|
if (offset) {
|
||||||
log.warn(`Offset scenario`)
|
log.warn(`Offset scenario`);
|
||||||
result = bleno.Characteristic.RESULT_ATTR_NOT_LONG
|
result = bleno_1.default.Characteristic.RESULT_ATTR_NOT_LONG;
|
||||||
return callback(result)
|
return result;
|
||||||
}
|
}
|
||||||
utf8 = data.toString('utf8')
|
utf8 = data.toString('utf8');
|
||||||
obj = JSON.parse(utf8)
|
obj = JSON.parse(utf8);
|
||||||
ssid = obj.ssid
|
ssid = obj.ssid;
|
||||||
pwd = obj.pwd
|
pwd = obj.pwd;
|
||||||
log.info(`connecting to AP`, { ssid : ssid })
|
log.info(`connecting to AP`, { ssid: ssid });
|
||||||
return wifi.createPSK(ssid, pwd, (err, hash, plaintext) => {
|
try {
|
||||||
if (err) {
|
psk = await wifi.createPSK(ssid, pwd);
|
||||||
log.error('Error hashing wifi password', err)
|
|
||||||
result = bleno.Characteristic.RESULT_UNLIKELY_ERROR
|
|
||||||
return callback(result)
|
|
||||||
}
|
}
|
||||||
return wifi.setNetwork(ssid, plaintext, hash, (err, data) => {
|
catch (err) {
|
||||||
if (err) {
|
log.error('Error hashing wifi password', err);
|
||||||
log.error('Error configuring wifi', err)
|
result = bleno_1.default.Characteristic.RESULT_UNLIKELY_ERROR;
|
||||||
result = bleno.Characteristic.RESULT_UNLIKELY_ERROR
|
return result;
|
||||||
return callback(result)
|
|
||||||
}
|
}
|
||||||
currentWifi = ssid
|
try {
|
||||||
currentAddr = getIp()
|
await wifi.setNetwork(ssid, psk.plaintext, psk.hash);
|
||||||
log.info(`Connected to AP`, { ssid : ssid, ip : currentAddr })
|
}
|
||||||
result = bleno.Characteristic.RESULT_SUCCESS
|
catch (err) {
|
||||||
return callback(result)
|
log.error('Error configuring wifi', err);
|
||||||
})
|
result = bleno_1.default.Characteristic.RESULT_UNLIKELY_ERROR;
|
||||||
})
|
return result;
|
||||||
|
}
|
||||||
|
currentWifi = ssid;
|
||||||
|
currentAddr = getIp();
|
||||||
|
log.info(`Connected to AP`, { ssid, ip: currentAddr });
|
||||||
|
result = bleno_1.default.Characteristic.RESULT_SUCCESS;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
async function onWifiRead(offset, callback) {
|
||||||
function onWifiRead (offset, callback) {
|
let result = bleno_1.default.Characteristic.RESULT_SUCCESS;
|
||||||
let result = bleno.Characteristic.RESULT_SUCCESS
|
let wifiRes = {};
|
||||||
let wifiRes = {}
|
let data;
|
||||||
let data
|
let list;
|
||||||
wifi.list((err, list) => {
|
try {
|
||||||
if (err) {
|
list = await wifi.list();
|
||||||
result = bleno.Characteristic.RESULT_UNLIKELY_ERROR
|
|
||||||
return callback(result)
|
|
||||||
}
|
}
|
||||||
wifiRes.available = list
|
catch (err) {
|
||||||
wifiRes.current = currentWifi
|
result = bleno_1.default.Characteristic.RESULT_UNLIKELY_ERROR;
|
||||||
wifiRes.ip = currentAddr
|
return callback(result);
|
||||||
log.info('Discovered available APs', { found : list.length })
|
}
|
||||||
data = new Buffer(JSON.stringify(wifiRes))
|
wifiRes.available = list;
|
||||||
callback(result, data.slice(offset, data.length))
|
wifiRes.current = currentWifi;
|
||||||
})
|
wifiRes.ip = currentAddr;
|
||||||
|
log.info('Discovered available APs', { found: list.length });
|
||||||
|
data = new Buffer(JSON.stringify(wifiRes));
|
||||||
|
return callback(result, data.slice(offset, data.length));
|
||||||
}
|
}
|
||||||
|
function getMac() {
|
||||||
function getMac () {
|
const colonRe = new RegExp(':', 'g');
|
||||||
const colonRe = new RegExp(':', 'g')
|
|
||||||
if (NETWORK && NETWORK.wlan0 && NETWORK.wlan0[0] && NETWORK.wlan0[0].mac) {
|
if (NETWORK && NETWORK.wlan0 && NETWORK.wlan0[0] && NETWORK.wlan0[0].mac) {
|
||||||
return NETWORK.wlan0[0].mac.replace(colonRe, '')
|
return NETWORK.wlan0[0].mac.replace(colonRe, '');
|
||||||
}
|
}
|
||||||
return undefined
|
return undefined;
|
||||||
}
|
}
|
||||||
|
function spoofMac() {
|
||||||
function spoofMac () {
|
const fs = require('fs');
|
||||||
const fs = require('fs')
|
const FSPATH = require.resolve('uuid');
|
||||||
const FSPATH = require.resolve('uuid')
|
const IDFILE = os_1.homedir() + '/.intval3id';
|
||||||
const IDFILE = os.homedir() + '/.intval3id'
|
let uuid;
|
||||||
let uuid
|
let UUIDPATH;
|
||||||
let UUIDPATH
|
let TMP;
|
||||||
let TMP
|
let MACTMP;
|
||||||
let MACTMP
|
let dashRe;
|
||||||
let dashRe
|
delete require.cache[FSPATH];
|
||||||
delete require.cache[FSPATH]
|
if (fs_extra_1.existsSync(IDFILE)) {
|
||||||
if (fs.existsSync(IDFILE)) {
|
return fs_extra_1.readFileSync(IDFILE, 'utf8');
|
||||||
return fs.readFileSync(IDFILE, 'utf8')
|
|
||||||
}
|
}
|
||||||
uuid = require('uuid').v4
|
uuid = require('uuid').v4;
|
||||||
UUIDPATH = require.resolve('uuid')
|
UUIDPATH = require.resolve('uuid');
|
||||||
delete require.cache[UUIDPATH]
|
delete require.cache[UUIDPATH];
|
||||||
TMP = uuid()
|
TMP = uuid();
|
||||||
MACTMP = TMP.replace(dashRe, '').substring(0, 12)
|
MACTMP = TMP.replace(dashRe, '').substring(0, 12);
|
||||||
dashRe = new RegExp('-', 'g')
|
dashRe = new RegExp('-', 'g');
|
||||||
fs.writeFileSync(IDFILE, MACTMP, 'utf8')
|
fs_extra_1.writeFileSync(IDFILE, MACTMP, 'utf8');
|
||||||
return MACTMP
|
return MACTMP;
|
||||||
}
|
}
|
||||||
|
function getIp() {
|
||||||
function getIp () {
|
let addr = null;
|
||||||
let addr = null
|
let ipv4;
|
||||||
let ipv4
|
const ifaces = os_1.networkInterfaces();
|
||||||
const ifaces = os.networkInterfaces()
|
|
||||||
if (ifaces && ifaces.wlan0) {
|
if (ifaces && ifaces.wlan0) {
|
||||||
ipv4 = ifaces.wlan0.filter(iface => {
|
ipv4 = ifaces.wlan0.filter(iface => {
|
||||||
if (iface.family === 'IPv4') {
|
if (iface.family === 'IPv4') {
|
||||||
return iface
|
return iface;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
if (ipv4.length === 1) {
|
if (ipv4.length === 1) {
|
||||||
addr = ipv4[0].address
|
addr = ipv4[0].address;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return addr
|
return addr;
|
||||||
}
|
}
|
||||||
|
function capitalize(str) {
|
||||||
|
return str[0].toUpperCase() + str.slice(1);
|
||||||
function capitalize (s) {
|
|
||||||
return s[0].toUpperCase() + s.slice(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Class representing the bluetooth interface */
|
/** Class representing the bluetooth interface */
|
||||||
class BLE {
|
class BLE {
|
||||||
/**
|
/**
|
||||||
|
@ -165,79 +160,78 @@ class BLE {
|
||||||
*
|
*
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
constructor (bleGetState) {
|
constructor(bleGetState) {
|
||||||
log.info('Starting bluetooth service')
|
this.listeners = {};
|
||||||
|
log.info('Starting bluetooth service');
|
||||||
getState = bleGetState
|
getState = bleGetState;
|
||||||
|
bleno_1.default.on('stateChange', state => {
|
||||||
bleno.on('stateChange', state => {
|
log.info('stateChange', { state: state });
|
||||||
log.info('stateChange', { state : state })
|
|
||||||
if (state === 'poweredOn') {
|
if (state === 'poweredOn') {
|
||||||
log.info('Starting advertising', { DEVICE_NAME, DEVICE_ID : process.env.BLENO_DEVICE_NAME })
|
log.info('Starting advertising', { DEVICE_NAME, DEVICE_ID: process.env.BLENO_DEVICE_NAME });
|
||||||
bleno.startAdvertising(DEVICE_NAME, [CHAR_ID])
|
bleno_1.default.startAdvertising(DEVICE_NAME, [CHAR_ID]);
|
||||||
} else {
|
|
||||||
bleno.stopAdvertising()
|
|
||||||
}
|
}
|
||||||
})
|
else {
|
||||||
|
bleno_1.default.stopAdvertising();
|
||||||
bleno.on('advertisingStart', err => {
|
}
|
||||||
log.info('advertisingStart', { res : (err ? 'error ' + err : 'success') })
|
});
|
||||||
createChars(this._onWrite.bind(this), this._onRead.bind(this))
|
bleno_1.default.on('advertisingStart', err => {
|
||||||
|
log.info('advertisingStart', { res: (err ? 'error ' + err : 'success') });
|
||||||
|
createChars(this._onWrite.bind(this), this._onRead.bind(this));
|
||||||
if (!err) {
|
if (!err) {
|
||||||
bleno.setServices([
|
bleno_1.default.setServices([
|
||||||
new bleno.PrimaryService({
|
new bleno_1.default.PrimaryService({
|
||||||
uuid : SERVICE_ID, //hardcoded across panels
|
uuid: SERVICE_ID,
|
||||||
characteristics : chars
|
characteristics: chars
|
||||||
})
|
})
|
||||||
])
|
]);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
bleno_1.default.on('accept', clientAddress => {
|
||||||
bleno.on('accept', clientAddress => {
|
log.info('accept', { clientAddress: clientAddress });
|
||||||
log.info('accept', { clientAddress : clientAddress })
|
});
|
||||||
})
|
bleno_1.default.on('disconnect', clientAddress => {
|
||||||
|
log.info('disconnect', { clientAddress: clientAddress });
|
||||||
bleno.on('disconnect', clientAddress => {
|
});
|
||||||
log.info('disconnect', { clientAddress : clientAddress })
|
this._refreshWifi();
|
||||||
})
|
|
||||||
|
|
||||||
wifi.getNetwork((err, ssid) => {
|
|
||||||
if (err) {
|
|
||||||
return log.error('wifi.getNetwork', err)
|
|
||||||
}
|
}
|
||||||
currentWifi = ssid
|
async _refreshWifi() {
|
||||||
currentAddr = getIp()
|
let ssid;
|
||||||
log.info('wifi.getNetwork', {ssid : ssid, ip : currentAddr })
|
try {
|
||||||
})
|
ssid = await wifi.getNetwork();
|
||||||
}
|
}
|
||||||
_onWrite (data, offset, withoutResponse, callback) {
|
catch (err) {
|
||||||
let result = {}
|
return log.error('wifi.getNetwork', err);
|
||||||
let utf8
|
}
|
||||||
let obj
|
currentWifi = ssid;
|
||||||
let fn
|
currentAddr = getIp();
|
||||||
|
log.info('wifi.getNetwork', { ssid: ssid, ip: currentAddr });
|
||||||
|
}
|
||||||
|
_onWrite(data, offset, withoutResponse, callback) {
|
||||||
|
let result = {};
|
||||||
|
let utf8;
|
||||||
|
let obj;
|
||||||
if (offset) {
|
if (offset) {
|
||||||
log.warn(`Offset scenario`)
|
log.warn(`Offset scenario`);
|
||||||
result = bleno.Characteristic.RESULT_ATTR_NOT_LONG
|
result = bleno_1.default.Characteristic.RESULT_ATTR_NOT_LONG;
|
||||||
return callback(result)
|
return callback(result);
|
||||||
}
|
}
|
||||||
utf8 = data.toString('utf8')
|
utf8 = data.toString('utf8');
|
||||||
obj = JSON.parse(utf8)
|
obj = JSON.parse(utf8);
|
||||||
result = bleno.Characteristic.RESULT_SUCCESS
|
result = bleno_1.default.Characteristic.RESULT_SUCCESS;
|
||||||
fn = `_on${capitalize(obj.type)}`
|
if (obj.type && this.listeners[obj.type]) {
|
||||||
if (obj.type && this[fn]) {
|
return this.listeners[obj.type](obj, () => {
|
||||||
return this[fn](obj, () => {
|
callback(result);
|
||||||
callback(result)
|
});
|
||||||
})
|
|
||||||
} else {
|
|
||||||
return callback(result)
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
return callback(result);
|
||||||
}
|
}
|
||||||
_onRead (offset, callback) {
|
}
|
||||||
const result = bleno.Characteristic.RESULT_SUCCESS
|
_onRead(offset, callback) {
|
||||||
const state = getState()
|
const result = bleno_1.default.Characteristic.RESULT_SUCCESS;
|
||||||
const data = new Buffer(JSON.stringify( state ))
|
const state = getState();
|
||||||
callback(result, data.slice(offset, data.length))
|
const data = new Buffer(JSON.stringify(state));
|
||||||
|
callback(result, data.slice(offset, data.length));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Binds functions to events that are triggered by BLE messages
|
* Binds functions to events that are triggered by BLE messages
|
||||||
|
@ -245,10 +239,9 @@ class BLE {
|
||||||
* @param {string} eventName Name of the event to to bind
|
* @param {string} eventName Name of the event to to bind
|
||||||
* @param {function} callback Invoked when the event is triggered
|
* @param {function} callback Invoked when the event is triggered
|
||||||
*/
|
*/
|
||||||
on (eventName, callback) {
|
on(eventName, callback) {
|
||||||
this[`_on${capitalize(eventName)}`] = callback
|
this.listeners[eventName] = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
module.exports = BLE;
|
||||||
module.exports = BLE
|
//# sourceMappingURL=index.js.map
|
File diff suppressed because one or more lines are too long
|
@ -1,149 +1,152 @@
|
||||||
'use strict'
|
'use strict';
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const networkPattern = /network[\s\S]*?=[\s\S]*?{([\s\S]*?)}/gi
|
const networkPattern = /network[\s\S]*?=[\s\S]*?{([\s\S]*?)}/gi;
|
||||||
const quoteRe = new RegExp('"', 'g')
|
const quoteRe = new RegExp('"', 'g');
|
||||||
|
const filePath = '/etc/wpa_supplicant/wpa_supplicant.conf';
|
||||||
const filePath = '/etc/wpa_supplicant/wpa_supplicant.conf'
|
const reconfigure = '/sbin/wpa_cli reconfigure';
|
||||||
const reconfigure = '/sbin/wpa_cli reconfigure'
|
const refresh = 'ip link set wlan0 down && ip link set wlan0 up';
|
||||||
const refresh = 'ip link set wlan0 down && ip link set wlan0 up'
|
const iwlist = '/sbin/iwlist wlan0 scanning | grep "ESSID:"';
|
||||||
const iwlist = '/sbin/iwlist wlan0 scanning | grep "ESSID:"'
|
const iwgetid = '/sbin/iwgetid';
|
||||||
const iwgetid = '/sbin/iwgetid'
|
const log = require('../log')('wifi');
|
||||||
|
const child_process_1 = require("child_process");
|
||||||
const log = require('../log')('wifi')
|
const fs_extra_1 = require("fs-extra");
|
||||||
const exec = require('child_process').exec
|
|
||||||
const fs = require('fs')
|
|
||||||
|
|
||||||
let _entry = null
|
|
||||||
let _ssid = null
|
|
||||||
let _cb = null
|
|
||||||
|
|
||||||
/** Class representing the wifi features */
|
/** Class representing the wifi features */
|
||||||
class Wifi {
|
class Wifi {
|
||||||
constructor () {
|
constructor() {
|
||||||
|
this._ssid = null;
|
||||||
|
this._entry = null;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* List available wifi access points
|
* List available wifi access points
|
||||||
*
|
*
|
||||||
* @param {function} callback Function which gets invoked after list is returned
|
* @param {function} callback Function which gets invoked after list is returned
|
||||||
*/
|
*/
|
||||||
list (callback) {
|
async list() {
|
||||||
exec(iwlist, (err, stdout, stderr) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
return child_process_1.exec(iwlist, (err, stdout, stderr) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err)
|
log.error('list', err);
|
||||||
return callback(err)
|
return reject(err);
|
||||||
}
|
}
|
||||||
const limit = 20;
|
const limit = 20;
|
||||||
const lines = stdout.split('\n')
|
const lines = stdout.split('\n');
|
||||||
let output = []
|
let output = [];
|
||||||
let line
|
let line;
|
||||||
let i = 0
|
let i = 0;
|
||||||
for (let l of lines) {
|
for (let l of lines) {
|
||||||
line = l.replace('ESSID:', '').trim()
|
line = l.replace('ESSID:', '').trim();
|
||||||
if (line !== '""' && i < limit) {
|
if (line !== '""' && i < limit) {
|
||||||
line = line.replace(quoteRe, '')
|
line = line.replace(quoteRe, '');
|
||||||
output.push(line)
|
output.push(line);
|
||||||
}
|
}
|
||||||
i++
|
i++;
|
||||||
}
|
}
|
||||||
output = output.filter(ap => {
|
output = output.filter(ap => {
|
||||||
if (ap !== '') return ap
|
if (ap !== '')
|
||||||
})
|
return ap;
|
||||||
return callback(null, output)
|
});
|
||||||
})
|
return resolve(output);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* (internal function) Invoked after config file is read,
|
* (internal function) Invoked after config file is read,
|
||||||
* then invokes file write on the config file
|
* then invokes file write on the config file
|
||||||
*
|
*
|
||||||
* @param {object} err (optional) Error object only present if problem reading config file
|
|
||||||
* @param {string} data Contents of the config file
|
|
||||||
*/
|
*/
|
||||||
_readConfigCb (err, data) {
|
async _readConfig() {
|
||||||
let parsed
|
let data;
|
||||||
let current
|
let parsed;
|
||||||
if (err) {
|
let current;
|
||||||
console.error(err)
|
try {
|
||||||
return _cb(err)
|
data = await fs_extra_1.readFile(filePath, 'utf8');
|
||||||
}
|
}
|
||||||
parsed = this._parseConfig(data)
|
catch (err) {
|
||||||
current = parsed.find(network => {
|
log.error('_readConfig', err);
|
||||||
return network.ssid === _ssid
|
throw err;
|
||||||
})
|
}
|
||||||
|
parsed = this._parseConfig(data);
|
||||||
|
current = parsed.find((network) => {
|
||||||
|
return network.ssid === this._ssid;
|
||||||
|
});
|
||||||
if (typeof current !== 'undefined') {
|
if (typeof current !== 'undefined') {
|
||||||
data = data.replace(current.raw, _entry)
|
data = data.replace(current.raw, this._entry);
|
||||||
} else {
|
|
||||||
data += '\n\n' + _entry
|
|
||||||
}
|
}
|
||||||
_entry = null
|
else {
|
||||||
fs.writeFile(filePath, data, 'utf8', this._writeConfigCb.bind(this))
|
data += '\n\n' + this._entry;
|
||||||
|
}
|
||||||
|
this._entry = null;
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* (internal function) Invoked after config file is written,
|
* (internal function) Invoked after config file is written,
|
||||||
* then executes reconfiguration command
|
* then executes reconfiguration command
|
||||||
*
|
*
|
||||||
* @param {object} err (optional) Error object only present if problem writing config file
|
|
||||||
*/
|
*/
|
||||||
_writeConfigCb (err) {
|
async _writeConfig(data) {
|
||||||
if (err) {
|
try {
|
||||||
console.error(err)
|
await fs_extra_1.writeFile(filePath, data, 'utf8');
|
||||||
return _cb(err)
|
}
|
||||||
|
catch (err) {
|
||||||
|
log.error('_readConfigCb', err);
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
exec(reconfigure, this._reconfigureCb.bind(this))
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* (internal function) Invoked after reconfiguration command is complete
|
* (internal function) Invoked after reconfiguration command is complete
|
||||||
*
|
*
|
||||||
* @param {object} err (optional) Error object only present if configuration command fails
|
|
||||||
* @param {string} stdout Standard output from reconfiguration command
|
|
||||||
* @param {string} stderr Error output from command if fails
|
|
||||||
*/
|
*/
|
||||||
_reconfigureCb (err, stdout, stderr) {
|
async _reconfigure() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
return child_process_1.exec(reconfigure, (err, stdout, stderr) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err)
|
return reject(err);
|
||||||
return _cb(err)
|
|
||||||
}
|
}
|
||||||
console.log('Wifi reconfigured')
|
log.info('Wifi reconfigured');
|
||||||
exec(refresh, this._refreshCb.bind(this))
|
return resolve(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* (internal function) Invoked after wifi refresh command is complete
|
* (internal function) Invoked after wifi refresh command is complete
|
||||||
*
|
*
|
||||||
* @param {object} err (optional) Error object only present if refresh command fails
|
|
||||||
* @param {string} stdout Standard output from refresh command
|
|
||||||
* @param {string} stderr Error output from command if fails
|
|
||||||
*/
|
*/
|
||||||
_refreshCb (err, stdout, stderr) {
|
async _refresh() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
return child_process_1.exec(refresh, (err, stdout, stderr) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err)
|
return reject(err);
|
||||||
return _cb(err)
|
|
||||||
}
|
}
|
||||||
console.log('Wifi refreshed')
|
log.info('Wifi refreshed');
|
||||||
_cb(null, { ssid : _ssid })
|
return resolve({ ssid: this._ssid });
|
||||||
_cb = () => {}
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
_parseConfig (str) {
|
_parseConfig(str) {
|
||||||
const networks = []
|
const networks = [];
|
||||||
const lines = str.split('\n')
|
const lines = str.split('\n');
|
||||||
let network = {}
|
let network = {};
|
||||||
for (let line of lines) {
|
for (let line of lines) {
|
||||||
if (line.substring(0, 9) === 'network={') {
|
if (line.substring(0, 9) === 'network={') {
|
||||||
network = {}
|
network = {};
|
||||||
network.raw = line
|
network.raw = line;
|
||||||
} else if (network.raw && line.indexOf('ssid=') !== -1) {
|
}
|
||||||
network.ssid = line.replace('ssid=', '').trim().replace(quoteRe, '')
|
else if (network.raw && line.indexOf('ssid=') !== -1) {
|
||||||
|
network.ssid = line.replace('ssid=', '').trim().replace(quoteRe, '');
|
||||||
if (network.raw) {
|
if (network.raw) {
|
||||||
network.raw += '\n' + line
|
network.raw += '\n' + line;
|
||||||
}
|
|
||||||
} else if (network.raw && line.substring(0, 1) === '}') {
|
|
||||||
network.raw += '\n' + line
|
|
||||||
networks.push(network)
|
|
||||||
network = {}
|
|
||||||
} else if (network.raw) {
|
|
||||||
network.raw += '\n' + line
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return networks
|
else if (network.raw && line.substring(0, 1) === '}') {
|
||||||
|
network.raw += '\n' + line;
|
||||||
|
networks.push(network);
|
||||||
|
network = {};
|
||||||
|
}
|
||||||
|
else if (network.raw) {
|
||||||
|
network.raw += '\n' + line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return networks;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Create sanitized wpa_supplicant.conf stanza for
|
* Create sanitized wpa_supplicant.conf stanza for
|
||||||
|
@ -157,22 +160,23 @@ class Wifi {
|
||||||
*
|
*
|
||||||
* @param {string} ssid SSID of wifi network
|
* @param {string} ssid SSID of wifi network
|
||||||
* @param {string} pwd Plaintext passphrase of wifi network
|
* @param {string} pwd Plaintext passphrase of wifi network
|
||||||
* @param {function} callback Function called after psk hash is generated
|
|
||||||
*/
|
*/
|
||||||
createPSK (ssid, pwd, callback) {
|
createPSK(ssid, pwd) {
|
||||||
const cmd = `wpa_passphrase '${ssid.replace(/'/g, `'\\''`)}' '${pwd.replace(/'/g, `'\\''`)}' | grep "psk="`
|
const cmd = `wpa_passphrase '${ssid.replace(/'/g, `'\\''`)}' '${pwd.replace(/'/g, `'\\''`)}' | grep "psk="`;
|
||||||
let lines
|
let lines;
|
||||||
let hash
|
let hash;
|
||||||
let plaintext
|
let plaintext;
|
||||||
exec(cmd, (err, stdout, stderr) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
return child_process_1.exec(cmd, (err, stdout, stderr) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err)
|
return reject(err);
|
||||||
}
|
}
|
||||||
lines = stdout.replace('#psk=', '').split('psk=')
|
lines = stdout.replace('#psk=', '').split('psk=');
|
||||||
hash = lines[1]
|
hash = lines[1];
|
||||||
plaintext = lines[0]
|
plaintext = lines[0];
|
||||||
callback(null, hash.trim(), plaintext.trim())
|
return resolve({ hash: hash.trim(), plaintext: plaintext.trim() });
|
||||||
})
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Function which initializes the processes for adding a wifi access point authentication
|
* Function which initializes the processes for adding a wifi access point authentication
|
||||||
|
@ -180,30 +184,56 @@ class Wifi {
|
||||||
* @param {string} ssid SSID of network to configure
|
* @param {string} ssid SSID of network to configure
|
||||||
* @param {string} pwd Password of access point, plaintext to be masked
|
* @param {string} pwd Password of access point, plaintext to be masked
|
||||||
* @param {string} hash Password/SSID of access point, securely hashed
|
* @param {string} hash Password/SSID of access point, securely hashed
|
||||||
* @param {function} callback Function invoked after process is complete, or fails
|
|
||||||
*/
|
*/
|
||||||
setNetwork (ssid, pwd, hash, callback) {
|
async setNetwork(ssid, pwd, hash) {
|
||||||
let masked = pwd.split('').map(char => { return char !== '"' ? '*' : '"' }).join('')
|
let masked = pwd.split('').map(char => { return char !== '"' ? '*' : '"'; }).join('');
|
||||||
_entry = `network={\n\tssid="${ssid}"\n\t#psk=${masked}\n\tpsk=${hash}\n}\n`
|
let data;
|
||||||
_cb = callback
|
this._entry = `network={\n\tssid="${ssid}"\n\t#psk=${masked}\n\tpsk=${hash}\n}\n`;
|
||||||
_ssid = ssid
|
this._ssid = ssid;
|
||||||
fs.readFile(filePath, 'utf8', this._readConfigCb.bind(this))
|
try {
|
||||||
|
data = await this._readConfig();
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
log.error(err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this._writeConfig(data);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
log.error(err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this._reconfigure();
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
log.error(err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this._refresh();
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
log.error(err);
|
||||||
|
}
|
||||||
|
return { ssid: this._ssid };
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Executes command which gets the currently connected network
|
* Executes command which gets the currently connected network
|
||||||
*
|
*
|
||||||
* @param {function} callback Function which is invoked after command is completed
|
* @param {function} callback Function which is invoked after command is completed
|
||||||
*/
|
*/
|
||||||
getNetwork (callback) {
|
async getNetwork() {
|
||||||
let output
|
let output;
|
||||||
exec(iwgetid, (err, stdout, stderr) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
return child_process_1.exec(iwgetid, (err, stdout, stderr) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err)
|
return reject(err);
|
||||||
}
|
}
|
||||||
output = stdout.split('ESSID:')[1].replace(quoteRe, '').trim()
|
output = stdout.split('ESSID:')[1].replace(quoteRe, '').trim();
|
||||||
callback(null, output)
|
return resolve(output);
|
||||||
})
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
exports.Wifi = Wifi;
|
||||||
module.exports = new Wifi()
|
module.exports.Wifi = Wifi;
|
||||||
|
//# sourceMappingURL=index.js.map
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
|
@ -1,12 +1,17 @@
|
||||||
{
|
{
|
||||||
"name": "intval3",
|
"name": "intval3",
|
||||||
"version": "3.1.0",
|
"version": "3.1.3",
|
||||||
"description": "Intervalometer for the Bolex",
|
"description": "Intervalometer for the Bolex",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "./node_modules/.bin/qunit",
|
"test": "./node_modules/.bin/qunit",
|
||||||
"docs": "sh docs.sh",
|
"docs": "sh docs.sh",
|
||||||
"build": "./node_modules/.bin/tsc -p tsconfig.json"
|
"compile": "./node_modules/.bin/tsc -p tsconfig.json",
|
||||||
|
"pretest": "",
|
||||||
|
"version": "",
|
||||||
|
"postversion": "git push && git push --tags",
|
||||||
|
"u": "npm run git -- -m \"update\"",
|
||||||
|
"git": "npm version patch --force"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -38,11 +43,13 @@
|
||||||
"winston": "^3.2.1"
|
"winston": "^3.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/bleno": "^0.4.1",
|
||||||
"@types/fs-extra": "^8.0.0",
|
"@types/fs-extra": "^8.0.0",
|
||||||
"@types/node": "^12.7.12",
|
"@types/node": "^12.7.12",
|
||||||
"@types/node-persist": "0.0.33",
|
"@types/node-persist": "0.0.33",
|
||||||
"@types/uuid": "^3.4.5",
|
"@types/uuid": "^3.4.5",
|
||||||
"jsdoc-to-markdown": "^5.0.2",
|
"jsdoc-to-markdown": "^5.0.2",
|
||||||
|
"pkg": "^4.4.0",
|
||||||
"qunit": "^2.9.3",
|
"qunit": "^2.9.3",
|
||||||
"typescript": "^3.6.4"
|
"typescript": "^3.6.4"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,291 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
/** @module ble */
|
||||||
|
/** Bluetooth Low Energy module */
|
||||||
|
|
||||||
|
import { inherits } from 'util'
|
||||||
|
import { networkInterfaces, homedir } from 'os'
|
||||||
|
import { readFileSync, existsSync, writeFileSync } from 'fs-extra'
|
||||||
|
|
||||||
|
const log = require('../log')('ble')
|
||||||
|
import { Wifi } from '../wifi'
|
||||||
|
const wifi = new Wifi()
|
||||||
|
|
||||||
|
const DEVICE_NAME : string = process.env.DEVICE_NAME || 'intval3'
|
||||||
|
const SERVICE_ID : string = process.env.SERVICE_ID || 'intval3_ble'
|
||||||
|
const CHAR_ID : string = process.env.CHAR_ID || 'intval3char'
|
||||||
|
const WIFI_ID : string = process.env.WIFI_ID || 'wifichar'
|
||||||
|
const NETWORK : any = networkInterfaces() //?type?
|
||||||
|
const MAC : string = getMac() || spoofMac()
|
||||||
|
|
||||||
|
//Give the device a unique device name, needs to be in env
|
||||||
|
process.env.BLENO_DEVICE_NAME += '_' + MAC
|
||||||
|
import bleno from 'bleno'
|
||||||
|
const { Characteristic } = bleno
|
||||||
|
|
||||||
|
let currentWifi : string = 'disconnected'
|
||||||
|
let currentAddr : string = null
|
||||||
|
let getState : Function
|
||||||
|
|
||||||
|
const chars : any[] = []
|
||||||
|
|
||||||
|
interface WifiInfo {
|
||||||
|
ssid : string
|
||||||
|
pwd : string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WifiResponse {
|
||||||
|
available : string[]
|
||||||
|
current : string
|
||||||
|
ip : string
|
||||||
|
}
|
||||||
|
|
||||||
|
function createChar(name : string, uuid : string, prop : string[], write : Function, read : Function) {
|
||||||
|
const characteristic : any = function () {
|
||||||
|
Characteristic.call(this, {
|
||||||
|
uuid,
|
||||||
|
properties: prop
|
||||||
|
})
|
||||||
|
}
|
||||||
|
inherits(characteristic, Characteristic)
|
||||||
|
if (prop.indexOf('read') !== -1) {
|
||||||
|
//data, offset, withoutResponse, callback
|
||||||
|
characteristic.prototype.onReadRequest = read
|
||||||
|
}
|
||||||
|
if (prop.indexOf('write') !== -1) {
|
||||||
|
characteristic.prototype.onWriteRequest = write
|
||||||
|
}
|
||||||
|
chars.push(new characteristic())
|
||||||
|
}
|
||||||
|
|
||||||
|
function createChars (onWrite : Function, onRead : Function) {
|
||||||
|
const permissions : string[] = ['read', 'write'];
|
||||||
|
createChar('intval3', CHAR_ID, permissions, onWrite, onRead)
|
||||||
|
createChar('wifi', WIFI_ID, permissions, onWifiWrite, onWifiRead)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onWifiWrite (data : any, offset : number) {
|
||||||
|
let result : any
|
||||||
|
let utf8 : string
|
||||||
|
let obj : WifiInfo = {} as WifiInfo
|
||||||
|
let ssid : string
|
||||||
|
let pwd : string
|
||||||
|
let psk : any
|
||||||
|
|
||||||
|
if (offset) {
|
||||||
|
log.warn(`Offset scenario`)
|
||||||
|
result = bleno.Characteristic.RESULT_ATTR_NOT_LONG
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
utf8 = data.toString('utf8')
|
||||||
|
obj = JSON.parse(utf8)
|
||||||
|
ssid = obj.ssid
|
||||||
|
pwd = obj.pwd
|
||||||
|
|
||||||
|
log.info(`connecting to AP`, { ssid : ssid })
|
||||||
|
|
||||||
|
try {
|
||||||
|
psk = await wifi.createPSK(ssid, pwd)
|
||||||
|
} catch (err) {
|
||||||
|
log.error('Error hashing wifi password', err)
|
||||||
|
result = bleno.Characteristic.RESULT_UNLIKELY_ERROR
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await wifi.setNetwork(ssid, psk.plaintext, psk.hash)
|
||||||
|
} catch (err) {
|
||||||
|
log.error('Error configuring wifi', err)
|
||||||
|
result = bleno.Characteristic.RESULT_UNLIKELY_ERROR
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
currentWifi = ssid
|
||||||
|
currentAddr = getIp()
|
||||||
|
log.info(`Connected to AP`, { ssid, ip : currentAddr })
|
||||||
|
result = bleno.Characteristic.RESULT_SUCCESS
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onWifiRead (offset : number, callback : Function) {
|
||||||
|
let result : any = bleno.Characteristic.RESULT_SUCCESS
|
||||||
|
let wifiRes : WifiResponse = {} as WifiResponse
|
||||||
|
let data : any
|
||||||
|
let list : any
|
||||||
|
|
||||||
|
try {
|
||||||
|
list = await wifi.list()
|
||||||
|
} catch (err) {
|
||||||
|
result = bleno.Characteristic.RESULT_UNLIKELY_ERROR
|
||||||
|
return callback(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
wifiRes.available = list
|
||||||
|
wifiRes.current = currentWifi
|
||||||
|
wifiRes.ip = currentAddr
|
||||||
|
log.info('Discovered available APs', { found : list.length })
|
||||||
|
data = new Buffer(JSON.stringify(wifiRes))
|
||||||
|
|
||||||
|
return callback(result, data.slice(offset, data.length))
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMac () {
|
||||||
|
const colonRe = new RegExp(':', 'g')
|
||||||
|
if (NETWORK && NETWORK.wlan0 && NETWORK.wlan0[0] && NETWORK.wlan0[0].mac) {
|
||||||
|
return NETWORK.wlan0[0].mac.replace(colonRe, '')
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function spoofMac () {
|
||||||
|
const fs = require('fs')
|
||||||
|
const FSPATH = require.resolve('uuid')
|
||||||
|
const IDFILE = homedir() + '/.intval3id'
|
||||||
|
let uuid
|
||||||
|
let UUIDPATH
|
||||||
|
let TMP
|
||||||
|
let MACTMP
|
||||||
|
let dashRe
|
||||||
|
delete require.cache[FSPATH]
|
||||||
|
if (existsSync(IDFILE)) {
|
||||||
|
return readFileSync(IDFILE, 'utf8')
|
||||||
|
}
|
||||||
|
uuid = require('uuid').v4
|
||||||
|
UUIDPATH = require.resolve('uuid')
|
||||||
|
delete require.cache[UUIDPATH]
|
||||||
|
TMP = uuid()
|
||||||
|
MACTMP = TMP.replace(dashRe, '').substring(0, 12)
|
||||||
|
dashRe = new RegExp('-', 'g')
|
||||||
|
writeFileSync(IDFILE, MACTMP, 'utf8')
|
||||||
|
return MACTMP
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIp () {
|
||||||
|
let addr = null
|
||||||
|
let ipv4
|
||||||
|
const ifaces = networkInterfaces()
|
||||||
|
if (ifaces && ifaces.wlan0) {
|
||||||
|
ipv4 = ifaces.wlan0.filter(iface => {
|
||||||
|
if (iface.family === 'IPv4') {
|
||||||
|
return iface
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (ipv4.length === 1) {
|
||||||
|
addr = ipv4[0].address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function capitalize (str : string) {
|
||||||
|
return str[0].toUpperCase() + str.slice(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
type functionKeys = "_onRead" | "_onWrite";
|
||||||
|
|
||||||
|
/** Class representing the bluetooth interface */
|
||||||
|
class BLE {
|
||||||
|
listeners : any = {}
|
||||||
|
/**
|
||||||
|
* Establishes Bluetooth Low Energy services, accessible to process through this class
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
constructor (bleGetState : Function) {
|
||||||
|
log.info('Starting bluetooth service')
|
||||||
|
|
||||||
|
getState = bleGetState
|
||||||
|
|
||||||
|
bleno.on('stateChange', state => {
|
||||||
|
log.info('stateChange', { state : state })
|
||||||
|
if (state === 'poweredOn') {
|
||||||
|
log.info('Starting advertising', { DEVICE_NAME, DEVICE_ID : process.env.BLENO_DEVICE_NAME })
|
||||||
|
bleno.startAdvertising(DEVICE_NAME, [CHAR_ID])
|
||||||
|
} else {
|
||||||
|
bleno.stopAdvertising()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
bleno.on('advertisingStart', err => {
|
||||||
|
log.info('advertisingStart', { res : (err ? 'error ' + err : 'success') })
|
||||||
|
createChars(this._onWrite.bind(this), this._onRead.bind(this))
|
||||||
|
if (!err) {
|
||||||
|
bleno.setServices([
|
||||||
|
new bleno.PrimaryService({
|
||||||
|
uuid : SERVICE_ID, //hardcoded across panels
|
||||||
|
characteristics : chars
|
||||||
|
})
|
||||||
|
])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
bleno.on('accept', clientAddress => {
|
||||||
|
log.info('accept', { clientAddress : clientAddress })
|
||||||
|
})
|
||||||
|
|
||||||
|
bleno.on('disconnect', clientAddress => {
|
||||||
|
log.info('disconnect', { clientAddress : clientAddress })
|
||||||
|
})
|
||||||
|
|
||||||
|
this._refreshWifi()
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _refreshWifi () {
|
||||||
|
let ssid : string
|
||||||
|
|
||||||
|
try {
|
||||||
|
ssid = await wifi.getNetwork() as string
|
||||||
|
} catch (err) {
|
||||||
|
return log.error('wifi.getNetwork', err)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentWifi = ssid
|
||||||
|
currentAddr = getIp()
|
||||||
|
log.info('wifi.getNetwork', {ssid : ssid, ip : currentAddr })
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onWrite (data : any, offset : number, withoutResponse : Function, callback : Function) {
|
||||||
|
let result : any = {}
|
||||||
|
let utf8 : string
|
||||||
|
let obj : any
|
||||||
|
|
||||||
|
if (offset) {
|
||||||
|
log.warn(`Offset scenario`)
|
||||||
|
result = bleno.Characteristic.RESULT_ATTR_NOT_LONG
|
||||||
|
return callback(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
utf8 = data.toString('utf8')
|
||||||
|
obj = JSON.parse(utf8)
|
||||||
|
result = bleno.Characteristic.RESULT_SUCCESS
|
||||||
|
|
||||||
|
if (obj.type && this.listeners[obj.type]) {
|
||||||
|
return this.listeners[obj.type](obj, () => {
|
||||||
|
callback(result)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return callback(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onRead (offset : number, callback : Function) {
|
||||||
|
const result = bleno.Characteristic.RESULT_SUCCESS
|
||||||
|
const state = getState()
|
||||||
|
const data = new Buffer(JSON.stringify( state ))
|
||||||
|
callback(result, data.slice(offset, data.length))
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Binds functions to events that are triggered by BLE messages
|
||||||
|
*
|
||||||
|
* @param {string} eventName Name of the event to to bind
|
||||||
|
* @param {function} callback Invoked when the event is triggered
|
||||||
|
*/
|
||||||
|
on (eventName : string, callback : Function) {
|
||||||
|
this.listeners[eventName] = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = BLE
|
|
@ -0,0 +1,247 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const networkPattern : RegExp = /network[\s\S]*?=[\s\S]*?{([\s\S]*?)}/gi
|
||||||
|
const quoteRe : RegExp = new RegExp('"', 'g')
|
||||||
|
|
||||||
|
const filePath : string = '/etc/wpa_supplicant/wpa_supplicant.conf'
|
||||||
|
const reconfigure : string = '/sbin/wpa_cli reconfigure'
|
||||||
|
const refresh : string = 'ip link set wlan0 down && ip link set wlan0 up'
|
||||||
|
const iwlist : string = '/sbin/iwlist wlan0 scanning | grep "ESSID:"'
|
||||||
|
const iwgetid : string = '/sbin/iwgetid'
|
||||||
|
|
||||||
|
const log : any = require('../log')('wifi')
|
||||||
|
import { exec } from 'child_process'
|
||||||
|
import { readFile, writeFile } from 'fs-extra'
|
||||||
|
import { reject } from 'q'
|
||||||
|
|
||||||
|
interface Network {
|
||||||
|
raw : string
|
||||||
|
ssid : string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Class representing the wifi features */
|
||||||
|
export class Wifi {
|
||||||
|
private _ssid : string = null
|
||||||
|
private _entry : string = null
|
||||||
|
|
||||||
|
constructor () {
|
||||||
|
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* List available wifi access points
|
||||||
|
*
|
||||||
|
* @param {function} callback Function which gets invoked after list is returned
|
||||||
|
*/
|
||||||
|
public async list () {
|
||||||
|
return new Promise ((resolve : Function, reject : Function) => {
|
||||||
|
return exec(iwlist, (err, stdout, stderr) => {
|
||||||
|
if (err) {
|
||||||
|
log.error('list', err)
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
|
const limit : number = 20;
|
||||||
|
const lines : string[] = stdout.split('\n')
|
||||||
|
let output : string[] = []
|
||||||
|
let line : string
|
||||||
|
let i = 0
|
||||||
|
for (let l of lines) {
|
||||||
|
line = l.replace('ESSID:', '').trim()
|
||||||
|
if (line !== '""' && i < limit) {
|
||||||
|
line = line.replace(quoteRe, '')
|
||||||
|
output.push(line)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
output = output.filter(ap => {
|
||||||
|
if (ap !== '') return ap
|
||||||
|
})
|
||||||
|
return resolve(output)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* (internal function) Invoked after config file is read,
|
||||||
|
* then invokes file write on the config file
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
async _readConfig () {
|
||||||
|
let data : string
|
||||||
|
let parsed : Network[]
|
||||||
|
let current : Network
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = await readFile(filePath, 'utf8')
|
||||||
|
} catch (err) {
|
||||||
|
log.error('_readConfig', err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed = this._parseConfig(data)
|
||||||
|
current = parsed.find((network : Network) => {
|
||||||
|
return network.ssid === this._ssid
|
||||||
|
})
|
||||||
|
if (typeof current !== 'undefined') {
|
||||||
|
data = data.replace(current.raw, this._entry)
|
||||||
|
} else {
|
||||||
|
data += '\n\n' + this._entry
|
||||||
|
}
|
||||||
|
this._entry = null
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* (internal function) Invoked after config file is written,
|
||||||
|
* then executes reconfiguration command
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private async _writeConfig (data : string) {
|
||||||
|
try {
|
||||||
|
await writeFile(filePath, data, 'utf8')
|
||||||
|
} catch (err) {
|
||||||
|
log.error('_readConfigCb', err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* (internal function) Invoked after reconfiguration command is complete
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private async _reconfigure () {
|
||||||
|
return new Promise((resolve : Function, reject : Function) => {
|
||||||
|
return exec(reconfigure, (err : Error, stdout : string, stderr : string) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
|
log.info('Wifi reconfigured')
|
||||||
|
return resolve(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* (internal function) Invoked after wifi refresh command is complete
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private async _refresh () {
|
||||||
|
return new Promise((resolve : Function, reject : Function) => {
|
||||||
|
return exec(refresh, (err : Error, stdout : string, stderr : string) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
|
log.info('Wifi refreshed')
|
||||||
|
return resolve({ ssid : this._ssid });
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private _parseConfig (str : string) : Network[] {
|
||||||
|
const networks : Network[] = []
|
||||||
|
const lines = str.split('\n')
|
||||||
|
let network : Network = {} as Network
|
||||||
|
for (let line of lines) {
|
||||||
|
if (line.substring(0, 9) === 'network={') {
|
||||||
|
network = {} as Network
|
||||||
|
network.raw = line
|
||||||
|
} else if (network.raw && line.indexOf('ssid=') !== -1) {
|
||||||
|
network.ssid = line.replace('ssid=', '').trim().replace(quoteRe, '')
|
||||||
|
if (network.raw) {
|
||||||
|
network.raw += '\n' + line
|
||||||
|
}
|
||||||
|
} else if (network.raw && line.substring(0, 1) === '}') {
|
||||||
|
network.raw += '\n' + line
|
||||||
|
networks.push(network)
|
||||||
|
network = {} as Network
|
||||||
|
} else if (network.raw) {
|
||||||
|
network.raw += '\n' + line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return networks
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Create sanitized wpa_supplicant.conf stanza for
|
||||||
|
* configuring wifi without storing plaintext passwords
|
||||||
|
* @example
|
||||||
|
* network={
|
||||||
|
* ssid="YOUR_SSID"
|
||||||
|
* #psk="YOUR_PASSWORD"
|
||||||
|
* psk=6a24edf1592aec4465271b7dcd204601b6e78df3186ce1a62a31f40ae9630702
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @param {string} ssid SSID of wifi network
|
||||||
|
* @param {string} pwd Plaintext passphrase of wifi network
|
||||||
|
*/
|
||||||
|
createPSK (ssid : string, pwd : string) {
|
||||||
|
const cmd : string = `wpa_passphrase '${ssid.replace(/'/g, `'\\''`)}' '${pwd.replace(/'/g, `'\\''`)}' | grep "psk="`
|
||||||
|
let lines : string[]
|
||||||
|
let hash : string
|
||||||
|
let plaintext : string
|
||||||
|
return new Promise ((resolve : Function, reject : Function) => {
|
||||||
|
return exec(cmd, (err, stdout, stderr) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
|
lines = stdout.replace('#psk=', '').split('psk=')
|
||||||
|
hash = lines[1]
|
||||||
|
plaintext = lines[0]
|
||||||
|
return resolve({ hash : hash.trim(), plaintext : plaintext.trim()})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Function which initializes the processes for adding a wifi access point authentication
|
||||||
|
*
|
||||||
|
* @param {string} ssid SSID of network to configure
|
||||||
|
* @param {string} pwd Password of access point, plaintext to be masked
|
||||||
|
* @param {string} hash Password/SSID of access point, securely hashed
|
||||||
|
*/
|
||||||
|
async setNetwork (ssid : string, pwd : string, hash : string) {
|
||||||
|
let masked : string = pwd.split('').map(char => { return char !== '"' ? '*' : '"' }).join('')
|
||||||
|
let data : string
|
||||||
|
this._entry = `network={\n\tssid="${ssid}"\n\t#psk=${masked}\n\tpsk=${hash}\n}\n`
|
||||||
|
this._ssid = ssid
|
||||||
|
|
||||||
|
try {
|
||||||
|
data = await this._readConfig()
|
||||||
|
} catch (err) {
|
||||||
|
log.error(err)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this._writeConfig(data)
|
||||||
|
} catch (err) {
|
||||||
|
log.error(err)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this._reconfigure()
|
||||||
|
} catch (err) {
|
||||||
|
log.error(err)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this._refresh()
|
||||||
|
} catch (err) {
|
||||||
|
log.error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ssid : this._ssid }
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Executes command which gets the currently connected network
|
||||||
|
*
|
||||||
|
* @param {function} callback Function which is invoked after command is completed
|
||||||
|
*/
|
||||||
|
public async getNetwork () {
|
||||||
|
let output : string
|
||||||
|
return new Promise((resolve : Function, reject : Function) => {
|
||||||
|
return exec(iwgetid, (err : Error, stdout : string, stderr : string) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
|
output = stdout.split('ESSID:')[1].replace(quoteRe, '').trim()
|
||||||
|
return resolve(output)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.Wifi = Wifi
|
|
@ -12,8 +12,11 @@
|
||||||
"outDir": "./lib/",
|
"outDir": "./lib/",
|
||||||
"rootDir" : "./src/",
|
"rootDir" : "./src/",
|
||||||
"paths" : {
|
"paths" : {
|
||||||
"log" : ["./lib/log"],
|
"log" : [ "./lib/log" ],
|
||||||
"delay" : [ "./lib/delay"]
|
"delay" : [ "./lib/delay" ],
|
||||||
|
"intval" : [ "./lib/intval" ],
|
||||||
|
"ble" : [ "./lib/ble" ],
|
||||||
|
"wifi" : [ "./lib/wifi" ]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exclude" : [
|
"exclude" : [
|
||||||
|
|
Loading…
Reference in New Issue