UNPKG

homebridge-mi-flora-care

Version:

This is a homebridge plugin for the Xiaomi Mi Flora / Xiaomi Flower Care devices.

418 lines 18.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MiFloraCareAccessory = void 0; const plantSensorService_1 = require("./plantSensorService"); const os_1 = __importDefault(require("os")); const fakegato_history_1 = __importDefault(require("fakegato-history")); const miflora_1 = __importDefault(require("miflora")); const hostname = os_1.default.hostname(); class MiFloraCareAccessory { constructor(log, config, api) { this.log = log; this.config = config; this.api = api; this.humidityAlertLevel = 0; this.lowLightAlertLevel = 0; this.log = log; this.config = config; this.api = api; this.Service = this.api.hap.Service; this.Characteristic = this.api.hap.Characteristic; this.PlantSensor = (0, plantSensorService_1.plantSensorService)(this.api); // extract name from config this.name = config.name || 'MiFlora'; this.displayName = this.name; this.deviceId = config.deviceId; this.interval = typeof config.interval === 'undefined' ? 1800 : config.interval; this.storedData = {}; // MiFLora scan input this._opts = { duration: 30000, ignoreUnknown: true, addresses: [this.deviceId.toLowerCase()], }; if (config.humidityAlertLevel !== null && config.humidityAlertLevel !== undefined) { this.humidityAlert = true; this.humidityAlertLevel = config.humidityAlertLevel; } else { this.humidityAlert = false; } if (config.lowLightAlertLevel !== null && config.lowLightAlertLevel !== undefined) { this.lowLightAlert = true; this.lowLightAlertLevel = config.lowLightAlertLevel; } else { this.lowLightAlert = false; } if (config.lowBatteryWarningLevel !== null && config.lowBatteryWarningLevel !== undefined && typeof config.lowBatteryWarningLevel === 'number') { this.lowBatteryWarningLevel = config.lowBatteryWarningLevel; } else { this.lowBatteryWarningLevel = 10; } // Setup services this.informationService = this._createInformationService(); this.batteryService = this._createBatteryService(); this.tempService = this._createTemperatureService(); [this.lightService, this.lowLightAlertService] = this._createLightService(); [this.humidityService, this.humidityAlertService] = this._createHumidityService(); this.plantSensorService = this._createPlantService(); this.fakeGatoHistoryService = this._createFakeGatoHistoryService(); this.log.debug('First Scan'); this._refreshInfo(); setInterval(() => { // Start scanning for updates, these will arrive in the corresponding callbacks this.log.debug('Interval Scan'); this._refreshInfo(); }, this.interval * 1000); } /* * This method is called directly after creation of this instance. * It should return all services which should be added to the accessory. */ getServices() { const services = [ this.informationService, this.batteryService, this.lightService, this.tempService, this.humidityService, this.plantSensorService, this.fakeGatoHistoryService, ]; if (this.humidityAlert && this.humidityAlertService) { services.push(this.humidityAlertService); } if (this.lowLightAlert && this.lowLightAlertService) { services.push(this.lowLightAlertService); } return services; } /** * own characteristics and services * * @returns Service */ _createPlantService() { const plantSensorService = new this.PlantSensor(this.name); plantSensorService .getCharacteristic(this.PlantSensor.SoilMoisture) .onGet(this.getCurrentMoisture.bind(this)); plantSensorService .getCharacteristic(this.PlantSensor.SoilFertility) .onGet(this.getCurrentFertility.bind(this)); return plantSensorService; } _createInformationService() { const informationService = new this.Service.AccessoryInformation(); informationService .setCharacteristic(this.Characteristic.Manufacturer, this.config.manufacturer || 'Xiaomi') .setCharacteristic(this.Characteristic.Model, this.config.model || 'Flower Care') .setCharacteristic(this.Characteristic.SerialNumber, this.config.serial || hostname + '-' + this.name); informationService .getCharacteristic(this.Characteristic.FirmwareRevision) .onGet(this.getFirmwareRevision.bind(this)); return informationService; } _createBatteryService() { const batteryService = new this.Service.Battery(this.name); batteryService .getCharacteristic(this.Characteristic.BatteryLevel) .onGet(this.getBatteryLevel.bind(this)); batteryService .setCharacteristic(this.Characteristic.ChargingState, this.Characteristic.ChargingState.NOT_CHARGEABLE); batteryService .getCharacteristic(this.Characteristic.StatusLowBattery) .onGet(this.getStatusLowBattery.bind(this)); return batteryService; } _createTemperatureService() { const tempService = new this.Service.TemperatureSensor(this.name); tempService .getCharacteristic(this.Characteristic.CurrentTemperature) .onGet(this.getCurrentTemperature.bind(this)); tempService .getCharacteristic(this.Characteristic.StatusLowBattery) .onGet(this.getStatusLowBattery.bind(this)); tempService .getCharacteristic(this.Characteristic.StatusActive) .onGet(this.getStatusActive.bind(this)); return tempService; } _createLightService() { const lightService = new this.Service.LightSensor(this.name); lightService .getCharacteristic(this.Characteristic.CurrentAmbientLightLevel) .onGet(this.getCurrentAmbientLightLevel.bind(this)); lightService .getCharacteristic(this.Characteristic.StatusLowBattery) .onGet(this.getStatusLowBattery.bind(this)); lightService .getCharacteristic(this.Characteristic.StatusActive) .onGet(this.getStatusActive.bind(this)); let lowLightAlertService; if (this.lowLightAlert) { lowLightAlertService = new this.Service.ContactSensor(this.name + ' Low Light', 'light'); lowLightAlertService .getCharacteristic(this.Characteristic.ContactSensorState) .onGet(this.getStatusLowLight.bind(this)); lowLightAlertService .getCharacteristic(this.Characteristic.StatusLowBattery) .onGet(this.getStatusLowBattery.bind(this)); lowLightAlertService .getCharacteristic(this.Characteristic.StatusActive) .onGet(this.getStatusActive.bind(this)); } else { lowLightAlertService = null; } return [ lightService, lowLightAlertService, ]; } _createHumidityService() { const humidityService = new this.Service.HumiditySensor(this.name); humidityService .getCharacteristic(this.Characteristic.CurrentRelativeHumidity) .onGet(this.getCurrentMoisture.bind(this)); humidityService .getCharacteristic(this.Characteristic.StatusLowBattery) .onGet(this.getStatusLowBattery.bind(this)); humidityService .getCharacteristic(this.Characteristic.StatusActive) .onGet(this.getStatusActive.bind(this)); let humidityAlertService; if (this.humidityAlert) { humidityAlertService = new this.api.hap.Service.ContactSensor(this.name + ' Low Humidity', 'humidity'); humidityAlertService .getCharacteristic(this.Characteristic.ContactSensorState) .onGet(this.getStatusLowMoisture.bind(this)); humidityAlertService .getCharacteristic(this.Characteristic.StatusLowBattery) .onGet(this.getStatusLowBattery.bind(this)); humidityAlertService .getCharacteristic(this.Characteristic.StatusActive) .onGet(this.getStatusActive.bind(this)); } else { humidityAlertService = null; } return [ humidityService, humidityAlertService, ]; } _createFakeGatoHistoryService() { const FakeGatoHistory = (0, fakegato_history_1.default)(this.api); const fakeGatoHistoryService = new FakeGatoHistory('room', this, { storage: 'fs' }); return fakeGatoHistoryService; } _updateData({ temperature, lux, moisture, fertility }) { // Homebridge throws an error if the emitted characteristic value is below 0.0001 if (lux < 0.0001) { lux = 0.0001; } this.log.info('Lux: %s, Temperature: %s, Moisture: %s, Fertility: %s', lux, temperature, moisture, fertility); this.storedData.data = { temperature, lux, moisture, fertility, }; this.fakeGatoHistoryService.addEntry({ time: new Date().getTime() / 1000, temp: temperature, humidity: moisture, }); this.lightService.updateCharacteristic(this.Characteristic.CurrentAmbientLightLevel, lux); this.lightService.updateCharacteristic(this.Characteristic.StatusActive, true); this.tempService.updateCharacteristic(this.Characteristic.CurrentTemperature, temperature); this.tempService.updateCharacteristic(this.Characteristic.StatusActive, true); this.humidityService.updateCharacteristic(this.Characteristic.CurrentRelativeHumidity, moisture); this.humidityService.updateCharacteristic(this.Characteristic.StatusActive, true); if (this.humidityAlert && this.humidityAlertService) { const alert = moisture <= this.humidityAlertLevel ? this.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : this.Characteristic.ContactSensorState.CONTACT_DETECTED; this.humidityAlertService.updateCharacteristic(this.Characteristic.ContactSensorState, alert); this.humidityAlertService.updateCharacteristic(this.Characteristic.StatusActive, true); } if (this.lowLightAlert && this.lowLightAlertService) { const alert = lux <= this.lowLightAlertLevel ? this.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : this.Characteristic.ContactSensorState.CONTACT_DETECTED; this.lowLightAlertService.updateCharacteristic(this.Characteristic.ContactSensorState, alert); this.lowLightAlertService.updateCharacteristic(this.Characteristic.StatusActive, true); } } _updateFirmware({ firmware, battery }) { this.log.info('Firmware: %s, Battery level: %s', firmware, battery); this.storedData.firmware = { firmwareVersion: firmware, batteryLevel: battery, }; // Update values this.informationService.updateCharacteristic(this.Characteristic.FirmwareRevision, firmware); const batteryAlert = battery <= this.lowBatteryWarningLevel ? this.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; this.batteryService.updateCharacteristic(this.Characteristic.BatteryLevel, battery); this.batteryService.updateCharacteristic(this.Characteristic.StatusLowBattery, batteryAlert); this.lightService.updateCharacteristic(this.Characteristic.StatusLowBattery, batteryAlert); this.tempService.updateCharacteristic(this.Characteristic.StatusLowBattery, batteryAlert); this.humidityService.updateCharacteristic(this.Characteristic.StatusLowBattery, batteryAlert); if (this.humidityAlert && this.humidityAlertService) { this.humidityAlertService.updateCharacteristic(this.Characteristic.StatusLowBattery, batteryAlert); } if (this.lowLightAlert && this.lowLightAlertService) { this.lowLightAlertService.updateCharacteristic(this.Characteristic.StatusLowBattery, batteryAlert); } } _scan() { const discover = (address, opts) => { // try to discover 3 times return new Promise((resolve) => { setTimeout(async () => { try { this.log.debug(`Discover Start ${address}`); const allDevices = await miflora_1.default.discover(opts); const devices = allDevices.filter(device => device.address === address); resolve(devices); } catch (error) { this.log.error(String(error)); resolve([]); } this.log.debug(`Discover End ${address}`); }, 10 * 1000); // 10 sec }); }; const scan = async (address, opts) => { try { this.log.debug(`Scan Start ${address}`); let devices = await discover(address, opts); if (devices.length === 0) { devices = await discover(address, opts); } if (devices.length === 0) { devices = await discover(address, opts); } this.log.debug(`Scan Got ${devices.length}`); return devices; } catch (error) { this.log.error(String(error)); return []; } }; return new Promise((resolveScan) => { MiFloraCareAccessory._waitScan = MiFloraCareAccessory._waitScan .then(async () => { const address = this._opts.addresses[0]; if (this.miFloraDevice) { this.log.debug(`Scan Cache ${address}`); resolveScan([this.miFloraDevice]); } else { const devices = await scan(address, this._opts); if (devices.length) { this.miFloraDevice = devices[0]; } resolveScan(devices); } await new Promise((resolve) => { setTimeout(() => { resolve(0); }, 15 * 1000); // 15 sec; }); }); }); } async _refreshInfo() { this.log.debug('Mi Flora Care scan...'); const devices = await this._scan(); if (devices.length) { try { const device = devices[0]; const data = await device.query(); // { // address: 'c4:7c:8d:6b:c9:2f', // type: 'MiFloraMonitor', // firmwareInfo: { battery: 38, firmware: '3.3.1' }, // sensorValues: { temperature: 21.8, lux: 0, moisture: 41, fertility: 273 } // } this._updateData(data.sensorValues); this._updateFirmware(data.firmwareInfo); await device.disconnect(); } catch (e) { this.log.error(String(e)); } } else { this.log.info('No devide found'); } } async getFirmwareRevision() { return this.storedData.firmware ? this.storedData.firmware.firmwareVersion : '0.0.0'; } async getBatteryLevel() { return this.storedData.firmware ? this.storedData.firmware.batteryLevel : 0; } async getStatusActive() { return this.storedData.data ? true : false; } async getStatusLowBattery() { if (this.storedData.firmware) { return this.storedData.firmware.batteryLevel <= 20 ? this.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; } else { return this.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL; } } async getStatusLowMoisture() { if (this.storedData.data) { return this.storedData.data.moisture <= this.humidityAlertLevel ? this.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : this.Characteristic.ContactSensorState.CONTACT_DETECTED; } else { return this.Characteristic.ContactSensorState.CONTACT_DETECTED; } } async getStatusLowLight() { if (this.storedData.data) { return this.storedData.data.lux <= this.lowLightAlertLevel ? this.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED : this.Characteristic.ContactSensorState.CONTACT_DETECTED; } else { return this.Characteristic.ContactSensorState.CONTACT_DETECTED; } } async getCurrentAmbientLightLevel() { return this.storedData.data ? this.storedData.data.lux : 0.0001; } async getCurrentTemperature() { return this.storedData.data ? this.storedData.data.temperature : 0.0001; } async getCurrentMoisture() { return this.storedData.data ? this.storedData.data.moisture : 0.0001; } async getCurrentFertility() { return this.storedData.data ? this.storedData.data.fertility : 0.0001; } } exports.MiFloraCareAccessory = MiFloraCareAccessory; // eslint-disable-next-line @typescript-eslint/no-explicit-any MiFloraCareAccessory._waitScan = Promise.resolve(); //# sourceMappingURL=miFloraAccessory.js.map