mirror of
https://github.com/Koenkk/zigbee2mqtt.git
synced 2026-08-28 05:24:29 +00:00
Discover enum exposes as select entities in Home Assistant (#7980)
* Read/write exposed enums are now discovered as MQTT Select entities in home assistant 2021.7. * Read/Write enums are now exposed as a Select in addition to the existing sensor, for compatibility. Also, the sensor will be disabled by default for new entities, shouldn't effect existing installations. * Add support for returning multiple config from a single call to exposeToConfig. * Update homeassistant.js Co-authored-by: Koen Kanters <koenkanters94@gmail.com>
This commit is contained in:
co-authored by
Koen Kanters
parent
60a1cf3727
commit
9edbe5a2a8
@@ -72,7 +72,7 @@ class HomeAssistant extends Extension {
|
||||
assert(entityType === 'device' || groupSupportedTypes.includes(firstExpose.type),
|
||||
`Unsupported expose type ${firstExpose.type} for group`);
|
||||
|
||||
let discoveryEntry = null;
|
||||
const discoveryEntries = [];
|
||||
const endpoint = entityType === 'device' ? exposes[0].endpoint : undefined;
|
||||
const getProperty = (feature) => entityType === 'group' ?
|
||||
featurePropertyWithoutEndpoint(feature) : feature.property;
|
||||
@@ -84,7 +84,7 @@ class HomeAssistant extends Extension {
|
||||
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'));
|
||||
|
||||
discoveryEntry = {
|
||||
const discoveryEntry = {
|
||||
type: 'light',
|
||||
object_id: endpoint ? `light_${endpoint}` : 'light',
|
||||
discovery_payload: {
|
||||
@@ -119,10 +119,12 @@ class HomeAssistant extends Extension {
|
||||
discoveryEntry.discovery_payload.effect = true;
|
||||
discoveryEntry.discovery_payload.effect_list = effect.values;
|
||||
}
|
||||
|
||||
discoveryEntries.push(discoveryEntry);
|
||||
} else if (firstExpose.type === 'switch') {
|
||||
const state = firstExpose.features.find((f) => f.name === 'state');
|
||||
const property = getProperty(state);
|
||||
discoveryEntry = {
|
||||
const discoveryEntry = {
|
||||
type: 'switch',
|
||||
object_id: endpoint ? `switch_${endpoint}` : 'switch',
|
||||
discovery_payload: {
|
||||
@@ -145,6 +147,8 @@ class HomeAssistant extends Extension {
|
||||
discoveryEntry.discovery_payload.icon = 'mdi:window-open-variant';
|
||||
}
|
||||
}
|
||||
|
||||
discoveryEntries.push(discoveryEntry);
|
||||
} else if (firstExpose.type === 'climate') {
|
||||
const setpointProperties = ['occupied_heating_setpoint', 'current_heating_setpoint'];
|
||||
const setpoint = firstExpose.features.find((f) => setpointProperties.includes(f.name));
|
||||
@@ -152,7 +156,7 @@ class HomeAssistant extends Extension {
|
||||
const temperature = firstExpose.features.find((f) => f.name === 'local_temperature');
|
||||
assert(temperature, 'No temperature found');
|
||||
|
||||
discoveryEntry = {
|
||||
const discoveryEntry = {
|
||||
type: 'climate',
|
||||
object_id: endpoint ? `climate_${endpoint}` : 'climate',
|
||||
discovery_payload: {
|
||||
@@ -237,11 +241,13 @@ class HomeAssistant extends Extension {
|
||||
if (firstExpose.endpoint) {
|
||||
discoveryEntry.discovery_payload.state_topic_postfix = firstExpose.endpoint;
|
||||
}
|
||||
|
||||
discoveryEntries.push(discoveryEntry);
|
||||
} else if (firstExpose.type === 'lock') {
|
||||
assert(!endpoint, `Endpoint not supported for lock type`);
|
||||
const state = firstExpose.features.find((f) => f.name === 'state');
|
||||
assert(state, 'No state found');
|
||||
discoveryEntry = {
|
||||
const discoveryEntry = {
|
||||
type: 'lock',
|
||||
object_id: 'lock',
|
||||
discovery_payload: {
|
||||
@@ -272,12 +278,14 @@ class HomeAssistant extends Extension {
|
||||
if (state.property !== 'state') {
|
||||
discoveryEntry.discovery_payload.command_topic_postfix = state.property;
|
||||
}
|
||||
|
||||
discoveryEntries.push(discoveryEntry);
|
||||
} else if (firstExpose.type === 'cover') {
|
||||
assert(!endpoint, `Endpoint not supported for cover type`);
|
||||
const hasPosition = exposes.find((expose) => expose.features.find((e) => e.name === 'position'));
|
||||
const hasTilt = exposes.find((expose) => expose.features.find((e) => e.name === 'tilt'));
|
||||
|
||||
discoveryEntry = {
|
||||
const discoveryEntry = {
|
||||
type: 'cover',
|
||||
object_id: 'cover',
|
||||
discovery_payload: {
|
||||
@@ -306,9 +314,11 @@ class HomeAssistant extends Extension {
|
||||
tilt_status_template: '{{ value_json.tilt }}',
|
||||
};
|
||||
}
|
||||
|
||||
discoveryEntries.push(discoveryEntry);
|
||||
} else if (firstExpose.type === 'fan') {
|
||||
assert(!endpoint, `Endpoint not supported for fan type`);
|
||||
discoveryEntry = {
|
||||
const discoveryEntry = {
|
||||
type: 'fan',
|
||||
object_id: 'fan',
|
||||
discovery_payload: {
|
||||
@@ -367,6 +377,8 @@ class HomeAssistant extends Extension {
|
||||
` else 'None' | default('None') }}`;
|
||||
discoveryEntry.discovery_payload.preset_modes = presets;
|
||||
}
|
||||
|
||||
discoveryEntries.push(discoveryEntry);
|
||||
} else if (firstExpose.type === 'binary') {
|
||||
const lookup = {
|
||||
occupancy: {device_class: 'motion'},
|
||||
@@ -388,7 +400,7 @@ class HomeAssistant extends Extension {
|
||||
* Dont expose boolean values for now: https://github.com/Koenkk/zigbee2mqtt/issues/7797
|
||||
*/
|
||||
if (firstExpose.access & ACCESS_SET) {
|
||||
discoveryEntry = {
|
||||
const discoveryEntry = {
|
||||
type: 'switch',
|
||||
object_id: endpoint ?
|
||||
`switch_${firstExpose.name}_${endpoint}` :
|
||||
@@ -405,8 +417,9 @@ class HomeAssistant extends Extension {
|
||||
...(lookup[firstExpose.name] || {}),
|
||||
},
|
||||
};
|
||||
discoveryEntries.push(discoveryEntry);
|
||||
} else {
|
||||
discoveryEntry = {
|
||||
const discoveryEntry = {
|
||||
type: 'binary_sensor',
|
||||
object_id: endpoint ? `${firstExpose.name}_${endpoint}` : `${firstExpose.name}`,
|
||||
discovery_payload: {
|
||||
@@ -416,6 +429,7 @@ class HomeAssistant extends Extension {
|
||||
...(lookup[firstExpose.name] || {}),
|
||||
},
|
||||
};
|
||||
discoveryEntries.push(discoveryEntry);
|
||||
}
|
||||
} else if (firstExpose.type === 'numeric') {
|
||||
const lookup = {
|
||||
@@ -459,7 +473,7 @@ class HomeAssistant extends Extension {
|
||||
z_axis: {icon: 'mdi:axis-z-arrow'},
|
||||
};
|
||||
|
||||
discoveryEntry = {
|
||||
const discoveryEntry = {
|
||||
type: 'sensor',
|
||||
object_id: endpoint ? `${firstExpose.name}_${endpoint}` : `${firstExpose.name}`,
|
||||
discovery_payload: {
|
||||
@@ -468,13 +482,50 @@ class HomeAssistant extends Extension {
|
||||
...lookup[firstExpose.name],
|
||||
},
|
||||
};
|
||||
} else if (firstExpose.type === 'enum' || firstExpose.type === 'text' || firstExpose.type === 'composite') {
|
||||
discoveryEntries.push(discoveryEntry);
|
||||
} else if (firstExpose.type === 'enum') {
|
||||
const lookup = {
|
||||
action: {icon: 'mdi:gesture-double-tap'},
|
||||
};
|
||||
|
||||
if (firstExpose.access & ACCESS_STATE) {
|
||||
discoveryEntries.push({
|
||||
type: 'sensor',
|
||||
object_id: firstExpose.property,
|
||||
discovery_payload: {
|
||||
value_template: `{{ value_json.${firstExpose.property} }}`,
|
||||
...lookup[firstExpose.name],
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* If enum attribute has SET access then expose as SELECT entity too.
|
||||
*/
|
||||
if ((firstExpose.access & ACCESS_SET)) {
|
||||
// Make the sensor disabled by default for new entities.
|
||||
discoveryEntries[discoveryEntries.length - 1].discovery_payload.enabled_by_default = false;
|
||||
discoveryEntries.push({
|
||||
type: 'select',
|
||||
object_id: firstExpose.property,
|
||||
discovery_payload: {
|
||||
value_template: `{{ value_json.${firstExpose.property} }}`,
|
||||
state_topic: true,
|
||||
command_topic_prefix: endpoint,
|
||||
command_topic: true,
|
||||
command_topic_postfix: firstExpose.property,
|
||||
options: firstExpose.values,
|
||||
...lookup[firstExpose.name],
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (firstExpose.type === 'text' || firstExpose.type === 'composite') {
|
||||
if (firstExpose.access & ACCESS_STATE) {
|
||||
const lookup = {
|
||||
action: {icon: 'mdi:gesture-double-tap'},
|
||||
};
|
||||
|
||||
discoveryEntry = {
|
||||
const discoveryEntry = {
|
||||
type: 'sensor',
|
||||
object_id: firstExpose.property,
|
||||
discovery_payload: {
|
||||
@@ -482,12 +533,13 @@ class HomeAssistant extends Extension {
|
||||
...lookup[firstExpose.name],
|
||||
},
|
||||
};
|
||||
discoveryEntries.push(discoveryEntry);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Unsupported exposes type: '${firstExpose.type}'`);
|
||||
}
|
||||
|
||||
return discoveryEntry;
|
||||
return discoveryEntries;
|
||||
}
|
||||
|
||||
populateMapping() {
|
||||
@@ -517,10 +569,7 @@ class HomeAssistant extends Extension {
|
||||
}
|
||||
|
||||
for (const expose of def.exposes) {
|
||||
const discoveryEntry = this.exposeToConfig([expose], 'device', def);
|
||||
if (discoveryEntry) {
|
||||
this.mapping[def.model].push(discoveryEntry);
|
||||
}
|
||||
this.mapping[def.model].push(...this.exposeToConfig([expose], 'device', def));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -666,7 +715,8 @@ class HomeAssistant extends Extension {
|
||||
}
|
||||
});
|
||||
|
||||
configs = Object.values(exposesByType).map((exposes) => this.exposeToConfig(exposes, 'group'));
|
||||
configs = [].concat(...Object.values(exposesByType)
|
||||
.map((exposes) => this.exposeToConfig(exposes, 'group')));
|
||||
}
|
||||
|
||||
if (isDevice && resolvedEntity.definition.hasOwnProperty('ota')) {
|
||||
|
||||
Reference in New Issue
Block a user