homebridge-tasmota
Version:
Homebridge plugin for Tasmota devices leveraging home assistant auto discovery.
207 lines • 9.85 kB
JavaScript
import createDebug from 'debug';
import os from 'node:os';
import nunjucks from 'nunjucks';
const debug = createDebug('Tasmota:Service');
export class TasmotaService {
platform;
accessory;
uniq_id;
service;
characteristic;
device_class;
statusSubscribe;
availabilitySubscribe;
fakegato;
nunjucksEnvironment;
uuid;
constructor(platform, accessory, uniq_id) {
this.platform = platform;
this.accessory = accessory;
this.uniq_id = uniq_id;
this.fakegato = '';
this.uuid = this.platform.api.hap.uuid.generate(this.accessory.context.device[this.uniq_id].uniq_id);
this.device_class = accessory.context.device[this.uniq_id].dev_cla;
this.nunjucksEnvironment = new nunjucks.Environment();
// Home Assistant device template filters
this.nunjucksEnvironment.addFilter('is_defined', (val, cb) => {
// console.log('is_defined', val, cb);
if (val || val === 0) {
cb(null, val);
}
else {
cb(new Error('missing key'), val);
}
}, true);
function float(val) {
return (Number.parseFloat(val));
}
this.nunjucksEnvironment.addGlobal('float', float);
nunjucks.installJinjaCompat();
nunjucks.configure({
autoescape: true,
});
}
enableFakegato() {
// Enable historical logging
if (this.platform.config.history && !this.accessory.context.fakegatoService?.addEntry) {
const hostname = os.hostname().split('.')[0];
this.accessory.context.fakegatoService = new this.platform.FakeGatoHistoryService('custom', this.accessory, {
storage: 'fs',
minutes: this.platform.config.historyInterval ?? 10,
log: this.platform.log,
filename: `${hostname}_${this.uniq_id}_persist.json`,
});
this.platform.log.debug('Creating fakegato service for %s %s', this.accessory.context.device[this.uniq_id].stat_t, this.accessory.context.device[this.uniq_id].name, this.accessory.context.device[this.uniq_id].uniq_id);
}
else {
debug('fakegatoService exists', this.accessory.context.device[this.uniq_id].name);
}
}
enableStatus() {
this.refresh();
if (this.characteristic) {
if (this.accessory.context.device[this.uniq_id].stat_t) {
this.platform.log.debug('Creating statusUpdate listener for %s %s', this.accessory.context.device[this.uniq_id].stat_t, this.accessory.context.device[this.uniq_id].name);
this.statusSubscribe = { event: this.accessory.context.device[this.uniq_id].stat_t, callback: this.statusUpdate.bind(this) };
this.platform.mqttHost.on(this.accessory.context.device[this.uniq_id].stat_t, this.statusUpdate.bind(this));
this.platform.mqttHost.statusSubscribe(this.accessory.context.device[this.uniq_id].stat_t);
}
if (this.accessory.context.device[this.uniq_id].avty_t) {
this.availabilitySubscribe = { event: this.accessory.context.device[this.uniq_id].avty_t, callback: this.availabilityUpdate.bind(this) };
this.platform.mqttHost.on(this.accessory.context.device[this.uniq_id].avty_t, this.availabilityUpdate.bind(this));
this.platform.mqttHost.availabilitySubscribe(this.accessory.context.device[this.uniq_id].avty_t);
}
else {
this.platform.log.warn('Warning: Availability not supported for: %s', this.accessory.context.device[this.uniq_id].name);
}
}
}
deviceClassToHKCharacteristic(device_class) {
switch (device_class) {
case '-dt24-amp':
case '_energy_current': // Amps
return (this.platform.CustomCharacteristics.ElectricCurrent);
case '_energy_voltage': // Voltage
case '-dt24-volt': // dt24
return (this.platform.CustomCharacteristics.Voltage);
case '_energy_power': // Watts
case '-dt24-watt': // dt24
return (this.platform.CustomCharacteristics.CurrentConsumption);
case '_energy_total': // Total Kilowatts
case '-dt24-watt-hour':
return (this.platform.CustomCharacteristics.TotalConsumption);
break;
}
}
refresh() {
// Get current status for accessory/service on startup
// Wild cards in topic break this
// eslint-disable-next-line no-useless-escape
if (this.accessory.context.device[this.uniq_id].stat_t && !this.accessory.context.device[this.uniq_id].stat_t.match('/\+|#/g')) {
const teleperiod = `${this.accessory.context.device[this.uniq_id].stat_t.substr(0, this.accessory.context.device[this.uniq_id].stat_t.lastIndexOf('/') + 1).replace('tele', 'cmnd')}teleperiod`;
this.platform.mqttHost.sendMessage(teleperiod, this.platform.teleperiod.toString());
}
}
statusUpdate(topic, message) {
debug('statusUpdate for "%s" on topic "%s" ->', this.service?.displayName, topic, message.toString());
this.accessory.context.timeout = this.platform.autoCleanup(this.accessory);
try {
let value = this.parseValue(this.accessory.context.device[this.uniq_id].val_tpl, message.toString());
// Sensor value tweaks or adjustments needed for homekit
switch (this.device_class) {
case 'temperature':
if (this.accessory.context.device[this.uniq_id].unit_of_meas.toUpperCase() === 'F') {
value = String(Math.round((Number(value) - 32) * 5 / 9 * 10) / 10);
}
break;
case 'illuminance':
// normalize LX in the range homebridge expects
value = String(Number(value) < 0.0001 ? 0.0001 : (Number(value) > 100000 ? 100000 : value));
break;
case 'co2':
if (Number(value) > 1200) {
this.service?.setCharacteristic(this.platform.Characteristic.CarbonDioxideDetected, this.platform.Characteristic.CarbonDioxideDetected.CO2_LEVELS_ABNORMAL);
}
else {
this.service?.setCharacteristic(this.platform.Characteristic.CarbonDioxideDetected, this.platform.Characteristic.CarbonDioxideDetected.CO2_LEVELS_NORMAL);
}
break;
}
if (this.characteristic?.value !== value && this.delta(String(this.characteristic?.value), String(value))) {
this.platform.log.info('Updating \'%s:%s\' to %s', this.service?.displayName, this.characteristic?.displayName ?? '', value);
}
else {
this.platform.log.debug('Updating \'%s:%s\' to %s', this.service?.displayName, this.characteristic?.displayName ?? '', value);
}
this.characteristic?.updateValue(value);
}
catch (err) {
this.platform.log.error('ERROR: Message Parse Error', topic, message.toString());
this.platform.log.debug(String((err && err.message ? err.message : err)));
}
}
/**
* Handle "LWT" Last Will and Testament messages from Tasmota
* These are sent when the device is no longer available from the MQTT server.
*/
availabilityUpdate(topic, message) {
// debug("availabilityUpdate", this, topic, message.toString());
this.platform.log.info('Marking accessory \'%s\' to %s', this.service?.displayName, message.toString());
if (message.toString() === this.accessory.context.device[this.uniq_id].pl_not_avail) {
const availability = new Error(`${this.accessory.displayName} ${message.toString()}`);
this.characteristic?.updateValue(availability);
}
else {
// debug("availabilityUpdate", this.characteristic);
this.characteristic?.updateValue(this.characteristic?.value);
}
}
// Utility functions for status update
delta(value1, value2) {
// debug("delta", (parseInt(value1) !== parseInt(value2)));
return (Number.parseInt(String(value1)) !== Number.parseInt(String(value2)));
}
parseValue(valueTemplate, value) {
try {
if (valueTemplate) {
// debug('nunjucksEnvironment', this, this.nunjucksEnvironment);
const template = nunjucks.compile(valueTemplate, this.nunjucksEnvironment);
// debug('nunjucksEnvironment', template, this.nunjucksEnvironment, value);
const result = template.render({ value_json: JSON.parse(value) });
// debug('nunjucksEnvironment-result', valueTemplate, value, result);
if (result) {
return result;
}
else {
return '';
}
}
else {
return value;
}
}
catch (err) {
// this.platform.log.error('ERROR: Template Parsing error', err.message);
// debug('ERROR: Template Parsing error', valueTemplate, value);
// return (err);
}
return '';
}
}
export function isTrue(value) {
if (typeof (value) === 'string') {
value = value.trim().toLowerCase();
}
switch (value) {
case true:
case 'true':
case 1:
case '1':
case 'on':
case 'yes':
return true;
default:
return false;
}
}
//# sourceMappingURL=TasmotaService.js.map