diff --git a/lib/extension/homeassistant.js b/lib/extension/homeassistant.js index aeb297b1..73429ec8 100644 --- a/lib/extension/homeassistant.js +++ b/lib/extension/homeassistant.js @@ -16,7 +16,8 @@ const sensorClick = { }, }; -const ACCESS_STATE = 1; +const ACCESS_STATE = 0b001; +const ACCESS_SET = 0b010; const defaultStatusTopic = 'homeassistant/status'; /** @@ -343,16 +344,40 @@ class HomeAssistant extends Extension { presence: {device_class: 'presence'}, }; - discoveryEntry = { - type: 'binary_sensor', - object_id: expose.endpoint ? `${expose.name}_${expose.endpoint}` : `${expose.name}`, - discovery_payload: { - value_template: `{{ value_json.${expose.property} }}`, - payload_on: expose.value_on, - payload_off: expose.value_off, - ...(lookup[expose.name] || {}), - }, - }; + /** + * If Z2M binary attribute has SET access then expose it as `switch` in HA + * There is also a check on the values for typeof boolean to prevent invalid values and commands + * silently failing - commands work fine but some devices won't reject unexpected values. + * https://github.com/Koenkk/zigbee2mqtt/issues/7740 + */ + if (expose.access & ACCESS_SET) { + discoveryEntry = { + type: 'switch', + object_id: expose.endpoint ? + `switch_${expose.name}_${expose.endpoint}` : + `switch_${expose.name}`, + discovery_payload: { + value_template: `{{ value_json.${expose.property} }}`, + payload_on: typeof expose.value_on == 'boolean' ? 'true' : expose.value_on, + payload_off: typeof expose.value_off == 'boolean' ? 'false' : expose.value_off, + command_topic: true, + command_topic_prefix: expose.endpoint ? expose.endpoint : undefined, + command_topic_postfix: expose.property, + ...(lookup[expose.name] || {}), + }, + }; + } else { + discoveryEntry = { + type: 'binary_sensor', + object_id: expose.endpoint ? `${expose.name}_${expose.endpoint}` : `${expose.name}`, + discovery_payload: { + value_template: `{{ value_json.${expose.property} }}`, + payload_on: expose.value_on, + payload_off: expose.value_off, + ...(lookup[expose.name] || {}), + }, + }; + } } else if (expose.type === 'numeric') { const lookup = { battery: {device_class: 'battery', state_class: 'measurement'},