iobroker.zigbee2mqtt
Version:
Zigbee2MQTT adapter for ioBroker
735 lines (686 loc) • 19.2 kB
JavaScript
'use strict';
/*eslint no-unused-vars: ['off']*/
const utils = require('./utils.js');
/* states for device:
id - sysname of state, id
name - display name of state
prop - attr name of payload object with value of state
icon - url of state icon
role - state role
write, read - allow to write and read state from admin
type - type of value
isEvent - sign of clearing the value after 300ms
isOption - if state is internal setting, not to be sent to device
inOptions - if true, value of this state will be included in options argument of other states setter(Opt)
getter - result of call is the value of state. if value is undefined - state not apply
setter - result of call is the value for publish to zigbee
setterOpt - result of call is the options for publish to zigbee
setattr - name of converter to zigbee, if it different from "prop" value
epname - endpoint name for publish
lazy - if true, then the state will not be created until the first event for the specified state arrives
*/
const nameLookup = {
C: 'temperature',
'%': 'humidity',
m: 'altitude',
Pa: 'pressure',
ppm: 'quality',
psize: 'particle_size',
V: 'voltage',
A: 'current',
Wh: 'energy',
W: 'power',
Hz: 'frequency',
pf: 'power_factor',
lx: 'illuminance_lux',
};
const unitLookup = {
temperature: 'C',
humidity: '%',
altitude: 'm',
pressure: 'Pa',
quality: 'ppm',
particle_size: 'psize',
voltage: 'V',
current: 'A',
energy: 'Wh',
power: 'W',
frequency: 'Hz',
power_factor: 'pf',
illuminance_lux: 'lx',
};
const states = {
link_quality: {
id: 'link_quality',
prop: 'linkquality',
name: 'Link quality',
icon: undefined,
role: 'state',
write: false,
read: true,
type: 'number',
def: 0,
min: 0,
max: 255,
},
available: {
id: 'available',
prop: 'available',
name: 'Available',
icon: undefined,
role: 'indicator.reachable',
write: false,
read: true,
type: 'boolean',
def: false,
},
last_seen: {
id: 'last_seen',
prop: 'last_seen',
name: 'The date/time of last Zigbee message',
icon: undefined,
role: 'state',
write: false,
read: true,
type: 'string',
def: '',
getter: (payload) => {
const val = payload.last_seen;
if (val == null) {
return undefined;
}
// Z2M kann last_seen als Unix-Timestamp (number in ms) ODER als ISO-String senden
if (typeof val === 'number') {
return new Date(val).toISOString().replace('T', ' ').split('.')[0];
}
if (typeof val === 'string') {
return val.replace('T', ' ').split('+')[0].split('.')[0];
}
return String(val);
},
},
simulated_brightness: {
id: 'simulated_brightness',
prop: 'brightness',
name: 'Simulated brightness',
icon: undefined,
role: 'level.dimmer',
write: true,
read: true,
type: 'number',
def: 0,
unit: '%',
// Fix: null-Guard – bulbLevelToAdapterLevel(undefined) würde 0 liefern (Lampe scheinbar aus)
getter: (payload) => {
if (payload.brightness == null) { return undefined; }
return utils.bulbLevelToAdapterLevel(payload.brightness);
},
},
// Fix: prop ergänzt – statesController sucht zuerst per state.prop === payload-key
state: {
id: 'state',
prop: 'state',
name: 'Switch state',
icon: undefined,
role: 'switch',
write: true,
read: true,
type: 'boolean',
def: false,
getter: (payload) => payload.state === 'ON',
setter: (value) => (value ? 'ON' : 'OFF'),
},
brightness: {
id: 'brightness',
prop: 'brightness',
name: 'Brightness',
options: ['transition'],
icon: undefined,
role: 'level.dimmer',
write: true,
read: true,
type: 'number',
def: 100,
unit: '%',
min: 0,
max: 100,
// Fix: null-Guard – payload ohne brightness-Key liefert sonst fälschlich 0
getter: (payload) => {
if (payload.brightness == null) { return undefined; }
return utils.bulbLevelToAdapterLevel(payload.brightness);
},
setter: (value) => utils.adapterLevelToBulbLevel(value),
},
brightness_move: {
id: 'brightness_move',
prop: 'brightness_move',
name: 'Dimming',
icon: undefined,
role: 'state',
write: true,
read: false,
type: 'number',
def: 0,
min: -50,
max: 50,
},
colortemp_move: {
id: 'colortemp_move',
prop: 'color_temp_move',
name: 'Colortemp change',
icon: undefined,
role: 'state',
write: true,
read: false,
type: 'number',
def: 0,
min: -50,
max: 50,
},
transition: {
id: 'transition',
prop: 'transition',
name: 'Transition time overwrite (-1 disabled)',
icon: undefined,
role: 'state',
write: true,
read: false,
type: 'number',
def: -1,
unit: 'sec',
min: -1,
max: 65535,
isOption: true,
},
send_payload: {
id: 'send_payload',
name: 'Send a raw json payload',
icon: undefined,
role: 'json',
write: true,
read: false,
type: 'string',
def: '{}',
},
// Fix: prop ergänzt – Z2M sendet payload.voltage
voltage: {
id: 'voltage',
prop: 'voltage',
name: 'Voltage',
icon: undefined,
role: 'value.voltage',
write: false,
read: true,
type: 'number',
def: 0,
unit: 'V',
},
// Fix: prop ergänzt – Z2M sendet payload.voltage (Battery-Variante)
battery_voltage: {
id: 'voltage',
prop: 'voltage',
name: 'Battery voltage',
icon: undefined,
role: 'battery.voltage',
write: false,
read: true,
type: 'number',
def: 0,
unit: 'V',
},
// Fix: prop ergänzt – Z2M sendet payload.energy
energy: {
id: 'energy',
prop: 'energy',
name: 'Sum of consumed energy',
icon: undefined,
role: 'value.power.consumption',
write: false,
read: true,
type: 'number',
def: 0,
unit: 'kWh',
},
battery: {
id: 'battery',
prop: 'battery',
name: 'Battery percent',
icon: undefined,
role: 'value.battery',
write: false,
read: true,
type: 'number',
def: 0,
unit: '%',
min: 0,
max: 100,
},
// Fix: prop ergänzt – Z2M sendet payload.device_temperature
device_temperature: {
id: 'device_temperature',
prop: 'device_temperature',
name: 'Temperature of the device',
icon: undefined,
role: 'value.temperature',
write: false,
read: true,
type: 'number',
def: 0,
unit: '°C',
},
// Fix: prop ergänzt – Z2M sendet payload.temperature
temperature: {
id: 'temperature',
prop: 'temperature',
name: 'Temperature',
icon: undefined,
role: 'value.temperature',
write: false,
read: true,
type: 'number',
def: 0,
unit: '°C',
},
humidity: {
id: 'humidity',
prop: 'humidity',
name: 'Humidity',
icon: undefined,
role: 'value.humidity',
write: false,
read: true,
type: 'number',
def: 0,
unit: '%',
min: 0,
max: 100,
},
pressure: {
id: 'pressure',
prop: 'pressure',
name: 'Pressure',
icon: undefined,
role: 'value.pressure',
write: false,
read: true,
type: 'number',
def: 0,
unit: 'hPa',
min: 0,
max: 10000,
},
illuminance: {
id: 'illuminance',
prop: 'illuminance_lux',
name: 'Illuminance',
icon: undefined,
role: 'value.brightness',
write: false,
read: true,
type: 'number',
def: 0,
unit: 'lux',
},
/**
* illuminance (Lux-State) – ausgelöst durch payload.illuminance.
*
* Fall 1 – beide Werte im Payload (illuminance + illuminance_raw):
* payload.illuminance wird direkt übernommen (neues Z2M sendet bereits Lux).
*
* Fall 2 – nur illuminance im Payload:
* payload.illuminance wird direkt übernommen.
*
* Fall 3 – nur illuminance_raw im Payload:
* Dieser State wird NICHT von diesem Eintrag gesetzt (prop='illuminance' nicht vorhanden).
* Stattdessen übernimmt illuminance_from_raw (prop='illuminance_raw') die Berechnung.
*/
illuminance_direct: {
id: 'illuminance',
prop: 'illuminance',
name: 'Illuminance',
icon: undefined,
role: 'value.brightness',
write: false,
read: true,
type: 'number',
def: 0,
unit: 'lux',
},
/**
* illuminance_raw (Rohwert-State) – ausgelöst durch payload.illuminance.
*
* Fall 2 – nur illuminance im Payload:
* illuminance_raw wird aus dem Lux-Wert zurückgerechnet:
* raw = round(log10(lux) * 10000 + 1)
*
* Fall 1 – beide Werte im Payload:
* Getter gibt undefined zurück → dieser State wird NICHT gesetzt,
* stattdessen setzt illuminance_raw_direct (prop='illuminance_raw') den Rohwert direkt.
*/
illuminance_raw_from_lux: {
id: 'illuminance_raw',
prop: 'illuminance',
name: 'Illuminance raw',
icon: undefined,
role: 'value.brightness',
write: false,
read: true,
type: 'number',
def: 0,
unit: '',
getter: (payload) => {
// Wenn illuminance_raw bereits im Payload → illuminance_raw_direct setzt ihn direkt
if (payload.illuminance_raw != null) { return undefined; }
const lux = payload.illuminance;
if (lux == null) { return undefined; }
if (lux <= 0) { return 0; }
return Math.round(Math.log10(lux) * 10000 + 1);
},
},
/**
* illuminance_raw (Rohwert-State) – ausgelöst durch payload.illuminance_raw.
*
* Fall 1 – beide Werte im Payload: direkt übernehmen.
* Fall 3 – nur illuminance_raw im Payload: direkt übernehmen.
*/
illuminance_raw_direct: {
id: 'illuminance_raw',
prop: 'illuminance_raw',
name: 'Illuminance raw',
icon: undefined,
role: 'value.brightness',
write: false,
read: true,
type: 'number',
def: 0,
unit: '',
},
/**
* illuminance (Lux-State) – ausgelöst durch payload.illuminance_raw.
*
* Fall 3 – nur illuminance_raw im Payload:
* Lux wird berechnet: lux = round(10^((raw - 1) / 10000))
*
* Fall 1 – beide Werte im Payload:
* Getter gibt undefined zurück → illuminance_direct (prop='illuminance') setzt Lux direkt.
*/
illuminance_from_raw: {
id: 'illuminance',
prop: 'illuminance_raw',
name: 'Illuminance',
icon: undefined,
role: 'value.brightness',
write: false,
read: true,
type: 'number',
def: 0,
unit: 'lux',
getter: (payload) => {
// Wenn illuminance bereits im Payload → illuminance_direct setzt Lux direkt
if (payload.illuminance != null) { return undefined; }
const raw = payload.illuminance_raw;
if (raw == null) { return undefined; }
if (raw <= 0) { return 0; }
return Math.round(Math.pow(10, (raw - 1) / 10000));
},
},
/**
* illuminance (Lux-State) – ausgelöst durch payload.illuminance_lux.
* Geräte wie Philips HUE Motion, IKEA liefern illuminance_lux (bereits Lux).
* Wert wird direkt übernommen.
*/
illuminance_lux_direct: {
id: 'illuminance',
prop: 'illuminance_lux',
name: 'Illuminance',
icon: undefined,
role: 'value.brightness',
write: false,
read: true,
type: 'number',
def: 0,
unit: 'lux',
},
/**
* illuminance_raw (Rohwert-State) – ausgelöst durch payload.illuminance_lux.
* Berechnet: raw = round(log10(lux) * 10000 + 1)
* NUR wenn kein illuminance_raw im Payload – kein Überschreiben.
*/
illuminance_raw_from_lux2: {
id: 'illuminance_raw',
prop: 'illuminance_lux',
name: 'Illuminance raw',
icon: undefined,
role: 'value.brightness',
write: false,
read: true,
type: 'number',
def: 0,
unit: '',
getter: (payload) => {
if (payload.illuminance_raw != null) { return undefined; }
const lux = payload.illuminance_lux;
if (lux == null) { return undefined; }
if (lux <= 0) { return 0; }
return Math.round(Math.log10(lux) * 10000 + 1);
},
},
// Fix: prop ergänzt – Z2M sendet payload.occupancy
occupancy: {
id: 'occupancy',
prop: 'occupancy',
name: 'Occupancy',
icon: undefined,
role: 'sensor.motion',
write: false,
read: true,
type: 'boolean',
def: false,
},
contact: {
id: 'contact',
prop: 'contact',
name: 'Contact event',
icon: undefined,
role: 'state',
write: false,
read: true,
type: 'boolean',
def: false,
},
opened: {
id: 'opened',
prop: 'contact',
name: 'Is open',
icon: undefined,
role: 'sensor.window',
write: false,
read: true,
type: 'boolean',
def: false,
// Fix: null-Guard – !undefined wäre true (Tür fälschlich offen)
getter: (payload) => {
if (payload.contact == null) { return undefined; }
return !payload.contact;
},
},
tamper: {
id: 'tampered',
prop: 'tamper',
name: 'Is tampered',
icon: undefined,
role: 'state',
write: false,
read: true,
type: 'boolean',
def: false,
},
water_leak: {
id: 'detected',
prop: 'water_leak',
name: 'Water leak detected',
icon: undefined,
role: 'indicator.alarm.flood',
write: false,
read: true,
type: 'boolean',
def: false,
},
batt_low_t_f: {
id: 'battery_low',
prop: 'battery_low',
name: 'Battery Status Low',
icon: undefined,
role: 'indicator.lowbat',
write: false,
read: true,
type: 'boolean',
def: false,
},
load_power: {
id: 'load_power',
prop: 'power',
name: 'Load power',
icon: undefined,
role: 'value.power',
write: false,
read: true,
type: 'number',
def: 0,
unit: 'W',
},
load_current: {
id: 'load_current',
prop: 'current',
name: 'Load current',
icon: undefined,
role: 'value.current',
write: false,
read: true,
type: 'number',
def: 0,
unit: 'A',
},
temp_calibration: {
id: 'temperature_calibration',
prop: 'temperature_calibration',
name: 'Temperature Calibration Offset',
icon: undefined,
role: 'value.temperature',
write: true,
read: true,
type: 'number',
def: 0,
unit: '°C',
isOption: true,
},
local_temperature: {
id: 'local_temperature',
prop: 'local_temperature',
name: 'Local Temperature',
icon: undefined,
role: 'value.temperature',
write: false,
read: true,
type: 'number',
def: 0,
unit: '°C',
},
local_temperature_calibration: {
id: 'local_temperature_calibration',
prop: 'local_temperature_calibration',
name: 'Temperature Calibration',
icon: undefined,
role: 'level',
write: true,
read: true,
type: 'number',
def: 0,
unit: '°C',
},
climate_away_mode: {
id: 'away_mode',
prop: 'away_mode',
name: 'Away',
icon: undefined,
role: 'state',
write: true,
read: true,
type: 'boolean',
def: false,
isEvent: true,
getter: (payload) => payload.away_mode === 'ON',
setter: (value) => (value ? 'ON' : 'OFF'),
},
// Fix: def ergänzt – ioBroker meldet Fehler beim Anlegen ohne def bei type:'string'
climate_system_mode: {
id: 'mode',
prop: 'system_mode',
name: 'Mode',
icon: undefined,
role: 'state',
write: true,
read: true,
type: 'string',
def: '',
states: { auto: 'auto', off: 'off', heat: 'heat' },
},
// Fix: write:false – running_state ist ein reiner Sensor-State (Z2M sendet nur, setzt nie)
// Fix: def ergänzt
climate_running_mode: {
id: 'running_state',
prop: 'running_state',
name: 'Running Mode',
icon: undefined,
role: 'state',
write: false,
read: true,
type: 'string',
def: '',
states: { idle: 'idle', heat: 'heat' },
},
brightness_step: {
id: 'brightness_step',
prop: 'brightness_step',
name: 'Brightness stepping',
icon: undefined,
role: 'state',
write: true,
read: false,
type: 'number',
def: 0,
min: -50,
max: 50,
},
hue_move: {
id: 'hue_move',
prop: 'hue_move',
name: 'Hue change',
icon: undefined,
role: 'state',
write: true,
read: false,
type: 'number',
def: 0,
min: -50,
max: 50,
},
child_lock: {
id: 'lock',
prop: 'child_lock',
name: 'Locked',
icon: undefined,
role: 'state',
write: true,
read: true,
type: 'boolean',
def: false,
getter: (payload) => payload.child_lock === 'LOCKED',
setter: (value) => (value ? 'LOCKED' : 'UNLOCKED'),
},
};
module.exports = {
states: states,
unitLookup: unitLookup,
nameLookup: nameLookup,
};