Last active
November 28, 2019 04:03
-
-
Save diginfo/ec01a1bbff98e111a89fb318f548db8a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const cl = console.log; | |
const noble = require('@abandonware/noble'); | |
const childProcess = require('child_process'); | |
//exec('sudo systemctl restart bluetooth'); | |
process.on('SIGINT', function() { | |
cl("Quitting Noble."); | |
noble.stopScanning(); | |
process.exit(); | |
}); | |
function exec(cmd,cb=cl){ | |
childProcess.exec(cmd,function (error, stdout, stderr) { | |
//cl(`exec() cmd:${cmd}, error:${error},stdout:${stdout},stderr:${stderr}`) | |
if(typeof(cb)=="function") { | |
if(stderr && !stderr.startsWith('INFO')) return cb({error:true,msg:stderr.toString()}); | |
return cb(stdout.toString()); | |
} else return null; | |
}); | |
} | |
function buf2arr(bufdata){ | |
var buffer = Buffer.from(bufdata) | |
return Array.prototype.slice.call(buffer, 0) | |
} | |
let sleep = require('util').promisify(setTimeout); | |
// async | |
function readServiceData(data,peripheral) { | |
//if (data.length < 5) return null; | |
const buff = Buffer.from(data); | |
const result = { | |
counter : new Date().getTime(), | |
id : peripheral.id, | |
mac : peripheral.address, | |
raw : Array.prototype.slice.call(buff, 0), | |
mfgdata : buf2arr(peripheral.advertisement.manufacturerData) | |
}; | |
result.event = { | |
data: { | |
/*batt : null,*/ | |
rssi : peripheral.rssi, | |
tmp : result.raw[1]+result.raw[2]/10, | |
hum : result.raw[4]+result.raw[5]/10, | |
stamp : new Date().getTime() | |
} | |
} | |
return result; | |
} | |
//var gwmod = ''; // change later for running as a script. | |
var gwmod = '/home/pi/mozilla-iot/gateway/node_modules/'; | |
const { | |
Adapter, | |
Device, | |
Property | |
} = require(`${gwmod}gateway-addon`); | |
class TemperatureHumiditySensor extends Device { | |
constructor(adapter, manifest, id) { | |
//super(adapter, `${manifest.display_name}-${id}`); | |
super(adapter,id); | |
this['@context'] = 'https://iot.mozilla.org/schemas/'; | |
this['@type'] = ['TemperatureSensor']; | |
this.name = manifest.display_name; | |
//this.name = id; | |
//this.description = manifest.description; | |
this.description = 'room1'; | |
this.addProperty({ | |
type: 'number', | |
'@type': 'TemperatureProperty', | |
minimum: -127.99, | |
maximum: 127.99, | |
multipleOf: 0.1, | |
unit: 'degree celsius', | |
title: 'temperature', | |
description: 'The ambient temperature', | |
readOnly: true | |
}); | |
this.addProperty({ | |
type: 'number', | |
minimum: 0, | |
maximum: 100, | |
multipleOf: 0.1, | |
unit: '%', | |
title: 'humidity', | |
description: 'The relative humidity', | |
readOnly: true | |
}); | |
this.addProperty({ | |
type: 'number', | |
minimum: -100, | |
maximum: 0, | |
multipleOf: 0.1, | |
unit: 'dbi', | |
title: 'rssi', | |
description: 'Signal Strength', | |
readOnly: true | |
}); | |
this.addProperty({ | |
type: 'number', | |
minimum: 0, | |
maximum: 100, | |
multipleOf: 0.1, | |
unit: '%', | |
title: 'batt', | |
description: 'Battery Level', | |
readOnly: true | |
}); | |
} | |
// function. | |
addProperty(description) { | |
const property = new Property(this, description.title, description); | |
this.properties.set(description.title, property); | |
} | |
// this function should get & set data ??? | |
setData(data) { | |
cl(`${data.id}.setData(${JSON.stringify(data)})`); | |
if(isdev) this.adapter.manager.sendPropertyChangedNotification = function (property){}; | |
/* | |
{ | |
mac: '', | |
capability: '', | |
raw: [ 0, 28, 4, 0, 66, 0 ], | |
event: { | |
raw: [ 0, 28, 4, 0, 66, 0 ], | |
data: { | |
batt: 41, | |
rssi: -67, | |
tmp: 28.4, | |
hum: 66, | |
stamp: 1574738827049 | |
} | |
} | |
} | |
*/ | |
const result = data; | |
//const result = readServiceData(serviceData.data); | |
if (result.event && result.event.data) { | |
const data = result.event.data; | |
if (data.batt) { | |
const batt = result.event.data.batt; | |
const property = this.properties.get('batt'); | |
property.setCachedValue(batt); | |
this.notifyPropertyChanged(property); | |
} | |
if (data.rssi) { | |
const rssi = result.event.data.rssi; | |
const property = this.properties.get('rssi'); | |
property.setCachedValue(rssi); | |
this.notifyPropertyChanged(property); | |
} | |
if (data.tmp) { | |
const temperature = result.event.data.tmp; | |
const property = this.properties.get('temperature'); | |
property.setCachedValue(temperature); | |
this.notifyPropertyChanged(property); | |
} | |
if (data.hum) { | |
const humidity = result.event.data.hum; | |
const property = this.properties.get('humidity'); | |
property.setCachedValue(humidity); | |
this.notifyPropertyChanged(property); | |
} | |
} | |
} | |
} | |
class TemperatureHumiditySensorAdapter extends Adapter { | |
/** | |
* Example process ro remove a device from the adapter. | |
* The important part is to call: `this.handleDeviceRemoved(device)` | |
* @param {String} deviceId ID of the device to remove. | |
* @return {Promise} which resolves to the device removed. | |
*/ | |
removeDevice(deviceId) { | |
cl('@@ removeDevice():',deviceId); | |
return new Promise((resolve, reject) => { | |
const device = this.devices[deviceId]; | |
if (device) { | |
this.handleDeviceRemoved(device); | |
resolve(device); | |
} else { | |
reject(`Device: ${deviceId} not found.`); | |
} | |
}); | |
} | |
/** | |
* Unpaira the provided the device from the adapter. | |
* @param {Object} device Device to unpair with | |
*/ | |
removeThing(device) { | |
cl('remove Device:', this.name, 'id', this.id,'removeThing(', device.id, ') started'); | |
this.removeDevice(device.id).then(() => { | |
cl('ExampleAdapter: device:', device.id, 'was unpaired.'); | |
}).catch((err) => { | |
cl('ExampleAdapter: unpairing', device.id, 'failed'); | |
cl(err); | |
}); | |
} | |
constructor(addonManager, manifest) { | |
super(addonManager, TemperatureHumiditySensorAdapter.name, manifest.name); | |
//this.pollInterval = manifest.moziot.config.pollInterval; | |
const me = this; | |
this.pollInterval = 30000; | |
this.knownDevices = {}; | |
this.polling = null; | |
addonManager.addAdapter(this); | |
// async-connect function | |
const connect = function(peripheral,cb){ | |
if(peripheral.connect && peripheral.state != 'connected'){ | |
peripheral.once('connect',function(error){; | |
//cl(`@@ ${peripheral.id} ${peripheral.state} connected.`) | |
return cb(peripheral.state=='connected'); | |
}); | |
peripheral.connect(); | |
} | |
else return cb(peripheral.state=='connected') | |
} | |
const __connect = async function(peripheral){ | |
if(peripheral.state != 'connected'){ | |
return await peripheral.once('connect',function(error){; | |
cl(`${peripheral.id} connected.`) | |
return(peripheral.state==true); | |
}); | |
peripheral.connect(); | |
} | |
else return(peripheral.state==true); | |
} | |
const __getBatt = function(peripheral,cb){ | |
connect(peripheral,function(){ | |
peripheral.discoverServices(['180f'], function(error, services) { | |
var batteryService = services[0]; | |
cl('discoveredBatter service'); | |
batteryService.discoverCharacteristics(['2a19'], function(error, characteristics) { | |
var batteryLevelCharacteristic = characteristics[0]; | |
cl('discovered Battery Level characteristic'); | |
batteryLevelCharacteristic.on('data', function(data, isNotification) { | |
cl('battery level is now: ', data.readUInt8(0) + '%'); | |
}); | |
// to enable notify | |
batteryLevelCharacteristic.subscribe(function(error) { | |
cb('battery level notification on'); | |
}); | |
}); | |
}); // peripheral.discoverServices | |
}) // connect | |
} | |
// Read a service characteristic | |
const read = function(peripheral,dsvc,dchr,cb){ | |
connect(peripheral,function(ok){ | |
peripheral.once('servicesDiscover', function(services){ | |
if(services.length) { | |
const service = services[0]; | |
//cl(`got-service ${dsvc}`) | |
service.once('characteristicsDiscover', function(devchr){ | |
//cl(`got-char ${dchr}`) | |
return cb(devchr[0]); // buffer ?? | |
}); | |
service.discoverCharacteristics([dchr]); | |
} | |
else { | |
cl('@@@ error no-services'); | |
} | |
}); | |
peripheral.discoverServices([dsvc]); | |
}) | |
} | |
// battery data | |
const getBatt = function(peripheral,cb){ | |
read(peripheral,'180f','2a19',function(cdata){ | |
cdata.read(function(err,bufdata){ | |
cb(buf2arr(bufdata)[0]); | |
//cl(`Battery: ${arr[0]}%`); | |
}); | |
}) | |
} | |
// temperature & humidity | |
const getData = function(peripheral,cb){ | |
read(peripheral,'aa20','aa21',function(cdata){ | |
cl(`getData(${cdata})`); | |
if(cdata) cdata.read(function(error, data) { | |
return cb(readServiceData(data,peripheral)); | |
}) | |
}) | |
} | |
// scan for new devices | |
const scan = function(duplicates=false){ | |
noble.stopScanning(); | |
setTimeout(function(){ | |
noble.startScanning(['aa20'],duplicates); | |
},1000); | |
} | |
// polling disables & re-enables scaning. | |
const poll = function(ms=30000){ | |
clearTimeout(me.polling); | |
cl(`start-polling ${ms} @ ${new Date()}`); | |
me.polling = setTimeout(function(){ | |
cl(`stop-polling @ ${new Date()}`); | |
scan(); | |
if(me.pollInterval>-1) poll(me.pollInterval); | |
},ms) | |
} | |
const discon = function(peripheral,cb=cl){ | |
if(peripheral.state == 'disconnected') return cb(true); | |
peripheral.once('disconnect', function(error){; | |
// cl(`${peripheral.id}.disconnected.`); | |
return cb(peripheral.state=='disconnected') | |
}) | |
peripheral.disconnect(); | |
} | |
// initialise | |
noble.on('stateChange', (state) => { | |
console.log('Noble adapter is %s', state); | |
if (state === 'poweredOn') scan(); | |
}); | |
// discovered | |
noble.on('discover', (peripheral) => { | |
// if(peripheral.advertisement.serviceUuids[0] != 'aa20') return; | |
const id = peripheral.id; | |
let knownDevice = this.knownDevices[id]; | |
// if unknown then add it. | |
if (!knownDevice) { | |
cl(`Detected new Sensor ${id}`); | |
knownDevice = new TemperatureHumiditySensor(this, manifest, id); | |
this.handleDeviceAdded(knownDevice); | |
this.knownDevices[id] = knownDevice; | |
} | |
// Connection-only-test | |
if(1==2){ | |
cl(`${id}.discovered(${peripheral.state})`); | |
discon(peripheral,function(ok){ | |
cl(`${id}.disconnect(${ok})`) | |
}); | |
return; | |
} | |
// data-only (reading more than 1 char seems to kill it) | |
if(1==1){ | |
//cl(`${id}.discovered(${peripheral.state})`); | |
getData(peripheral,function(data){ | |
//cl(`${id}.data: ${JSON.stringify(data.event,null,2)}`); | |
knownDevice.setData(data); | |
discon(peripheral); | |
}) | |
} | |
// get-all (unreliable) | |
if(1==2){ | |
//cl(`${id}.discovered(${peripheral.state})`); | |
getBatt(peripheral,function(batt){ | |
cl(`${id}.battery: ${batt}%`); | |
setTimeout(function(){ | |
//getData(peripheral,function(data){ | |
//data.event.data.batt = batt; | |
//cl(`${id}.data: ${JSON.stringify(data.event,null,2)}`); | |
//knownDevice.setData(data); | |
discon(peripheral,function(ok){ | |
//cl(`${id}.disconnect(${ok})\r\n`) | |
}); | |
//}) | |
},500); | |
}); | |
} | |
}); | |
// Polling - Restart discovery | |
if(this.pollInterval>-1) poll(this.pollInterval); | |
} | |
} | |
// Run this as a stand-alone script for development | |
// node /home/pi/.mozilla-iot/addons/xiaomi-temperature-humidity-sensor-adapter/xiaomi-temperature-humidity-sensor-adapter.js | |
// sudo systemctl restart bluetooth; sudo systemctl restart mozilla-iot-gateway.service; journalctl -f | grep xiaomi | |
var isdev = false; | |
const debug = 1; | |
if(require.main === module){ | |
isdev = true; | |
gwmod = '/home/pi/mozilla-iot/gateway/node_modules/'; | |
const manifest = require('/home/pi/.mozilla-iot/addons/xiaomi-temperature-humidity-sensor-adapter/manifest.json'); | |
if(!manifest.moziot) manifest.moziot = {config:{pollInterval:null}}; | |
const addonManager = { | |
handleDeviceAdded: function(knownDevice){ | |
cl('handleDeviceAdded()') | |
}, | |
addAdapter: function(){ | |
cl('addAdapter()') | |
} | |
}; | |
// restart bluetooth. | |
exec('sudo systemctl restart bluetooth',function(err){ | |
test = new TemperatureHumiditySensorAdapter(addonManager,manifest); | |
}) | |
} | |
module.exports = TemperatureHumiditySensorAdapter; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment