homebridge-tasmota
Version:
Homebridge plugin for Tasmota devices leveraging home assistant auto discovery.
411 lines (393 loc) • 21.1 kB
JavaScript
import createDebug from 'debug';
import os from 'node:os';
import { TasmotaService } from './TasmotaService.js';
const debug = createDebug('Tasmota:sensor');
/**
* Platform Accessory
* An instance of this class is created for each accessory your platform registers
* Each accessory may expose multiple services of different service types.
*/
export class tasmotaSensorService extends TasmotaService {
platform;
accessory;
uniq_id;
outletInUse;
constructor(platform, accessory, uniq_id, outletInUse = false) {
super(platform, accessory, uniq_id);
this.platform = platform;
this.accessory = accessory;
this.uniq_id = uniq_id;
this.outletInUse = outletInUse;
let hostname;
if (!accessory.context.device[this.uniq_id].dev_cla
&& this.findDeviceClass(this.accessory.context.device[this.uniq_id].unit_of_meas, this.accessory.context.device[this.uniq_id].ic)) {
accessory.context.device[this.uniq_id].dev_cla
= this.findDeviceClass(this.accessory.context.device[this.uniq_id].unit_of_meas, this.accessory.context.device[this.uniq_id].ic);
}
switch (accessory.context.device[this.uniq_id].dev_cla) {
case 'temperature':
this.platform.log.debug('Creating %s sensor %s', accessory.context.device[this.uniq_id].dev_cla, accessory.context.device[this.uniq_id].name);
this.service = this.accessory.getService(this.uuid)
|| this.accessory.addService(this.platform.Service.TemperatureSensor, accessory.context.device[this.uniq_id].name, this.uuid);
this.service?.setCharacteristic(this.platform.Characteristic.ConfiguredName, accessory.context.device[this.uniq_id].name);
// debug('displayName', this.service?.displayName);
if (!this.service?.displayName) {
this.service?.setCharacteristic(this.platform.Characteristic.Name, accessory.context.device[this.uniq_id].name);
}
// Burr winter is coming
this.service?.getCharacteristic(this.platform.Characteristic.CurrentTemperature)
.setProps({
minValue: -100,
maxValue: 100,
});
if (this.platform.config.history) {
this.fakegato = 'custom';
}
this.characteristic = this.service?.getCharacteristic(this.platform.Characteristic.CurrentTemperature);
break;
case 'humidity':
this.platform.log.debug('Creating %s sensor %s', accessory.context.device[this.uniq_id].dev_cla, accessory.context.device[this.uniq_id].name);
this.service = this.accessory.getService(this.uuid)
|| this.accessory.addService(this.platform.Service.HumiditySensor, accessory.context.device[this.uniq_id].name, this.uuid);
this.service?.setCharacteristic(this.platform.Characteristic.ConfiguredName, accessory.context.device[this.uniq_id].name);
if (!this.service?.displayName) {
this.service?.setCharacteristic(this.platform.Characteristic.Name, accessory.context.device[this.uniq_id].name);
}
this.characteristic = this.service?.getCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity);
break;
case 'pressure':
this.platform.log.debug('Creating "%s" sensor %s', accessory.context.device[this.uniq_id].dev_cla, accessory.context.device[this.uniq_id].name, this.uuid);
this.service = this.accessory.getService(this.uuid)
|| this.accessory.addService(this.platform.CustomServices.AirPressureSensor, accessory.context.device[this.uniq_id].name, this.uuid);
this.service?.setCharacteristic(this.platform.Characteristic.ConfiguredName, accessory.context.device[this.uniq_id].name);
if (!this.service?.displayName) {
this.service?.setCharacteristic(this.platform.Characteristic.Name, accessory.context.device[this.uniq_id].name);
}
this.characteristic = this.service?.getCharacteristic(this.platform.CustomCharacteristics.AirPressure);
break;
case 'illuminance':
this.platform.log.debug('Creating %s sensor %s', accessory.context.device[this.uniq_id].dev_cla, accessory.context.device[this.uniq_id].name);
this.service = this.accessory.getService(this.uuid) || this.accessory.addService(this.platform.Service.LightSensor, accessory.context.device[this.uniq_id].name, this.uuid);
this.service?.setCharacteristic(this.platform.Characteristic.ConfiguredName, accessory.context.device[this.uniq_id].name);
if (!this.service?.displayName) {
this.service?.setCharacteristic(this.platform.Characteristic.Name, accessory.context.device[this.uniq_id].name);
}
this.characteristic = this.service?.getCharacteristic(this.platform.Characteristic.CurrentAmbientLightLevel);
break;
case 'co2':
this.platform.log.debug('Creating %s sensor %s', accessory.context.device[this.uniq_id].dev_cla, accessory.context.device[this.uniq_id].name);
this.service = this.accessory.getService(this.uuid)
|| this.accessory.addService(this.platform.Service.CarbonDioxideSensor, accessory.context.device[this.uniq_id].name, this.uuid);
this.service?.setCharacteristic(this.platform.Characteristic.ConfiguredName, accessory.context.device[this.uniq_id].name);
if (!this.service?.displayName) {
this.service?.setCharacteristic(this.platform.Characteristic.Name, accessory.context.device[this.uniq_id].name);
}
this.characteristic = this.service?.getCharacteristic(this.platform.Characteristic.CarbonDioxideLevel);
break;
case 'pm25':
this.platform.log.debug('Creating %s sensor %s', accessory.context.device[this.uniq_id].dev_cla, accessory.context.device[this.uniq_id].name);
this.service = this.accessory.getService(this.uuid)
|| this.accessory.addService(this.platform.Service.AirQualitySensor, accessory.context.device[this.uniq_id].name, this.uuid);
if (!this.service?.displayName) {
this.service?.setCharacteristic(this.platform.Characteristic.Name, accessory.context.device[this.uniq_id].name);
}
this.characteristic = this.service?.getCharacteristic(this.platform.Characteristic.AirParticulateDensity);
this.service?.setCharacteristic(this.platform.Characteristic.AirParticulateSize, this.platform.Characteristic.AirParticulateSize._2_5_M);
break;
case 'power':
switch (this.uniq_id.replace(accessory.context.identifier, '').toLowerCase()) {
case '_energy_power': // Watts
case '-dt24-watt': // dt24
if (this.platform.config.history) {
this.fakegato = 'custom';
}
this.service = this.accessory.getService(this.platform.Service.Outlet)
|| this.accessory.addService(this.platform.Service.Outlet);
// debug('this.service', this.service);
this.characteristic
= this.service?.getCharacteristic(this.deviceClassToHKCharacteristic(this.uniq_id.replace(accessory.context.identifier, '').toLowerCase()));
this.outletInUse = true;
if (!this.service.getCharacteristic(this.platform.Characteristic.OutletInUse)) {
this.service?.addCharacteristic(this.platform.Characteristic.OutletInUse);
}
// this.service?.setCharacteristic(this.platform.Characteristic.ConfiguredName, accessory.context.device[this.uniq_id].name);
// this.characteristic = this.service?.getCharacteristic(this.platform.CustomTypes.ResetTotal);
break;
case '_energy_voltage': // Voltage
case '_energy_current': // Amps
case '_energy_total': // Total Kilowatts
case '-dt24-volt': // dt24
case '-dt24-amp':
case '-dt24-watt-hour':
this.service = this.accessory.getService(this.platform.Service.Outlet)
|| this.accessory.addService(this.platform.Service.Outlet, accessory.context.device[this.uniq_id].name, this.uuid);
// debug('this.service', this.service);
this.characteristic
= this.service?.getCharacteristic(this.deviceClassToHKCharacteristic(this.uniq_id.replace(accessory.context.identifier, '').toLowerCase()));
// this.service?.setCharacteristic(this.platform.Characteristic.ConfiguredName, accessory.context.device[this.uniq_id].name);
break;
default:
this.platform.log.warn('Warning: Unhandled Tasmota power sensor type', this.uniq_id.replace(accessory.context.identifier, '').toLowerCase());
}
break;
case undefined:
// This is this Device status object
// _status is a Tasmota device, and rssi is an OpenMQTTGateway
if (this.uniq_id.replace(accessory.context.identifier, '').toLowerCase() === '_status'
|| this.uniq_id.replace(accessory.context.identifier, '').toLowerCase() === 'rssi') {
hostname = os.hostname().replace(/[^-\w ]/g, '');
this.platform.log.debug('Setting accessory information', accessory.context.device[this.uniq_id].name);
if (accessory.context.device[this.uniq_id].dev.mf && accessory.context.device[this.uniq_id].dev.mdl
&& accessory.context.device[this.uniq_id].dev.sw) {
this.accessory.getService(this.platform.Service.AccessoryInformation)
.setCharacteristic(this.platform.Characteristic.Name, accessory.context.device[this.uniq_id].dev.name)
.setCharacteristic(this.platform.Characteristic.Manufacturer, (accessory.context.device[this.uniq_id].dev.mf
?? 'undefined').replace(/[^-\w ]/g, ''))
.setCharacteristic(this.platform.Characteristic.Model, (accessory.context.device[this.uniq_id].dev.mdl
?? 'undefined').replace(/[^-\w ]/g, ''))
.setCharacteristic(this.platform.Characteristic.FirmwareRevision, (accessory.context.device[this.uniq_id].dev.sw
?? 'undefined').replace(/[^-\w. ]/g, ''))
.setCharacteristic(this.platform.Characteristic.SerialNumber, `${accessory.context.device[this.uniq_id].dev.ids[0]}-${hostname}`); // A unique fakegato ID
}
}
else {
this.platform.log.warn('Warning: missing dev_cla', accessory.context.device[this.uniq_id].name);
}
break;
default:
this.platform.log.warn('Warning: Unhandled Tasmota sensor type', accessory.context.device[this.uniq_id].dev_cla);
}
// Enable historical logging
this.enableFakegato();
this.enableStatus();
}
// Override base statusUpdate
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 = Number(this.parseValue(this.accessory.context.device[this.uniq_id].val_tpl, message.toString()));
if (value === null) {
debug('statusUpdate skipping, not for', this.service?.displayName);
return;
}
// 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 = Math.round((Number(value) - 32) * 5 / 9 * 10) / 10;
}
else {
value = Math.round(Number(value) * 10) / 10;
}
break;
case 'illuminance':
// normalize LX in the range homebridge expects
value = (value < 0.0001 ? 0.0001 : (value > 100000 ? 100000 : value));
break;
case 'co2':
if (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 (value !== null || typeof value !== 'undefined') {
if (this.characteristic?.value !== value && this.delta(this.characteristic?.value, 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);
}
if (this.outletInUse && this.service?.getCharacteristic(this.platform.Characteristic.OutletInUse)) {
this.service?.setCharacteristic(this.platform.Characteristic.OutletInUse, value > 0);
}
this.characteristic?.updateValue(value);
}
// debug('fakegato', this.platform.config.history, this.fakegato, this.device_class);
if (this.platform.config.history && this.fakegato) {
setTimeout((that) => {
// slightly delay updates for multi characteristic devices to ensure the latest data is shared
switch (that.device_class) {
case 'temperature':
debug('Updating fakegato \'%s:%s\'', that.service?.displayName, that.characteristic?.displayName, {
temp: value,
pressure: that.accessory.getService(this.platform.CustomServices.AirPressureSensor)
?.getCharacteristic(this.platform.CustomCharacteristics.AirPressure)
.value ?? 0,
humidity: that.accessory.getService(that.platform.Service.HumiditySensor)
?.getCharacteristic(that.platform.Characteristic.CurrentRelativeHumidity)
.value ?? 0,
});
that.accessory.context.fakegatoService.appendData({
temp: value,
pressure: that.accessory.getService(this.platform.CustomServices.AirPressureSensor)
?.getCharacteristic(this.platform.CustomCharacteristics.AirPressure)
.value ?? 0,
humidity: that.accessory.getService(that.platform.Service.HumiditySensor)
?.getCharacteristic(that.platform.Characteristic.CurrentRelativeHumidity)
.value ?? 0,
});
break;
case 'power':
debug('Updating fakegato \'%s:%s\'', that.characteristic?.displayName, that.service?.displayName, {
power: +value,
});
that.accessory.context.fakegatoService.appendData({
power: +value,
});
break;
case undefined:
break;
default:
that.platform.log.warn('Unknown fakegato type', that.device_class);
}
}, 1000, this);
}
}
catch (err) {
this.platform.log.error('ERROR: Message Parse Error', topic, message.toString());
this.platform.log.debug(String((err && err.message ? err.message : err)));
}
}
findDeviceClass(unit_of_meas, icon) {
// Used by ESPHome devices to create appropriate device
// ICON check is for the default Tasmota device using RSSI as a %
switch (unit_of_meas) {
case '%':
switch (icon) {
case 'mdi:gauge':
return 'humidity';
break;
default:
return undefined;
}
case '°F':
case '°C':
return 'temperature';
break;
case 'hPa':
return 'pressure';
break;
default:
return undefined;
}
}
}
/*
ZMAi-90 Power Meter Tasmota Config
ZMAi-90 with PCB version 2011 F20439
2) Set module as Tuya MCU (54)
3) run:
a)Backlog TuyaMCU 32,17; TuyaMCU 31,19; TuyaMCU 33,20; SetOption59 1
Rule1 on System#Boot do RuleTimer1 5 endon on Rules#Timer=1 do backlog SerialSend5 55aa0001000000; RuleTimer1 5 endon
rule1 1
rule2 on Energy#Power != %var1% do backlog var1 %value%; teleperiod 300 endon
rule2 1
Not needed
SetOption66 1 - Send TUYA Messages over MQTT
Status update message - BME280
{"Time":"2020-08-28T17:39:01",
"BME280":{"Temperature":21.2,"Humidity":64.5,"Pressure":991.4}
,"PressureUnit":"hPa","TempUnit":"C"}
*/
/*
{
name: 'Scanner status',
stat_t: 'tele/tasmota_00705C/HASS_STATE',
avty_t: 'tele/tasmota_00705C/LWT',
pl_avail: 'Online',
pl_not_avail: 'Offline',
json_attr_t: 'tele/tasmota_00705C/HASS_STATE',
unit_of_meas: '%',
val_tpl: "{{value_json['RSSI']}}",
ic: 'mdi:information-outline',
uniq_id: '00705C_status',
dev: {
ids: [ '00705C' ],
name: 'Scanner',
mdl: 'WiOn',
sw: '8.4.0(tasmota)',
mf: 'Tasmota'
},
tasmotaType: 'sensor'
}
*/
/*
Tasmota Model - Generic (18) - MCUIOT Device
BME280 connected to:
D5 - GPIO14 - SCL,
D6 - GPIO12 -> SDA,
D4 - GPIO2 -> LedLink
{
name: 'Sensor BME280 Temperature',
stat_t: '~SENSOR',
avty_t: '~LWT',
frc_upd: true,
pl_avail: 'Online',
pl_not_avail: 'Offline',
uniq_id: 'DC4492_BME280_Temperature',
device: { identifiers: [ 'DC4492' ], connections: [ [Array] ] },
'~': 'sonoff_DC4492/tele/',
unit_of_meas: '°C',
val_tpl: "{{value_json['BME280'].Temperature}}",
dev_cla: 'temperature',
tasmotaType: 'sensor'
}
{
name: 'Sensor BME280 Humidity',
stat_t: '~SENSOR',
avty_t: '~LWT',
frc_upd: true,
pl_avail: 'Online',
pl_not_avail: 'Offline',
uniq_id: 'DC4492_BME280_Humidity',
device: { identifiers: [ 'DC4492' ], connections: [ [Array] ] },
'~': 'sonoff_DC4492/tele/',
unit_of_meas: '%',
val_tpl: "{{value_json['BME280'].Humidity}}",
dev_cla: 'humidity',
tasmotaType: 'sensor'
}
{
name: 'Sensor BME280 Pressure',
stat_t: '~SENSOR',
avty_t: '~LWT',
frc_upd: true,
pl_avail: 'Online',
pl_not_avail: 'Offline',
uniq_id: 'DC4492_BME280_Pressure',
device: { identifiers: [ 'DC4492' ], connections: [ [Array] ] },
'~': 'sonoff_DC4492/tele/',
unit_of_meas: 'hPa',
val_tpl: "{{value_json['BME280'].Pressure}}",
dev_cla: 'pressure',
tasmotaType: 'sensor'
}
{
name: 'Sensor status',
stat_t: '~HASS_STATE',
avty_t: '~LWT',
frc_upd: true,
pl_avail: 'Online',
pl_not_avail: 'Offline',
json_attributes_topic: '~HASS_STATE',
unit_of_meas: ' ',
val_tpl: "{{value_json['RSSI']}}",
ic: 'mdi:information-outline',
uniq_id: 'DC4492_status',
device: {
identifiers: [ 'DC4492' ],
connections: [ [Array] ],
name: 'Sensor',
model: 'Generic',
sw_version: '8.1.0(sensors)',
manufacturer: 'Tasmota'
},
'~': 'sonoff_DC4492/tele/',
tasmotaType: 'sensor'
}
*/
//# sourceMappingURL=tasmotaSensorService.js.map