diff --git a/lib/extension/homeassistant.ts b/lib/extension/homeassistant.ts index 4c498bef..af247ba4 100644 --- a/lib/extension/homeassistant.ts +++ b/lib/extension/homeassistant.ts @@ -51,6 +51,10 @@ const COVER_STOPPED_LOOKUP: ReadonlyArray = ["stopped", "stop", "pause", const CONFIG_SWITCH_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = { auto_lock: {entity_category: "config", icon: "mdi:lock"}, away_mode: {entity_category: "config", icon: "mdi:home-export-outline"}, + comfort_smiley: {entity_category: "config", icon: "mdi:emoticon-happy-outline"}, + enable_display: {entity_category: "config", icon: "mdi:monitor"}, + indicator: {entity_category: "config", icon: "mdi:led-on"}, + tilt_mode: {entity_category: "config", icon: "mdi:angle-acute"}, valve_detection: {entity_category: "config", icon: "mdi:pipe-valve"}, window_detection: {entity_category: "config", icon: "mdi:window-open-variant"}, } as const; @@ -67,6 +71,8 @@ const BINARY_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = { battery_low: {entity_category: "diagnostic", device_class: "battery"}, button_lock: {entity_category: "config", icon: "mdi:lock"}, calibration: {entity_category: "config", icon: "mdi:progress-wrench"}, + calibration_left: {entity_category: "config", icon: "mdi:progress-wrench"}, + calibration_right: {entity_category: "config", icon: "mdi:progress-wrench"}, capabilities_configurable_curve: {entity_category: "diagnostic", icon: "mdi:tune"}, capabilities_forward_phase_control: {entity_category: "diagnostic", icon: "mdi:tune"}, capabilities_overload_detection: {entity_category: "diagnostic", icon: "mdi:tune"}, @@ -82,15 +88,19 @@ const BINARY_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = { frost_protection: {entity_category: "config", icon: "mdi:snowflake-thermometer"}, heating_stop: {entity_category: "config", icon: "mdi:radiator-off"}, eco_mode: {entity_category: "config", icon: "mdi:leaf"}, + enable_display: {entity_category: "config", icon: "mdi:monitor"}, expose_pin: {entity_category: "config", icon: "mdi:pin"}, flip_indicator_light: {entity_category: "config", icon: "mdi:arrow-left-right"}, gas: {device_class: "gas"}, + indicator: {entity_category: "config", icon: "mdi:led-on"}, indicator_mode: {entity_category: "config", icon: "mdi:led-on"}, invert_cover: {entity_category: "config", icon: "mdi:arrow-left-right"}, led_disabled_night: {entity_category: "config", icon: "mdi:led-off"}, led_indication: {entity_category: "config", icon: "mdi:led-on"}, led_enable: {entity_category: "config", icon: "mdi:led-on"}, motor_reversal: {entity_category: "config", icon: "mdi:arrow-left-right"}, + motor_reversal_left: {entity_category: "config", icon: "mdi:arrow-left-right"}, + motor_reversal_right: {entity_category: "config", icon: "mdi:arrow-left-right"}, moving: {device_class: "moving"}, no_position_support: {entity_category: "config", icon: "mdi:minus-circle-outline"}, noise_detected: {device_class: "sound"}, @@ -111,6 +121,7 @@ const BINARY_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = { temperature_scale: {entity_category: "config", icon: "mdi:temperature-celsius"}, test: {entity_category: "diagnostic", icon: "mdi:test-tube"}, th_heater: {icon: "mdi:heat-wave"}, + tilt_mode: {entity_category: "config", icon: "mdi:angle-acute"}, trigger_indicator: {icon: "mdi:led-on"}, valve_alarm: {device_class: "problem"}, valve_detection: {entity_category: "config", icon: "mdi:pipe-valve"}, @@ -147,8 +158,14 @@ const NUMERIC_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = { boost_time: {entity_category: "config", icon: "mdi:timer"}, calibration: {entity_category: "config", icon: "mdi:wrench-clock"}, calibration_time: {entity_category: "config", icon: "mdi:wrench-clock"}, + calibration_time_left: {entity_category: "config", icon: "mdi:wrench-clock"}, + calibration_time_right: {entity_category: "config", icon: "mdi:wrench-clock"}, co2: {device_class: "carbon_dioxide", state_class: "measurement"}, + comfort_humidity_max: {device_class: "humidity", entity_category: "config", icon: "mdi:water-percent"}, + comfort_humidity_min: {device_class: "humidity", entity_category: "config", icon: "mdi:water-percent"}, comfort_temperature: {entity_category: "config", icon: "mdi:thermometer"}, + comfort_temperature_max: {device_class: "temperature", entity_category: "config", icon: "mdi:thermometer-high"}, + comfort_temperature_min: {device_class: "temperature", entity_category: "config", icon: "mdi:thermometer-low"}, cpu_temperature: { device_class: "temperature", entity_category: "diagnostic", @@ -159,6 +176,7 @@ const NUMERIC_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = { current_phase_b: {device_class: "current", state_class: "measurement"}, current_phase_c: {device_class: "current", state_class: "measurement"}, deadzone_temperature: {entity_category: "config", icon: "mdi:thermometer"}, + detection_delay: {entity_category: "config", icon: "mdi:timer"}, detection_interval: {icon: "mdi:timer"}, device_temperature: { device_class: "temperature", @@ -174,6 +192,7 @@ const NUMERIC_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = { external_temperature_input: {device_class: "temperature", icon: "mdi:thermometer"}, external_temperature: {device_class: "temperature", icon: "mdi:thermometer", state_class: "measurement"}, external_humidity: {device_class: "humidity", icon: "mdi:water-percent", state_class: "measurement"}, + fading_time: {entity_category: "config", icon: "mdi:timer"}, formaldehyd: {state_class: "measurement"}, flow: {device_class: "volume_flow_rate", state_class: "measurement"}, gas: {device_class: "gas", state_class: "total_increasing", icon: "mdi:meter-gas"}, @@ -189,6 +208,7 @@ const NUMERIC_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = { illuminance_calibration: {entity_category: "config", icon: "mdi:wrench-clock"}, illuminance: {device_class: "illuminance", state_class: "measurement"}, illuminance_raw: {state_class: "measurement"}, + interval_time: {entity_category: "config", icon: "mdi:timer"}, internalTemperature: { device_class: "temperature", entity_category: "diagnostic", @@ -202,13 +222,20 @@ const NUMERIC_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = { }, load_estimate: {state_class: "measurement"}, local_temperature: {device_class: "temperature", state_class: "measurement"}, + large_motion_detection_distance: {entity_category: "config", icon: "mdi:signal-distance-variant"}, + large_motion_detection_sensitivity: {entity_category: "config", icon: "mdi:motion-sensor"}, max_range: {entity_category: "config", icon: "mdi:signal-distance-variant"}, max_temperature: {entity_category: "config", icon: "mdi:thermometer-high"}, max_temperature_limit: {entity_category: "config", icon: "mdi:thermometer-high"}, + maximum_range: {entity_category: "config", icon: "mdi:signal-distance-variant"}, + measurement_interval: {entity_category: "config", icon: "mdi:clock-out"}, min_temperature_limit: {entity_category: "config", icon: "mdi:thermometer-low"}, min_temperature: {entity_category: "config", icon: "mdi:thermometer-low"}, + minimum_range: {entity_category: "config", icon: "mdi:signal-distance-variant"}, minimum_on_level: {entity_category: "config"}, measurement_poll_interval: {entity_category: "config", icon: "mdi:clock-out"}, + medium_motion_detection_distance: {entity_category: "config", icon: "mdi:signal-distance-variant"}, + medium_motion_detection_sensitivity: {entity_category: "config", icon: "mdi:motion-sensor"}, motion_sensitivity: {entity_category: "config", icon: "mdi:motion-sensor"}, noise: {device_class: "sound_pressure", state_class: "measurement"}, noise_detect_level: {icon: "mdi:volume-equal"}, @@ -244,14 +271,21 @@ const NUMERIC_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = { icon: "mdi:brightness-5", }, smoke_density: {icon: "mdi:google-circles-communities", state_class: "measurement"}, + sensitivity: {entity_category: "config", icon: "mdi:tune"}, + small_detection_distance: {entity_category: "config", icon: "mdi:signal-distance-variant"}, + small_detection_sensitivity: {entity_category: "config", icon: "mdi:motion-sensor"}, + soil_calibration: {entity_category: "config", icon: "mdi:wrench-clock"}, soil_fertility: {device_class: "conductivity", state_class: "measurement"}, soil_moisture: {device_class: "moisture", state_class: "measurement"}, + soil_sampling: {entity_category: "config", icon: "mdi:clock-out"}, + soil_warning: {entity_category: "config", icon: "mdi:water-percent-alert"}, temperature: {device_class: "temperature", state_class: "measurement"}, temperature_probe: {device_class: "temperature", state_class: "measurement"}, temperature_calibration: {entity_category: "config", icon: "mdi:wrench-clock"}, temperature_max: {entity_category: "config", icon: "mdi:thermometer-plus"}, temperature_min: {entity_category: "config", icon: "mdi:thermometer-minus"}, temperature_offset: {icon: "mdi:thermometer-lines"}, + temperature_sampling: {entity_category: "config", icon: "mdi:clock-out"}, transition: {entity_category: "config", icon: "mdi:transition"}, trigger_count: {icon: "mdi:counter", enabled_by_default: false, state_class: "measurement"}, uv_index: {icon: "mdi:white-balance-sunny", state_class: "measurement"}, @@ -298,6 +332,7 @@ const ENUM_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = { mode_phase_control: {entity_category: "config", icon: "mdi:tune"}, mode: {entity_category: "config", icon: "mdi:tune"}, mode_switch: {icon: "mdi:tune"}, + motor_direction: {entity_category: "config", icon: "mdi:arrow-left-right"}, motion_sensitivity: {entity_category: "config", icon: "mdi:tune"}, operation_mode: {entity_category: "config", icon: "mdi:tune"}, power_on_behavior: {entity_category: "config", icon: "mdi:power-settings"}, @@ -308,11 +343,13 @@ const ENUM_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = { sensitivity: {entity_category: "config", icon: "mdi:tune"}, sensor: {icon: "mdi:tune"}, sensors_type: {entity_category: "config", icon: "mdi:tune"}, + set_limits: {entity_category: "config", icon: "mdi:ray-start-end"}, sound_volume: {entity_category: "config", icon: "mdi:volume-high"}, status: {icon: "mdi:state-machine"}, switch_type: {entity_category: "config", icon: "mdi:tune"}, temperature_display_mode: {entity_category: "config", icon: "mdi:thermometer"}, temperature_sensor_select: {entity_category: "config", icon: "mdi:home-thermometer"}, + temperature_unit: {entity_category: "config", icon: "mdi:temperature-celsius"}, thermostat_unit: {entity_category: "config", icon: "mdi:thermometer"}, update: {device_class: "update"}, volume: {entity_category: "config", icon: "mdi: volume-high"}, @@ -324,7 +361,7 @@ const LIST_DISCOVERY_LOOKUP: {[s: string]: KeyValue} = { color_options: {icon: "mdi:palette"}, level_config: {entity_category: "diagnostic"}, programming_mode: {icon: "mdi:calendar-clock"}, - schedule_settings: {icon: "mdi:calendar-clock"}, + schedule_settings: {entity_category: "config", icon: "mdi:calendar-clock"}, } as const; const featurePropertyWithoutEndpoint = (feature: zhc.Feature): string => { diff --git a/test/extensions/homeassistant.test.ts b/test/extensions/homeassistant.test.ts index 172f466c..49e2b2b4 100644 --- a/test/extensions/homeassistant.test.ts +++ b/test/extensions/homeassistant.test.ts @@ -174,6 +174,79 @@ describe("Extension: HomeAssistant", () => { } }); + it("Should mark device settings as config entities", () => { + const getDiscoveryConfigs = (expose: zhc.Expose): KeyValueAny[] => { + const device = { + definition: {}, + isDevice: (): boolean => true, + isGroup: (): boolean => false, + endpoint: () => undefined, + options: {}, + exposes: (): zhc.Expose[] => [expose], + zh: {endpoints: []}, + }; + // @ts-expect-error private method and minimal test device + return extension.getConfigs(device); + }; + + const enumExposes = [ + new zhc.Enum("set_limits", zhc.access.STATE_SET, ["START", "END", "RESET"]), + new zhc.Enum("motor_direction", zhc.access.STATE_SET, ["forward", "back"]), + new zhc.Enum("temperature_unit", zhc.access.STATE_SET, ["celsius", "fahrenheit"]), + ]; + + for (const expose of enumExposes) { + const [config] = getDiscoveryConfigs(expose); + expect(config.type).toStrictEqual("select"); + expect(config.object_id).toStrictEqual(expose.property); + expect(config.discovery_payload.entity_category).toStrictEqual("config"); + } + + const binaryExposes = [ + new zhc.Binary("tilt_mode", zhc.access.STATE_SET, "ON", "OFF"), + new zhc.Binary("calibration_left", zhc.access.STATE_SET, "ON", "OFF"), + new zhc.Binary("motor_reversal_right", zhc.access.STATE_SET, "ON", "OFF"), + new zhc.Binary("enable_display", zhc.access.STATE_SET, "ON", "OFF"), + new zhc.Binary("indicator", zhc.access.STATE_SET, "ON", "OFF"), + ]; + + for (const expose of binaryExposes) { + const [config] = getDiscoveryConfigs(expose); + expect(config.type).toStrictEqual("switch"); + expect(config.object_id).toStrictEqual(`switch_${expose.property}`); + expect(config.discovery_payload.entity_category).toStrictEqual("config"); + } + + const numericExposes = [ + new zhc.Numeric("calibration_time_left", zhc.access.STATE_SET), + new zhc.Numeric("comfort_temperature_min", zhc.access.STATE_SET), + new zhc.Numeric("comfort_humidity_max", zhc.access.STATE_SET), + new zhc.Numeric("measurement_interval", zhc.access.STATE_SET), + new zhc.Numeric("minimum_range", zhc.access.STATE_SET), + new zhc.Numeric("maximum_range", zhc.access.STATE_SET), + new zhc.Numeric("detection_delay", zhc.access.STATE_SET), + new zhc.Numeric("fading_time", zhc.access.STATE_SET), + new zhc.Numeric("large_motion_detection_sensitivity", zhc.access.STATE_SET), + new zhc.Numeric("medium_motion_detection_distance", zhc.access.STATE_SET), + new zhc.Numeric("small_detection_sensitivity", zhc.access.STATE_SET), + new zhc.Numeric("soil_calibration", zhc.access.STATE_SET), + new zhc.Numeric("soil_sampling", zhc.access.STATE_SET), + new zhc.Numeric("soil_warning", zhc.access.STATE_SET), + ]; + + for (const expose of numericExposes) { + const [config] = getDiscoveryConfigs(expose); + expect(config.type).toStrictEqual("number"); + expect(config.object_id).toStrictEqual(expose.property); + expect(config.discovery_payload.entity_category).toStrictEqual("config"); + } + + const [textConfig] = getDiscoveryConfigs(new zhc.Text("schedule_settings", zhc.access.STATE_SET)); + expect(textConfig.type).toStrictEqual("text"); + expect(textConfig.object_id).toStrictEqual("schedule_settings"); + expect(textConfig.discovery_payload.entity_category).toStrictEqual("config"); + }); + it("Should apply expose-level Home Assistant discovery metadata", () => { const createDevice = (exposes: zhc.Expose[]): Device => ({