Skip to content

Instantly share code, notes, and snippets.

@diginfo
Last active November 28, 2019 04:03
Show Gist options
  • Save diginfo/ec01a1bbff98e111a89fb318f548db8a to your computer and use it in GitHub Desktop.
Save diginfo/ec01a1bbff98e111a89fb318f548db8a to your computer and use it in GitHub Desktop.
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