homebridge-mi-hygrothermograph
Version:
Homebridge plugin for Xiaomi Mi Bluetooth Temperature and Humidity Sensor
198 lines (175 loc) • 4.85 kB
JavaScript
const EventEmitter = require("events");
const noble = require("@stoprocent/noble");
const { Parser, EventTypes, SERVICE_DATA_UUID } = require("./parser");
class Scanner extends EventEmitter {
constructor(address, options) {
super();
options = options || {};
const {
log = console,
forceDiscovering = true,
restartDelay = 2500,
bindKey = null,
} = options;
this.log = log;
this.address = address;
this.forceDiscovering = forceDiscovering;
this.restartDelay = restartDelay;
this.bindKey = bindKey;
this.scanning = false;
this.configure();
}
configure() {
noble.on("discover", this.onDiscover.bind(this));
noble.on("scanStart", this.onScanStart.bind(this));
noble.on("scanStop", this.onScanStop.bind(this));
noble.on("warning", this.onWarning.bind(this));
noble.on("stateChange", this.onStateChange.bind(this));
}
start() {
this.log.debug("Start scanning.");
try {
noble.startScanning([], true);
this.scanning = true;
} catch (e) {
this.scanning = false;
this.log.error(e);
}
}
stop() {
this.scanning = false;
noble.stopScanning();
}
onStateChange(state) {
if (state === "poweredOn") {
this.start();
} else {
this.log.info(`Stop scanning. (${state})`);
this.stop();
}
}
onWarning(message) {
this.log.info("Warning: ", message);
}
onScanStart() {
this.log.debug("Started scanning.");
}
onScanStop() {
this.log.debug("Stopped scanning.");
// We are scanning but something stopped it. Restart scan.
if (this.scanning && this.forceDiscovering) {
setTimeout(() => {
this.log.debug("Restarting scan.");
this.start();
}, this.restartDelay);
}
}
onDiscover(peripheral) {
const {
advertisement: { serviceData } = {},
id,
address,
} = peripheral || {};
if (!this.isValidAddress(address) && !this.isValidAddress(id)) {
return;
}
const miServiceData = this.getValidServiceData(serviceData);
if (!miServiceData) {
return;
}
this.logPeripheral({ peripheral, serviceData: miServiceData });
const result = this.parseServiceData(miServiceData.data);
if (result == null) {
return;
}
if (!result.frameControl.hasEvent) {
this.log.debug("No event");
return;
}
const { eventType, event } = result;
switch (eventType) {
case EventTypes.temperature: {
const { temperature } = event;
this.emit("temperatureChange", temperature, { id, address });
break;
}
case EventTypes.humidity: {
const { humidity } = event;
this.emit("humidityChange", humidity, { id, address });
break;
}
case EventTypes.battery: {
const { battery } = event;
this.emit("batteryChange", battery, { id, address });
break;
}
case EventTypes.temperatureAndHumidity: {
const { temperature, humidity } = event;
this.emit("temperatureChange", temperature, { id, address });
this.emit("humidityChange", humidity, { id, address });
break;
}
case EventTypes.illuminance: {
const { illuminance } = event;
this.emit("illuminanceChange", illuminance, { id, address });
break;
}
case EventTypes.moisture: {
const { moisture } = event;
this.emit("moistureChange", moisture, { id, address });
break;
}
case EventTypes.fertility: {
const { fertility } = event;
this.emit("fertilityChange", fertility, { id, address });
break;
}
default: {
this.emit("error", new Error(`Unknown event type ${eventType}`));
return;
}
}
this.emit("change", event, { id, address });
}
cleanAddress(address) {
if (address == null) {
return address;
}
return address.toLowerCase().replace(/[:-]/g, "");
}
isValidAddress(address) {
return (
this.address == null ||
this.cleanAddress(this.address) === this.cleanAddress(address)
);
}
getValidServiceData(serviceData) {
return (
serviceData &&
serviceData.find((data) => data.uuid.toLowerCase() === SERVICE_DATA_UUID)
);
}
parseServiceData(serviceData) {
try {
return new Parser(serviceData, this.bindKey).parse();
} catch (error) {
this.emit("error", error);
}
}
logPeripheral({
peripheral: {
address,
id,
rssi,
advertisement: { localName },
},
serviceData,
}) {
this.log.debug(`[${address || id}] Discovered peripheral
Id: ${id}
LocalName: ${localName}
rssi: ${rssi}
serviceData: ${serviceData.data.toString("hex")}`);
}
}
module.exports = { Scanner };