mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2026-08-14 14:50:16 +00:00
Fix Home Assistant template variable warnings (#9088)
* Fix more template warnings. https://github.com/Koenkk/zigbee2mqtt/issues/8769 * Update * Updates
This commit is contained in:
+3
-3
@@ -145,6 +145,9 @@ class Controller {
|
||||
this.exitCallback(1);
|
||||
}
|
||||
|
||||
// Call extensions
|
||||
await this.callExtensions('start', this.extensions);
|
||||
|
||||
// Send all cached states.
|
||||
if (settings.get().advanced.cache_state_send_on_startup && settings.get().advanced.cache_state) {
|
||||
for (const device of devices) {
|
||||
@@ -154,9 +157,6 @@ class Controller {
|
||||
}
|
||||
}
|
||||
|
||||
// Call extensions
|
||||
await this.callExtensions('start', this.extensions);
|
||||
|
||||
if (settings.get().advanced.last_seen && settings.get().advanced.last_seen !== 'disable') {
|
||||
this.eventBus.onLastSeenChanged(this, (data) =>
|
||||
this.publishEntityState(data.device, {}, 'lastSeenChanged'));
|
||||
|
||||
@@ -8,11 +8,12 @@ import Extension from './extension';
|
||||
import bind from 'bind-decorator';
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
interface DiscoveryEntry {type: string, object_id: string, discovery_payload: KeyValue}
|
||||
interface DiscoveryEntry {mockProperties: string[], type: string, object_id: string, discovery_payload: KeyValue}
|
||||
|
||||
const sensorClick = {
|
||||
type: 'sensor',
|
||||
object_id: 'click',
|
||||
mockProperties: ['click'],
|
||||
discovery_payload: {
|
||||
icon: 'mdi:toggle-switch',
|
||||
value_template: '{{ value_json.click }}',
|
||||
@@ -36,7 +37,7 @@ const featurePropertyWithoutEndpoint = (feature: zhc.DefinitionExposeFeature): s
|
||||
* This extensions handles integration with HomeAssistant
|
||||
*/
|
||||
export default class HomeAssistant extends Extension {
|
||||
private discovered: {[s: string]: string[]} = {};
|
||||
private discovered: {[s: string]: {topics: Set<string>, mockProperties: Set<string>}} = {};
|
||||
private mapping: {[s: string]: DiscoveryEntry[]} = {};
|
||||
private discoveredTriggers : {[s: string]: Set<string>}= {};
|
||||
private legacyApi = settings.get().advanced.legacy_api;
|
||||
@@ -91,7 +92,7 @@ export default class HomeAssistant extends Extension {
|
||||
assert(entityType === 'device' || groupSupportedTypes.includes(firstExpose.type),
|
||||
`Unsupported expose type ${firstExpose.type} for group`);
|
||||
|
||||
const discoveryEntries = [];
|
||||
const discoveryEntries: DiscoveryEntry[] = [];
|
||||
const endpoint = entityType === 'device' ? exposes[0].endpoint : undefined;
|
||||
const getProperty = (feature: zhc.DefinitionExposeFeature): string => entityType === 'group' ?
|
||||
featurePropertyWithoutEndpoint(feature) : feature.property;
|
||||
@@ -102,10 +103,12 @@ export default class HomeAssistant extends Extension {
|
||||
const hasColorHS = exposes.find((expose) => expose.features.find((e) => e.name === 'color_hs'));
|
||||
const hasBrightness = exposes.find((expose) => expose.features.find((e) => e.name === 'brightness'));
|
||||
const hasColorTemp = exposes.find((expose) => expose.features.find((e) => e.name === 'color_temp'));
|
||||
const state = firstExpose.features.find((f) => f.name === 'state');
|
||||
|
||||
const discoveryEntry: DiscoveryEntry = {
|
||||
type: 'light',
|
||||
object_id: endpoint ? `light_${endpoint}` : 'light',
|
||||
mockProperties: [state.property],
|
||||
discovery_payload: {
|
||||
brightness: !!hasBrightness,
|
||||
schema: 'json',
|
||||
@@ -149,6 +152,7 @@ export default class HomeAssistant extends Extension {
|
||||
const discoveryEntry: DiscoveryEntry = {
|
||||
type: 'switch',
|
||||
object_id: endpoint ? `switch_${endpoint}` : 'switch',
|
||||
mockProperties: [property],
|
||||
discovery_payload: {
|
||||
payload_off: state.value_off,
|
||||
payload_on: state.value_on,
|
||||
@@ -181,6 +185,7 @@ export default class HomeAssistant extends Extension {
|
||||
const discoveryEntry: DiscoveryEntry = {
|
||||
type: 'climate',
|
||||
object_id: endpoint ? `climate_${endpoint}` : 'climate',
|
||||
mockProperties: [],
|
||||
discovery_payload: {
|
||||
// Static
|
||||
state_topic: false,
|
||||
@@ -268,6 +273,7 @@ export default class HomeAssistant extends Extension {
|
||||
const discoveryEntry: DiscoveryEntry = {
|
||||
type: 'lock',
|
||||
object_id: 'lock',
|
||||
mockProperties: [state.property],
|
||||
discovery_payload: {
|
||||
command_topic: true,
|
||||
value_template: `{{ value_json.${state.property} }}`,
|
||||
@@ -304,6 +310,7 @@ export default class HomeAssistant extends Extension {
|
||||
|
||||
const discoveryEntry: DiscoveryEntry = {
|
||||
type: 'cover',
|
||||
mockProperties: [],
|
||||
object_id: endpoint ? `cover_${endpoint}` : 'cover',
|
||||
discovery_payload: {},
|
||||
};
|
||||
@@ -347,6 +354,7 @@ export default class HomeAssistant extends Extension {
|
||||
const discoveryEntry: DiscoveryEntry = {
|
||||
type: 'fan',
|
||||
object_id: 'fan',
|
||||
mockProperties: ['fan_state'],
|
||||
discovery_payload: {
|
||||
state_topic: true,
|
||||
state_value_template: '{{ value_json.fan_state }}',
|
||||
@@ -427,6 +435,7 @@ export default class HomeAssistant extends Extension {
|
||||
if (firstExpose.access & ACCESS_SET) {
|
||||
const discoveryEntry: DiscoveryEntry = {
|
||||
type: 'switch',
|
||||
mockProperties: [firstExpose.property],
|
||||
object_id: endpoint ?
|
||||
`switch_${firstExpose.name}_${endpoint}` :
|
||||
`switch_${firstExpose.name}`,
|
||||
@@ -447,6 +456,7 @@ export default class HomeAssistant extends Extension {
|
||||
const discoveryEntry = {
|
||||
type: 'binary_sensor',
|
||||
object_id: endpoint ? `${firstExpose.name}_${endpoint}` : `${firstExpose.name}`,
|
||||
mockProperties: [firstExpose.property],
|
||||
discovery_payload: {
|
||||
value_template: `{{ value_json.${firstExpose.property} }}`,
|
||||
payload_on: firstExpose.value_on,
|
||||
@@ -506,6 +516,7 @@ export default class HomeAssistant extends Extension {
|
||||
const discoveryEntry = {
|
||||
type: 'sensor',
|
||||
object_id: endpoint ? `${firstExpose.name}_${endpoint}` : `${firstExpose.name}`,
|
||||
mockProperties: [firstExpose.property],
|
||||
discovery_payload: {
|
||||
value_template: `{{ value_json.${firstExpose.property} }}`,
|
||||
enabled_by_default: !allowsSet,
|
||||
@@ -524,6 +535,7 @@ export default class HomeAssistant extends Extension {
|
||||
const discoveryEntry = {
|
||||
type: 'number',
|
||||
object_id: endpoint ? `${firstExpose.name}_${endpoint}` : `${firstExpose.name}`,
|
||||
mockProperties: [firstExpose.property],
|
||||
discovery_payload: {
|
||||
value_template: `{{ value_json.${firstExpose.property} }}`,
|
||||
command_topic: true,
|
||||
@@ -561,6 +573,7 @@ export default class HomeAssistant extends Extension {
|
||||
discoveryEntries.push({
|
||||
type: 'sensor',
|
||||
object_id: firstExpose.property,
|
||||
mockProperties: [firstExpose.property],
|
||||
discovery_payload: {
|
||||
value_template: `{{ value_json.${firstExpose.property} }}`,
|
||||
enabled_by_default: !(firstExpose.access & ACCESS_SET),
|
||||
@@ -577,6 +590,7 @@ export default class HomeAssistant extends Extension {
|
||||
discoveryEntries.push({
|
||||
type: 'select',
|
||||
object_id: firstExpose.property,
|
||||
mockProperties: [firstExpose.property],
|
||||
discovery_payload: {
|
||||
value_template: `{{ value_json.${firstExpose.property} }}`,
|
||||
state_topic: true,
|
||||
@@ -598,6 +612,7 @@ export default class HomeAssistant extends Extension {
|
||||
const discoveryEntry = {
|
||||
type: 'sensor',
|
||||
object_id: firstExpose.property,
|
||||
mockProperties: [firstExpose.property],
|
||||
discovery_payload: {
|
||||
value_template: `{{ value_json.${firstExpose.property} }}`,
|
||||
...lookup[firstExpose.name],
|
||||
@@ -629,6 +644,7 @@ export default class HomeAssistant extends Extension {
|
||||
// deprecated
|
||||
this.mapping[def.model].push({
|
||||
type: 'sensor',
|
||||
mockProperties: ['brightness'],
|
||||
object_id: 'brightness',
|
||||
discovery_payload: {
|
||||
unit_of_measurement: 'brightness',
|
||||
@@ -653,7 +669,7 @@ export default class HomeAssistant extends Extension {
|
||||
|
||||
@bind onDeviceRemoved(data: eventdata.DeviceRemoved): void {
|
||||
logger.debug(`Clearing Home Assistant discovery topic for '${data.name}'`);
|
||||
this.discovered[data.ieeeAddr]?.forEach((topic) => {
|
||||
this.discovered[data.ieeeAddr]?.topics.forEach((topic) => {
|
||||
this.mqtt.publish(topic, null, {retain: true, qos: 0}, this.discoveryTopic, false, false);
|
||||
});
|
||||
|
||||
@@ -781,6 +797,7 @@ export default class HomeAssistant extends Extension {
|
||||
configs.push({
|
||||
type: 'sensor',
|
||||
object_id: 'last_seen',
|
||||
mockProperties: ['last_seen'],
|
||||
discovery_payload: {
|
||||
icon: 'mdi:clock',
|
||||
value_template: '{{ value_json.last_seen }}',
|
||||
@@ -790,9 +807,10 @@ export default class HomeAssistant extends Extension {
|
||||
}
|
||||
|
||||
if (isDevice && entity.definition.hasOwnProperty('ota')) {
|
||||
const updateStateSensor = {
|
||||
const updateStateSensor: DiscoveryEntry = {
|
||||
type: 'sensor',
|
||||
object_id: 'update_state',
|
||||
mockProperties: [],
|
||||
discovery_payload: {
|
||||
icon: 'mdi:update',
|
||||
value_template: `{{ value_json['update']['state'] }}`,
|
||||
@@ -805,6 +823,7 @@ export default class HomeAssistant extends Extension {
|
||||
const updateAvailableSensor = {
|
||||
type: 'binary_sensor',
|
||||
object_id: 'update_available',
|
||||
mockProperties: ['update_available'],
|
||||
discovery_payload: {
|
||||
payload_on: true,
|
||||
payload_off: false,
|
||||
@@ -842,9 +861,13 @@ export default class HomeAssistant extends Extension {
|
||||
return configs;
|
||||
}
|
||||
|
||||
private getDiscoverKey(entity: Device | Group): string | number {
|
||||
return entity.isDevice() ? entity.ieeeAddr : entity.ID;
|
||||
}
|
||||
|
||||
private discover(entity: Device | Group, force=false): void {
|
||||
// Check if already discoverd and check if there are configs.
|
||||
const discoverKey = entity.isDevice() ? entity.ieeeAddr : entity.ID;
|
||||
const discoverKey = this.getDiscoverKey(entity);
|
||||
const discover = force || !this.discovered[discoverKey];
|
||||
|
||||
if (entity.isGroup()) {
|
||||
@@ -855,7 +878,7 @@ export default class HomeAssistant extends Extension {
|
||||
return;
|
||||
}
|
||||
|
||||
this.discovered[discoverKey] = [];
|
||||
this.discovered[discoverKey] = {topics: new Set(), mockProperties: new Set()};
|
||||
this.getConfigs(entity).forEach((config) => {
|
||||
const payload = {...config.discovery_payload};
|
||||
let stateTopic = `${settings.get().mqtt.base_topic}/${entity.name}`;
|
||||
@@ -1044,7 +1067,8 @@ export default class HomeAssistant extends Extension {
|
||||
|
||||
const topic = this.getDiscoveryTopic(config, entity);
|
||||
this.mqtt.publish(topic, stringify(payload), {retain: true, qos: 0}, this.discoveryTopic, false, false);
|
||||
this.discovered[discoverKey].push(topic);
|
||||
this.discovered[discoverKey].topics.add(topic);
|
||||
config.mockProperties?.forEach((property) => this.discovered[discoverKey].mockProperties.add(property));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1139,26 +1163,12 @@ export default class HomeAssistant extends Extension {
|
||||
}
|
||||
|
||||
override adjustMessageBeforePublish(entity: Device | Group, message: KeyValue): void {
|
||||
// Set missing values of state to 'null': https://github.com/Koenkk/zigbee2mqtt/issues/6987
|
||||
if (!entity.isDevice() || !entity.definition) return null;
|
||||
|
||||
const add = (expose: zhc.DefinitionExpose | zhc.DefinitionExposeFeature): void => {
|
||||
if (!message.hasOwnProperty(expose.property) && expose.access & ACCESS_STATE) {
|
||||
message[expose.property] = null;
|
||||
const discoverKey = this.getDiscoverKey(entity);
|
||||
this.discovered[discoverKey]?.mockProperties?.forEach((property) => {
|
||||
if (!message.hasOwnProperty(property)) {
|
||||
message[property] = null;
|
||||
}
|
||||
};
|
||||
|
||||
for (const expose of entity.definition.exposes) {
|
||||
if (expose.hasOwnProperty('features')) {
|
||||
for (const feature of expose.features) {
|
||||
if (feature.name === 'state') {
|
||||
add(feature);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
add(expose);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Copy hue -> h, saturation -> s to make homeassitant happy
|
||||
if (message.hasOwnProperty('color')) {
|
||||
@@ -1200,6 +1210,7 @@ export default class HomeAssistant extends Extension {
|
||||
const config: DiscoveryEntry = {
|
||||
type: 'device_automation',
|
||||
object_id: `${key}_${value}`,
|
||||
mockProperties: [],
|
||||
discovery_payload: {
|
||||
automation_type: 'trigger',
|
||||
type: key,
|
||||
|
||||
@@ -140,7 +140,7 @@ describe('Frontend', () => {
|
||||
expect(MQTT.publish).toHaveBeenCalledTimes(1);
|
||||
expect(MQTT.publish).toHaveBeenCalledWith(
|
||||
'zigbee2mqtt/bulb_color',
|
||||
stringify({state: 'ON', linkquality: null}),
|
||||
stringify({state: 'ON', linkquality: null, update_available: null}),
|
||||
{ retain: false, qos: 0 },
|
||||
expect.any(Function)
|
||||
);
|
||||
@@ -151,7 +151,7 @@ describe('Frontend', () => {
|
||||
|
||||
// Received message on socket
|
||||
expect(mockWSClient.implementation.send).toHaveBeenCalledTimes(1);
|
||||
expect(mockWSClient.implementation.send).toHaveBeenCalledWith(stringify({topic: 'bulb_color', payload: {state: 'ON', linkquality: null}}));
|
||||
expect(mockWSClient.implementation.send).toHaveBeenCalledWith(stringify({topic: 'bulb_color', payload: {state: 'ON', linkquality: null, update_available: null}}));
|
||||
|
||||
// Shouldnt set when not ready
|
||||
mockWSClient.implementation.send.mockClear();
|
||||
|
||||
+10
-10
@@ -825,7 +825,7 @@ describe('HomeAssistant extension', () => {
|
||||
expect(MQTT.publish).toHaveBeenCalledTimes(1);
|
||||
expect(MQTT.publish).toHaveBeenCalledWith(
|
||||
'zigbee2mqtt/bulb_color',
|
||||
stringify({"color":{"hue": 0, "saturation": 100, "h": 0, "s": 100}, "color_mode": "hs", "linkquality": null, "state": null}),
|
||||
stringify({"color":{"hue": 0, "saturation": 100, "h": 0, "s": 100}, "color_mode": "hs", "linkquality": null, "state": null, "update_available": null}),
|
||||
{ retain: false, qos: 0 },
|
||||
expect.any(Function),
|
||||
);
|
||||
@@ -841,7 +841,7 @@ describe('HomeAssistant extension', () => {
|
||||
expect(MQTT.publish).toHaveBeenCalledTimes(1);
|
||||
expect(MQTT.publish).toHaveBeenCalledWith(
|
||||
'zigbee2mqtt/bulb_color',
|
||||
stringify({"color": {"x": 0.4576,"y": 0.41}, "color_mode": "xy", "linkquality": null,"state": null}),
|
||||
stringify({"color": {"x": 0.4576,"y": 0.41}, "color_mode": "xy", "linkquality": null,"state": null, "update_available": null}),
|
||||
{ retain: false, qos: 0 },
|
||||
expect.any(Function),
|
||||
);
|
||||
@@ -857,7 +857,7 @@ describe('HomeAssistant extension', () => {
|
||||
expect(MQTT.publish).toHaveBeenCalledTimes(1);
|
||||
expect(MQTT.publish).toHaveBeenCalledWith(
|
||||
'zigbee2mqtt/bulb_color',
|
||||
stringify({"linkquality": null,"state": "ON"}),
|
||||
stringify({"linkquality": null,"state": "ON", "update_available": null}),
|
||||
{ retain: false, qos: 0 },
|
||||
expect.any(Function),
|
||||
);
|
||||
@@ -932,13 +932,13 @@ describe('HomeAssistant extension', () => {
|
||||
await flushPromises();
|
||||
expect(MQTT.publish).toHaveBeenCalledWith(
|
||||
'zigbee2mqtt/bulb',
|
||||
stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99,"power_on_behavior":null}),
|
||||
stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99,"power_on_behavior":null, "update_available": null}),
|
||||
{ retain: true, qos: 0 },
|
||||
expect.any(Function)
|
||||
);
|
||||
expect(MQTT.publish).toHaveBeenCalledWith(
|
||||
'zigbee2mqtt/remote',
|
||||
stringify({"action":null,"battery":null,"brightness":255,"linkquality":null}),
|
||||
stringify({"action":null,"battery":null,"brightness":255,"linkquality":null, "update_available": null}),
|
||||
{ retain: true, qos: 0 },
|
||||
expect.any(Function)
|
||||
);
|
||||
@@ -956,13 +956,13 @@ describe('HomeAssistant extension', () => {
|
||||
await flushPromises();
|
||||
expect(MQTT.publish).toHaveBeenCalledWith(
|
||||
'zigbee2mqtt/bulb',
|
||||
stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99,"power_on_behavior":null}),
|
||||
stringify({"state":"ON","brightness":50,"color_temp":370,"linkquality":99,"power_on_behavior":null, "update_available": null}),
|
||||
{ retain: true, qos: 0 },
|
||||
expect.any(Function)
|
||||
);
|
||||
expect(MQTT.publish).toHaveBeenCalledWith(
|
||||
'zigbee2mqtt/remote',
|
||||
stringify({"action":null,"battery":null,"brightness":255,"linkquality":null}),
|
||||
stringify({"action":null,"battery":null,"brightness":255,"linkquality":null, "update_available": null}),
|
||||
{ retain: true, qos: 0 },
|
||||
expect.any(Function)
|
||||
);
|
||||
@@ -1290,7 +1290,7 @@ describe('HomeAssistant extension', () => {
|
||||
|
||||
expect(MQTT.publish).toHaveBeenCalledWith(
|
||||
'zigbee2mqtt/button',
|
||||
stringify({action: "", battery: null, linkquality: null, voltage: null}),
|
||||
stringify({action: "", battery: null, linkquality: null, voltage: null, click: null}),
|
||||
{ retain: false, qos: 0 },
|
||||
expect.any(Function),
|
||||
);
|
||||
@@ -1477,10 +1477,10 @@ describe('HomeAssistant extension', () => {
|
||||
await flushPromises();
|
||||
expect(MQTT.publish).toHaveBeenCalledTimes(4);
|
||||
expect(MQTT.publish.mock.calls[0][0]).toStrictEqual('zigbee2mqtt/button');
|
||||
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({action: 'single', battery: null, linkquality: null, voltage: null});
|
||||
expect(JSON.parse(MQTT.publish.mock.calls[0][1])).toStrictEqual({action: 'single', click: null, battery: null, linkquality: null, voltage: null});
|
||||
expect(MQTT.publish.mock.calls[0][2]).toStrictEqual({"qos": 0, "retain": false});
|
||||
expect(MQTT.publish.mock.calls[1][0]).toStrictEqual('zigbee2mqtt/button');
|
||||
expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({action: '', battery: null, linkquality: null, voltage: null});
|
||||
expect(JSON.parse(MQTT.publish.mock.calls[1][1])).toStrictEqual({action: '', click: null, battery: null, linkquality: null, voltage: null});
|
||||
expect(MQTT.publish.mock.calls[1][2]).toStrictEqual({"qos": 0, "retain": false});
|
||||
expect(MQTT.publish.mock.calls[2][0]).toStrictEqual('homeassistant/device_automation/0x0017880104e45520/action_single/config');
|
||||
expect(MQTT.publish.mock.calls[3][0]).toStrictEqual('zigbee2mqtt/button/action');
|
||||
|
||||
Reference in New Issue
Block a user