diff --git a/drivers/SmartThings/zigbee-motion-sensor/config.yml b/drivers/SmartThings/zigbee-motion-sensor/config.yml index 209ccb2762..da33a7cb38 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/config.yml +++ b/drivers/SmartThings/zigbee-motion-sensor/config.yml @@ -1,4 +1,4 @@ -name: 'Zigbee Motion Sensor' +name: 'Zigbee Motion Sensor vX' packageKey: 'zigbee-motion-sensor' permissions: zigbee: {} diff --git a/drivers/SmartThings/zigbee-motion-sensor/fingerprints.yml b/drivers/SmartThings/zigbee-motion-sensor/fingerprints.yml index 89d3df1586..7aa6fb3b02 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/fingerprints.yml +++ b/drivers/SmartThings/zigbee-motion-sensor/fingerprints.yml @@ -9,6 +9,11 @@ zigbeeManufacturer: manufacturer: LUMI model: lumi.motion.agl02 deviceProfileName: motion-illuminance-battery-aqara + - id: "LUMI/lumi.motion.ac02" + deviceLabel: Aqara Motion Sensor P1 + manufacturer: LUMI + model: lumi.motion.ac02 + deviceProfileName: motion-illuminance-battery-aqara - id: "LUMI/lumi.motion.agl04" deviceLabel: Aqara High Precision Motion Sensor manufacturer: LUMI diff --git a/drivers/SmartThings/zigbee-motion-sensor/profiles/motion-illuminance-battery-aqara.yml b/drivers/SmartThings/zigbee-motion-sensor/profiles/motion-illuminance-battery-aqara.yml index ff58019d63..4e5f20b2da 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/profiles/motion-illuminance-battery-aqara.yml +++ b/drivers/SmartThings/zigbee-motion-sensor/profiles/motion-illuminance-battery-aqara.yml @@ -6,13 +6,19 @@ components: version: 1 - id: illuminanceMeasurement version: 1 + - id: battery + version: 1 - id: stse.detectionFrequency version: 1 - - id: battery + - id: stse.sensitivityAdjustment version: 1 - id: firmwareUpdate version: 1 - id: refresh version: 1 + - id: voltageMeasurement + version: 1 + - id: signalStrength + version: 1 categories: - name: MotionSensor diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/aqara/init.lua b/drivers/SmartThings/zigbee-motion-sensor/src/aqara/init.lua index e4586d4b0f..a3ac5d96e2 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/aqara/init.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/aqara/init.lua @@ -19,6 +19,12 @@ local FREQUENCY_ATTRIBUTE_ID = 0x0102 local MOTION_DETECTED_UINT32 = 65536 +local FINGERPRINTS = { + { mfr = "LUMI", model = "lumi.motion.ac02" }, + { mfr = "LUMI", model = "lumi.motion.agl02" }, + { mfr = "LUMI", model = "lumi.motion.agl04" }, + { mfr = "LUMI", model = "lumi.motion.ac02" } +} local CONFIGURATIONS = { { diff --git a/drivers/SmartThings/zigbee-switch/config.yml b/drivers/SmartThings/zigbee-switch/config.yml index 32ad44a36e..74e58c52da 100644 --- a/drivers/SmartThings/zigbee-switch/config.yml +++ b/drivers/SmartThings/zigbee-switch/config.yml @@ -1,4 +1,4 @@ -name: 'Zigbee Switch' +name: 'Zigbee Switch Vx' packageKey: 'zigbee-switch' permissions: zigbee: {} diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index 613c471dcb..5c69fd4895 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -400,7 +400,7 @@ zigbeeManufacturer: deviceLabel: "RM3500ZB Sinope Load Controller" manufacturer: Sinope Technologies model: RM3500ZB - deviceProfileName: switch-power-smartplug + deviceProfileName: switch-power-waterheater - id: "Sinope Dimmer Switch/DM2500ZB" deviceLabel: "DM2500ZB Sinope Dimmer" manufacturer: Sinope Technologies @@ -950,6 +950,11 @@ zigbeeManufacturer: manufacturer: IKEA of Sweden model: "TRADFRI control outlet" deviceProfileName: basic-switch + - id: IKEA/INSPELNING Smart plug + deviceLabel: INSPELNING Smart plug + manufacturer: IKEA of Sweden + model: "INSPELNING Smart plug" + deviceProfileName: switch-power-energy - id: MEGAMAN/BSZTM005 deviceLabel: INGENIUM Switch manufacturer: MEGAMAN diff --git a/drivers/SmartThings/zigbee-switch/profiles/switch-power-waterheater.yml b/drivers/SmartThings/zigbee-switch/profiles/switch-power-waterheater.yml new file mode 100644 index 0000000000..56acb58b47 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/profiles/switch-power-waterheater.yml @@ -0,0 +1,82 @@ +name: switch-power-waterheater +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: powerMeter + version: 1 + - id: temperatureMeasurement + version: 1 + - id: waterSensor + version: 1 + - id: voltageMeasurement + version: 1 + - id: currentMeasurement + version: 1 + - id: energyMeter + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: SmartPlug + +preferences: + - name: "lowTempProtection" + title: "Low Water Temperature Protection" + description: "Minimum temperature threshold to restart water heater" + required: false + preferenceType: enumeration + definition: + options: + 0: "OFF" + 45: "45 °C" + 46: "46 °C" + 47: "47 °C" + 48: "48 °C" + 49: "49 °C" + 50: "50 °C" + 51: "51 °C" + 52: "52 °C" + 53: "53 °C" + 54: "54 °C" + 55: "55 °C" + default: 45 + + # - title: "Min Measured Temperature" + # name: "minMeasuredTempp" + # description: "Lowest temperature recorded (°C) - Read Only" + # required: false + # preferenceType: number + # definition: + # minimum: -50 + # maximum: 100 + # default: 0 + + # - title: "Max Measured Temperature" + # name: "maxMeasuredTempp" + # description: "Highest temperature recorded (°C) - Read Only" + # required: false + # preferenceType: number + # definition: + # minimum: -50 + # maximum: 100 + # default: 0 + + + + # Tank Size not used yet + # - name: "tankSize" + # title: "Tank Size" + # description: "Select the size of the hot water tank" + # required: false + # preferenceType: enumeration + # definition: + # options: + # 40: "40 Gal" + # 50: "50 Gal" + # 60: "60 Gal" + # 80: "80 Gal" + # default: 40 \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-switch/src/init.lua b/drivers/SmartThings/zigbee-switch/src/init.lua index 9fbb09119e..86edbebf9c 100644 --- a/drivers/SmartThings/zigbee-switch/src/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/init.lua @@ -66,8 +66,43 @@ local zigbee_switch_driver_template = { capabilities.illuminanceMeasurement, capabilities.relativeHumidityMeasurement, capabilities.temperatureMeasurement, + capabilities.waterSensor, + capabilities.voltageMeasurement, + + }, + sub_drivers = { + lazy_load_if_possible("non_zigbee_devices"), + lazy_load_if_possible("hanssem"), + lazy_load_if_possible("aqara"), + lazy_load_if_possible("aqara-light"), + lazy_load_if_possible("ezex"), + lazy_load_if_possible("rexense"), + lazy_load_if_possible("sinope"), + lazy_load_if_possible("sinope-dimmer"), + require("sinope-waterheater"), + lazy_load_if_possible("zigbee-dimmer-power-energy"), + lazy_load_if_possible("zigbee-metering-plug-power-consumption-report"), + lazy_load_if_possible("jasco"), + lazy_load_if_possible("multi-switch-no-master"), + lazy_load_if_possible("zigbee-dual-metering-switch"), + lazy_load_if_possible("rgb-bulb"), + lazy_load_if_possible("zigbee-dimming-light"), + lazy_load_if_possible("white-color-temp-bulb"), + lazy_load_if_possible("rgbw-bulb"), + (version.api < 16) and lazy_load_if_possible("zll-dimmer-bulb") or nil, + lazy_load_if_possible("ikea-xy-color-bulb"), + lazy_load_if_possible("zll-polling"), + lazy_load_if_possible("zigbee-switch-power"), + lazy_load_if_possible("ge-link-bulb"), + lazy_load_if_possible("bad_on_off_data_type"), + lazy_load_if_possible("robb"), + lazy_load_if_possible("wallhero"), + lazy_load_if_possible("inovelli"), -- Combined driver for both VZM31-SN and VZM32-SN + lazy_load_if_possible("laisiao"), + lazy_load_if_possible("tuya-multi"), + lazy_load_if_possible("frient") + }, - sub_drivers = require("sub_drivers"), zigbee_handlers = { global = { [SIMPLE_METERING_ID] = { diff --git a/drivers/SmartThings/zigbee-switch/src/sinope-waterheater/can_handle.lua b/drivers/SmartThings/zigbee-switch/src/sinope-waterheater/can_handle.lua new file mode 100644 index 0000000000..4a1b216130 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/sinope-waterheater/can_handle.lua @@ -0,0 +1,16 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local function can_handle_sinope_rm3500zb(opts, driver, device, ...) + if device ~= nil then + local manufacturer = device:get_manufacturer() + local model = device:get_model() + + -- Check if this is an RM3500ZB + return manufacturer == "Sinope Technologies" and model == "RM3500ZB" + end + + return false +end + +return can_handle_sinope_rm3500zb \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-switch/src/sinope-waterheater/init.lua b/drivers/SmartThings/zigbee-switch/src/sinope-waterheater/init.lua new file mode 100644 index 0000000000..d7ae297c04 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/sinope-waterheater/init.lua @@ -0,0 +1,447 @@ +-- Copyright 2025 SmartThings, Inc. +-- Licensed under the Apache License, Version 2.0 + +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" +local capabilities = require "st.capabilities" +-- local ZigbeeDriver = require "st.zigbee" +-- local defaults = require "st.zigbee.defaults" +local clusters = require "st.zigbee.zcl.clusters" +-- local log = require "log" +local zb_utils = require "st.zigbee.utils" + +-- Sinopé manufacturer cluster and attributes +local SINOPE_SWITCH_CLUSTER = 0xFF01 +local SINOPE_MAX_INTENSITY_ON_ATTRIBUTE = 0x0052 +local SINOPE_MAX_INTENSITY_OFF_ATTRIBUTE = 0x0053 +local SINOPE_CONNECTED_LOAD_ATTR = 0x0060 -- ConnectedLoad (W) +local SINOPE_CURRENT_LOAD_ATTR = 0x0070 -- CurrentLoad (W-ish bitmap) +local SINOPE_DR_MIN_WATER_TEMP_ATTR = 0x0076 -- drConfigWaterTempMin (°C) +local SINOPE_DR_MIN_WATER_TEMP_TIME_ATTR = 0x0077 -- drConfigWaterTempTime +local SINOPE_TIMER_ATTR = 0x00A0 -- Timer seconds +local SINOPE_TIMER_COUNTDOWN_ATTR = 0x00A1 -- Timer_countDown +local SINOPE_MIN_MEASURED_TEMP_ATTR = 0x007C -- min_measured_temp (°C×100) +local SINOPE_MAX_MEASURED_TEMP_ATTR = 0x007D -- max_measured_temp (°C×100) +local SINOPE_ENERGY_INTERNAL_ATTR = 0x0090 -- currentSummationDelivered (internal) + +-- Standard Zigbee clusters +local SimpleMetering = clusters.SimpleMetering -- 0x0702 +local OnOff = clusters.OnOff -- 0x0006 +local TemperatureMeasurement = clusters.TemperatureMeasurement -- 0x0402 +local IASZone = clusters.IASZone -- 0x0500 +local ElectricalMeasurement = clusters.ElectricalMeasurement -- 0x0B04 +-- local Thermostat = clusters.Thermostat + + +-- ============================================================================ +-- STANDARD ATTRIBUTE HANDLERS +-- ============================================================================ + +-- Leak state from IAS Zone 0x0500/0x0002 +local function water_sensor_handler(driver, device, value, zb_rx) + -- log.info("========================================") + -- log.info("RM3500ZB LEAK HANDLER CALLED") + local zone_status = value.value + local wet = (bit32.band(zone_status, 0x0001) ~= 0) -- example mask + if wet then + device:emit_event(capabilities.waterSensor.water.wet()) + else + device:emit_event(capabilities.waterSensor.water.dry()) + end +end + +-- Active power from 0x0B04/0x050B +local function active_power_meter_handler(driver, device, value, zb_rx) + -- log.info("========================================") + -- log.info("RM3500ZB POWERMETER HANDLER CALLED") + + device:emit_event(capabilities.powerMeter.power({value = value.value, unit = "W"})) + device:send(TemperatureMeasurement.attributes.MeasuredValue:read(device)) +end + +-- local function instantaneous_demand_handler(driver, device, value, zb_rx) +-- log.info("========================================") +-- log.info("RM3500ZB INSTANTMETER HANDLER CALLED") + +-- device:emit_event(capabilities.powerMeter.power({value = value.value, unit = "W"})) +-- end + +local function rms_voltage_handler(driver, device, value, zb_rx) + -- log.info("========================================") + -- log.info("RM3500ZB RMSVOLTS HANDLER CALLED") + device:emit_event(capabilities.voltageMeasurement.voltage({value = value.value, unit = "V"})) +end + +local function rms_current_handler(driver, device, value, zb_rx) + -- log.info("========================================") + -- log.info("RM3500ZB RMS CURRENT HANDLER CALLED") + local raw_value = value.value + raw_value = raw_value / 1000 + + device:emit_event(capabilities.currentMeasurement.current({value = raw_value, unit = "A"})) +end + +local function metering_handler(driver, device, value, zb_rx) + -- log.info("========================================") + -- log.info("RM3500ZB ENERGYMETER HANDLER CALLED") + local raw_value = value.value / 1000 -- Convert Wh to kWh + device:emit_event(capabilities.energyMeter.energy({value = raw_value, unit = "kWh"})) +end + +local function temperature_handler(driver, device, value, zb_rx) + -- log.info("========================================") + -- log.info("RM3500ZB TEMPERATURE HANDLER CALLED") + -- log.info("========================================") + local temp_c = value.value /100 + -- the RM3500ZB may report 2 types of values with one in the -300 range. This only sends the value if it is over -100 deg C + if temp_c > -100 then + device:emit_event(capabilities.temperatureMeasurement.temperature({value = value.value / 100, unit = "C"})) + return + end +end + +-- ============================================================================ +-- SINOPE MANUFACTURER ATTRIBUTE HANDLERS +-- ============================================================================ + +-- Handler for Low Temperature Protection (0xFF01/0x0076) +local function sinope_low_temp_protection_handler(driver, device, value, zb_rx) + -- log.info("========================================") + -- log.info("RM3500ZB LOW TEMP PROTECTION HANDLER CALLED") + local temp_value = value.value + -- log.info(string.format("Low temp protection value from device: %d°C", temp_value)) + + -- Store in field + device:set_field("lowTempProtection", temp_value, {persist = true}) +end + +-- -- Handler for Tank Size (0xFF01/0x0060) +-- local function sinope_tank_size_handler(driver, device, value, zb_rx) +-- log.info("========================================") +-- log.info("RM3500ZB TANK SIZE HANDLER CALLED") +-- local tank_size = value.value +-- log.info(string.format("Tank size from device: %d Gallons", tank_size)) + +-- -- Store in field +-- device:set_field("tankSize", tank_size, {persist = true}) +-- end + +-- -- Handler for Min Measured Temp (0xFF01/0x007C) - READ ONLY +-- local function sinope_min_measured_temp_handler(driver, device, value, zb_rx) +-- log.info("========================================") +-- log.info("RM3500ZB MIN MEASURED TEMP HANDLER CALLED") +-- local temp_value = value.value / 100 -- Convert from °C×100 +-- log.info(string.format("Min measured temp from device: %.2f°C", temp_value)) + +-- -- Store in field +-- device:set_field("minMeasuredTemp", temp_value, {persist = true}) + +-- -- -- Update the preference value (this makes it show in settings) +-- -- device:try_update_metadata({ +-- -- preferences = { +-- -- minMeasuredTemp = temp_value +-- -- } +-- -- }) +-- end + +-- -- Handler for Max Measured Temp (0xFF01/0x007D) - READ ONLY +-- local function sinope_max_measured_temp_handler(driver, device, value, zb_rx) +-- log.info("========================================") +-- log.info("RM3500ZB MAX MEASURED TEMP HANDLER CALLED") +-- local temp_value = value.value / 100 -- Convert from °C×100 +-- log.info(string.format("Max measured temp from device updated: %.2f°C", temp_value)) + +-- -- Store in field +-- device:set_field("maxMeasuredTemp", temp_value, {persist = true}) +-- local templog = device:get_field("maxMeasuredTemp") +-- log.info(string.format("Max measured temp from device read: %.2f°C", templog))end + +-- ============================================================================ +-- REFRESH SETTINGS FUNCTION +-- ============================================================================ + +local function refresh_settings(device) + -- log.info("========================================") + -- log.info("RM3500ZB REFRESHING SETTINGS") + -- log.info("========================================") + + -- Read Low Temperature Protection setting + device:send(cluster_base.read_manufacturer_specific_attribute( + device, + SINOPE_SWITCH_CLUSTER, + SINOPE_DR_MIN_WATER_TEMP_ATTR, + 0x119C + )) + + -- -- Read Tank Size setting (Connected Load) + -- device:send(cluster_base.read_manufacturer_specific_attribute( + -- device, + -- SINOPE_SWITCH_CLUSTER, + -- SINOPE_CONNECTED_LOAD_ATTR, + -- 0x119C + -- )) + + -- -- Read Min Measured Temp + -- device:send(cluster_base.read_manufacturer_specific_attribute( + -- device, + -- SINOPE_SWITCH_CLUSTER, + -- SINOPE_MIN_MEASURED_TEMP_ATTR, + -- 0x119C + -- )) + + -- -- Read Max Measured Temp + -- device:send(cluster_base.read_manufacturer_specific_attribute( + -- device, + -- SINOPE_SWITCH_CLUSTER, + -- SINOPE_MAX_MEASURED_TEMP_ATTR, + -- 0x119C + -- )) +end + +-- ============================================================================ +-- TEMPERATURE POLLING +-- ============================================================================ + +local POLL_TIMER_FIELD = "poll_temperature_timer" +local function schedule_temperature_poll(device) + -- 1. Cancel existing timer if any + local existing = device:get_field(POLL_TIMER_FIELD) + if existing then + device.thread:cancel_timer(existing) -- cancel by handle + end + + -- 2. Create new timer + local handle = device:call_with_delay(600, function(d) + device:send(TemperatureMeasurement.attributes.MeasuredValue:read(device)) + end) + + -- 3. Store handle for next time + device:set_field(POLL_TIMER_FIELD, handle) +end + +-- ============================================================================ +-- REFRESH ALL VALUES + SETTINGS +-- ============================================================================ + + +local function do_refresh(driver, device) + -- log.info("========================================") + -- log.info("RM3500ZB DOREFRESH HANDLER CALLED") + -- log.info("========================================") + + -- Refresh all values + device:send(TemperatureMeasurement.attributes.MeasuredValue:read(device)) + -- device:send(SimpleMetering.attributes.InstantaneousDemand:read(device)) + device:send(SimpleMetering.attributes.CurrentSummationDelivered:read(device)) + device:send(ElectricalMeasurement.attributes.ActivePower:read(device)) + device:send(ElectricalMeasurement.attributes.RMSVoltage:read(device)) + device:send(ElectricalMeasurement.attributes.RMSCurrent:read(device)) + device:send(IASZone.attributes.ZoneStatus:read(device)) + device:send(OnOff.attributes.OnOff:read(device)) + + -- Refresh settings values + refresh_settings(device) +end + +-- ============================================================================ +-- CONFIGURE HANDLER - Set reporting intervals +-- ============================================================================ + +local function configure_device(driver, device) + -- log.info("========================================") + -- log.info("RM3500ZB CONFIGURE DEVICE HANDLER CALLED") + -- log.info("========================================") + + -- Configure Temperature reporting (every 30s to 5min, on 0.5°C change) + device:send(TemperatureMeasurement.attributes.MeasuredValue:configure_reporting( + device, + 60, -- min_report_interval (seconds) + 900, -- max_report_interval (seconds) + 1 -- reportable_change (0.5°C, in 0.01°C units) + )) + + -- -- Configure Power reporting (every 10s to 60s, on 5W change) + -- device:send(SimpleMetering.attributes.InstantaneousDemand:configure_reporting( + -- device, + -- 10, -- min interval: 10 seconds + -- 900, -- max interval: 15 minute + -- 1 -- reportable change: 1W (in 0.1W units) + -- )) + + -- Configure Energy reporting (every 5min to 30min, on 0.01 kWh change) + device:send(SimpleMetering.attributes.CurrentSummationDelivered:configure_reporting( + device, + 10, -- min interval: 10 sec + 900, -- max interval: 15 minutes + 10 -- reportable change: 0.01 kWh (in 0.001 kWh units) + )) + + -- Configure Power reporting (every 10s to 60s, on 5W change) + device:send(ElectricalMeasurement.attributes.ActivePower:configure_reporting( + device, + 10, -- min interval: 10 seconds + 900, -- max interval: 15 minute + 1 -- reportable change: 1W (in 0.1W units) + )) + + -- Configure Voltage reporting (every 60s to 5min, on 1V change) + device:send(ElectricalMeasurement.attributes.RMSVoltage:configure_reporting( + device, + 60, -- min interval: 1 minute + 1800, -- max interval: 30 minutes + 10 -- reportable change: 1V (in 0.1V units) + )) + + -- Configure Current reporting (every 10s to 60s, on 0.1A change) + device:send(ElectricalMeasurement.attributes.RMSCurrent:configure_reporting( + device, + 10, -- min interval: 10 seconds + 900, -- max interval: 15 minute + 500 -- reportable change: 0.1A (in 0.001A units) + )) + + -- Configure IAS Zone reporting (water leak detection) + device:send(IASZone.attributes.ZoneStatus:configure_reporting( + device, + 0, -- min interval: immediate + 1800, -- max interval: 30 minutes + 1 -- reportable change: any change + )) + + -- Configure On/Off reporting + device:send(OnOff.attributes.OnOff:configure_reporting( + device, + 0, -- min interval: immediate + 1800, -- max interval: 30 minutes + 1 -- reportable change: any change + )) + + + -- Start (or restart) temperature polling every 600s + schedule_temperature_poll(device) + + -- Refresh all values + settings + do_refresh(driver, device) + + +end + +-- ============================================================================ +-- PREFERENCES CHANGE HANDLER +-- ============================================================================ + +local function info_changed_handler(driver, device, event, args) + -- log.info("========================================") + -- log.info("RM3500ZB INFO_CHANGED HANDLER CALLED") + -- log.info("========================================") + + -- -- Check if this is a settings page open (no old preferences) + if not args.old_st_store.preferences then + -- log.info("Settings page opened - refreshing values") + refresh_settings(device) + return + end + + -- Handle Low Temperature Protection change + if args.old_st_store.preferences.lowTempProtection ~= device.preferences.lowTempProtection then + local new_value = tonumber(device.preferences.lowTempProtection) + -- log.info(string.format("Low temp protection changed to: %d°C", new_value)) + + -- Write to device + device:send(cluster_base.write_manufacturer_specific_attribute( + device, + SINOPE_SWITCH_CLUSTER, + SINOPE_DR_MIN_WATER_TEMP_ATTR, + 0x119C, + data_types.Uint8, + new_value + )) + + -- Read back after 2 seconds to verify + device.thread:call_with_delay(2, function() + -- log.info("Reading back lowTempProtection after write...") + device:send(cluster_base.read_manufacturer_specific_attribute( + device, + SINOPE_SWITCH_CLUSTER, + SINOPE_DR_MIN_WATER_TEMP_ATTR, + 0x119C + )) + end) + end + + -- -- Handle Tank Size change + -- if args.old_st_store.preferences.tankSize ~= device.preferences.tankSize then + -- local new_value = device.preferences.tankSize + -- log.info(string.format("Tank size changed to: %d Gallons", new_value)) + + -- -- Write to device + -- device:send(cluster_base.write_manufacturer_specific_attribute( + -- device, + -- SINOPE_SWITCH_CLUSTER, + -- SINOPE_CONNECTED_LOAD_ATTR, + -- 0x119C, + -- data_types.Uint16, + -- new_value + -- )) + + -- -- Read back after 2 seconds to verify + -- device.thread:call_with_delay(2, function() + -- log.info("Reading back tankSize after write...") + -- device:send(cluster_base.read_manufacturer_specific_attribute( + -- device, + -- SINOPE_SWITCH_CLUSTER, + -- SINOPE_CONNECTED_LOAD_ATTR, + -- 0x119C + -- )) + -- end) + -- end +end + +-- ============================================================================ +-- DRIVER DEFINITION +-- ============================================================================ + +local sinope_rm3500zb = { + NAME = "Sinopé RM3500ZB Water Heater", + capability_handlers = { + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = do_refresh, + } + }, + lifecycle_handlers = { + init = configure_device, -- Called when device is added/configured + doConfigure = configure_device, + infoChanged = info_changed_handler, + }, + zigbee_handlers = { + attr = { + [ElectricalMeasurement.ID] = { + [ElectricalMeasurement.attributes.ActivePower.ID] = active_power_meter_handler, + [ElectricalMeasurement.attributes.RMSVoltage.ID] = rms_voltage_handler, + [ElectricalMeasurement.attributes.RMSCurrent.ID] = rms_current_handler + }, + [SimpleMetering.ID] = { + -- [SimpleMetering.attributes.InstantaneousDemand.ID] = instantaneous_demand_handler, + [SimpleMetering.attributes.CurrentSummationDelivered.ID] = metering_handler + }, + [TemperatureMeasurement.ID] = { + [TemperatureMeasurement.attributes.MeasuredValue.ID] = temperature_handler + }, + [IASZone.ID] = { + [IASZone.attributes.ZoneStatus.ID] = water_sensor_handler + }, + [SINOPE_SWITCH_CLUSTER] = { + [SINOPE_DR_MIN_WATER_TEMP_ATTR] = sinope_low_temp_protection_handler, + -- [SINOPE_MIN_MEASURED_TEMP_ATTR] = sinope_min_measured_temp_handler, + -- [SINOPE_MAX_MEASURED_TEMP_ATTR] = sinope_max_measured_temp_handler, + }, + }, + }, + can_handle = require("sinope-waterheater.can_handle") +} + + +return sinope_rm3500zb + + + diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/fingerprints.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/fingerprints.lua index f393f61eea..3c60f23233 100644 --- a/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/fingerprints.lua +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/fingerprints.lua @@ -15,6 +15,5 @@ return { { mfr = "AduroSmart Eria", model = "AD-SmartPlug3001" }, { mfr = "AduroSmart Eria", model = "BPU3" }, { mfr = "AduroSmart Eria", model = "BDP3001" }, - { mfr = "LEDVANCE", model = "PLUG COMPACT EU EM T" }, - { mfr = "LEDVANCE", model = "PLUG EU EM T" } + { mfr = "IKEA of Sweden", model = "INSPELNING Smart plug" } } diff --git a/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/init.lua b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/init.lua index 0ea2224a6f..eb2138b84c 100644 --- a/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/zigbee-switch-power/init.lua @@ -7,6 +7,7 @@ local constants = require "st.zigbee.constants" local SimpleMetering = clusters.SimpleMetering local ElectricalMeasurement = clusters.ElectricalMeasurement +local TemperatureMeasurement = clusters.TemperatureMeasurement local function active_power_meter_handler(driver, device, value, zb_rx) local raw_value = value.value @@ -26,15 +27,42 @@ local function instantaneous_demand_handler(driver, device, value, zb_rx) device:emit_event(capabilities.powerMeter.power({value = raw_value, unit = "W"})) end +local function rms_voltage_handler(driver, device, value, zb_rx) + device:emit_event(capabilities.voltageMeasurement.voltage({value = value.value, unit = "V"})) +end + +local function rms_current_handler(driver, device, value, zb_rx) + device:emit_event(capabilities.currentMeter.current({value = value.value, unit = "A"})) +end + +local function metering_handler(driver, device, value, zb_rx) + local raw_value = value.value / 1000 -- Convert Wh to kWh + device:emit_event(capabilities.energyMeter.energy({value = raw_value, unit = "kWh"})) +end + +local function temperature_handler(driver, device, value, zb_rx) + log.info("========================================") + log.info("ZIGBEE GENERIC SWITCHPOWER TEMPERATURE HANDLER CALLED") + log.info("========================================") + device:emit_event(capabilities.temperatureMeasurement.temperature({value = value.value / 100, unit = "C"})) +end + local zigbee_switch_power = { NAME = "Zigbee Switch Power", zigbee_handlers = { attr = { [ElectricalMeasurement.ID] = { - [ElectricalMeasurement.attributes.ActivePower.ID] = active_power_meter_handler + [ElectricalMeasurement.attributes.ActivePower.ID] = active_power_meter_handler, + [ElectricalMeasurement.attributes.RMSVoltage.ID] = rms_voltage_handler, + [ElectricalMeasurement.attributes.RMSCurrent.ID] = rms_current_handler }, [SimpleMetering.ID] = { - [SimpleMetering.attributes.InstantaneousDemand.ID] = instantaneous_demand_handler + [SimpleMetering.attributes.InstantaneousDemand.ID] = instantaneous_demand_handler, + [SimpleMetering.attributes.CurrentSummationDelivered.ID] = metering_handler + + }, + [TemperatureMeasurement.ID] = { + [TemperatureMeasurement.attributes.MeasuredValue.ID] = temperature_handler } } }, diff --git a/drivers/SmartThings/zigbee-thermostat/config.yml b/drivers/SmartThings/zigbee-thermostat/config.yml index 8c0d6d79fd..f36485b693 100644 --- a/drivers/SmartThings/zigbee-thermostat/config.yml +++ b/drivers/SmartThings/zigbee-thermostat/config.yml @@ -1,4 +1,4 @@ -name: 'Zigbee Thermostat' +name: 'Zigbee Thermostat vX' defaultProfile: 'thermostat-battery-powerSource' packageKey: 'zigbee-thermostat' permissions: diff --git a/drivers/SmartThings/zigbee-thermostat/fingerprints.yml b/drivers/SmartThings/zigbee-thermostat/fingerprints.yml index ed6afd59b5..068784ba44 100644 --- a/drivers/SmartThings/zigbee-thermostat/fingerprints.yml +++ b/drivers/SmartThings/zigbee-thermostat/fingerprints.yml @@ -84,6 +84,11 @@ zigbeeManufacturer: manufacturer: Stelpro model: STZB402+ deviceProfileName: thermostat-temperature-temperaturealarm + - id: "Stelpro/SMT402AD" + deviceLabel: Stelpro Thermostat + manufacturer: Stelpro + model: SMT402AD + deviceProfileName: thermostat-stelpro-profile - id: "Stelpro/ST218" deviceLabel: Stelpro Thermostat manufacturer: Stelpro diff --git a/drivers/SmartThings/zigbee-thermostat/profiles/thermostat-stelpro-profile.yml b/drivers/SmartThings/zigbee-thermostat/profiles/thermostat-stelpro-profile.yml index aee0c42f2d..02ddb08a4f 100644 --- a/drivers/SmartThings/zigbee-thermostat/profiles/thermostat-stelpro-profile.yml +++ b/drivers/SmartThings/zigbee-thermostat/profiles/thermostat-stelpro-profile.yml @@ -14,6 +14,15 @@ components: values: - key: "heatingSetpoint.value" range: [ 5, 30 ] + - id: thermostatCoolingSetpoint + - id: thermostatMode + version: 1 + config: + values: + - key: "thermostatMode.value" + enabledValues: + - heat + default: heat - id: thermostatOperatingState version: 1 config: @@ -22,6 +31,11 @@ components: enabledValues: - heating - idle + - id: powerMeter + version: 1 + config: + values: + - key: "powerMeter.value" - id: temperatureAlarm version: 1 config: diff --git a/drivers/SmartThings/zigbee-thermostat/src/stelpro/init.lua b/drivers/SmartThings/zigbee-thermostat/src/stelpro/init.lua index d186c46b67..15159564f4 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/stelpro/init.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/stelpro/init.lua @@ -4,18 +4,78 @@ local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" +local device_management = require "st.zigbee.device_management" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" +local RelativeHumidity = clusters.RelativeHumidity local Thermostat = clusters.Thermostat local ThermostatUserInterfaceConfiguration = clusters.ThermostatUserInterfaceConfiguration +local ThermostatMode = capabilities.thermostatMode local ThermostatOperatingState = capabilities.thermostatOperatingState +local ThermostatPower = capabilities.powerMeter + +local POWER_ATTTRIBUTE = 0x4008 +local MFG_CODE = 0x1185 local RX_FREEZE_VALUE = 0x7ffd local RX_HEAT_VALUE = 0x7fff local FREEZE_ALRAM_TEMPERATURE = 0 local HEAT_ALRAM_TEMPERATURE = 50 +local STELPRO_THERMOSTAT_FINGERPRINTS = { + { mfr = "Stelpro", model = "MaestroStat" }, + { mfr = "Stelpro", model = "SORB" }, + { mfr = "Stelpro", model = "SonomaStyle" }, + { mfr = "Stelpro", model = "SMT402AD" } -- added M.Colmenarejo +} + +local is_stelpro_thermostat = function(opts, driver, device) + for _, fingerprint in ipairs(STELPRO_THERMOSTAT_FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true + end + end + return false +end + +local do_refresh = function(self, device) + local attributes = { + Thermostat.attributes.LocalTemperature, + Thermostat.attributes.PIHeatingDemand, + Thermostat.attributes.OccupiedHeatingSetpoint, + ThermostatUserInterfaceConfiguration.attributes.TemperatureDisplayMode, + ThermostatUserInterfaceConfiguration.attributes.KeypadLockout, + RelativeHumidity.attributes.MeasuredValue + } + for _, attribute in pairs(attributes) do + device:send(attribute:read(device)) + end + device:send(cluster_base.read_manufacturer_specific_attribute(device, Thermostat.ID, POWER_ATTTRIBUTE, MFG_CODE)) +end + +local device_added = function(self, device) + device:emit_event(capabilities.temperatureAlarm.temperatureAlarm.cleared()) + do_refresh(self, device) +end + +local do_configure = function(self, device) + device:send(Thermostat.attributes.LocalTemperature:configure_reporting(device, 10, 60, 50)) + device:send(Thermostat.attributes.OccupiedHeatingSetpoint:configure_reporting(device, 1, 600, 50)) + device:send(Thermostat.attributes.PIHeatingDemand:configure_reporting(device, 1, 60, 1)) + device:send(cluster_base.configure_reporting(device, data_types.ClusterId(Thermostat.ID) , POWER_ATTTRIBUTE, 0x23 , 1, 60, 1)) + device:send(ThermostatUserInterfaceConfiguration.attributes.TemperatureDisplayMode:configure_reporting(device, 1, 0, 1)) + device:send(ThermostatUserInterfaceConfiguration.attributes.KeypadLockout:configure_reporting(device, 1, 0, 1)) + device:send(RelativeHumidity.attributes.MeasuredValue:configure_reporting(device, 10, 300, 1)) + + device:emit_event(ThermostatMode.thermostatMode.heat()) +end + +local device_init = function(self, device) + do_configure(self, device) +end local function get_temperature(temperature) return temperature / 100 @@ -50,11 +110,11 @@ local function thermostat_local_temp_attr_handler(driver, device, value, zb_rx) (last_alarm == "freeze" and temperature > FREEZE_ALRAM_TEMPERATURE) or (last_alarm == "heat" and temperature < HEAT_ALRAM_TEMPERATURE) ) then - if last_alarm == "freeze" then - event = capabilities.temperatureAlarm.temperatureAlarm.freeze() - else - event = capabilities.temperatureAlarm.temperatureAlarm.heat() - end + if last_alarm == "freeze" then + event = capabilities.temperatureAlarm.temperatureAlarm.freeze() + else + event = capabilities.temperatureAlarm.temperatureAlarm.heat() + end end else if temperature <= FREEZE_ALRAM_TEMPERATURE then @@ -75,11 +135,16 @@ local function thermostat_heating_set_point_attr_handler(driver, device, value, end local function thermostat_heating_demand_attr_handler(driver, device, value, zb_rx) - local event = value.value < 10 and ThermostatOperatingState.thermostatOperatingState.idle() or - ThermostatOperatingState.thermostatOperatingState.heating() + local event = value.value < 1 and ThermostatOperatingState.thermostatOperatingState.idle() or + ThermostatOperatingState.thermostatOperatingState.heating() device:emit_event(event) end +local function thermostat_power_attr_handler(driver, device, zb_rx) + local point_value = zb_rx.value + device:emit_event(capabilities.powerMeter.power({value = point_value, unit = "W"})) +end + local function info_changed(driver, device, event, args) if device.preferences ~= nil and device.preferences.lock ~= args.old_st_store.preferences.lock then device:send(ThermostatUserInterfaceConfiguration.attributes.KeypadLockout:write(device, tonumber(device.preferences.lock))) @@ -98,15 +163,22 @@ local stelpro_thermostat = { [Thermostat.attributes.PIHeatingDemand.ID] = thermostat_heating_demand_attr_handler, [Thermostat.attributes.LocalTemperature.ID] = thermostat_local_temp_attr_handler, [Thermostat.attributes.OccupiedHeatingSetpoint.ID] = thermostat_heating_set_point_attr_handler, + [POWER_ATTTRIBUTE] = thermostat_power_attr_handler } } }, lifecycle_handlers = { + init = device_init, + doConfigure = do_configure, added = device_added, infoChanged = info_changed }, - sub_drivers = require("stelpro.sub_drivers"), - can_handle = require("stelpro.can_handle"), + capability_handlers = { + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = do_refresh, + } + }, + can_handle = is_stelpro_thermostat, } return stelpro_thermostat