diff --git a/huan.json b/huan.json index a5f19ce..578c2ce 100644 --- a/huan.json +++ b/huan.json @@ -1,5 +1,5 @@ { - "name": "huan-light1.1", + "name": "huan-light1.3", "description": light, "termsOfServiceUrl": "", "type": "DRIVER" diff --git a/zigbee-switch/fingerprints.yml b/zigbee-switch/fingerprints.yml index 2c1fa50..18c6de9 100644 --- a/zigbee-switch/fingerprints.yml +++ b/zigbee-switch/fingerprints.yml @@ -9,6 +9,31 @@ zigbeeManufacturer: manufacturer: LUMI model: lumi.switch.b1laus01 deviceProfileName: basic-switch + - id: "LUMI/lumi.switch.n1acn1" + deviceLabel: Aqara Smart Wall Switch H1 (With Neutral, Single Rocker) + manufacturer: LUMI + model: lumi.switch.n1acn1 + deviceProfileName: aqara-switch-power + - id: "LUMI/lumi.switch.n2acn1" + deviceLabel: Aqara Smart Wall Switch H1 (With Neutral, Double Rocker) 1 + manufacturer: LUMI + model: lumi.switch.n2acn1 + deviceProfileName: aqara-switch-power + - id: "LUMI/lumi.switch.n3acn1" + deviceLabel: Aqara Smart Wall Switch H1 (With Neutral, Triple Rocker) 1 + manufacturer: LUMI + model: lumi.switch.n3acn1 + deviceProfileName: aqara-switch-power + - id: "LUMI/lumi.switch.n0agl1" + deviceLabel: Aqara Single Switch Module T1 (With Neutral) + manufacturer: LUMI + model: lumi.switch.n0agl1 + deviceProfileName: aqara-switch-module + - id: "LUMI/lumi.light.acn004" + deviceLabel: Aqara Smart Dimmer Controller T1 Pro + manufacturer: LUMI + model: lumi.light.acn004 + deviceProfileName: aqara-light # VIMAR - id: "Vimar/xx592-2-way-smart-switch" deviceLabel: Vimar 2-way Smart Switch @@ -21,7 +46,7 @@ zigbeeManufacturer: - id: "Vimar/xx595-smart-dimmer-switch" deviceLabel: Vimar Smart Dimmer Switch model: "DimmerSwitch_v1.0" - deviceProfileName: on-off-level-no-firmware-update + deviceProfileName: on-off-level-no-firmware-update - id: "Vimar/xx593-smart-actuator-power" deviceLabel: Vimar Smart Actuator with Power Metering model: "Mains_Power_Outlet_v1.0" @@ -219,6 +244,11 @@ zigbeeManufacturer: manufacturer: Sinope Technologies model: RM3250ZB deviceProfileName: switch-power-smartplug + - id: "Sinope Switch/RM3500ZB" + deviceLabel: "RM3500ZB Sinope Load Controller" + manufacturer: Sinope Technologies + model: RM3500ZB + deviceProfileName: switch-power-smartplug - id: "Sinope Dimmer Switch/DM2500ZB" deviceLabel: "DM2500ZB Sinope Dimmer" manufacturer: Sinope Technologies @@ -844,7 +874,7 @@ zigbeeManufacturer: model: 3RSP019BZ deviceProfileName: basic-switch - id: Third Reality/3RSP02028BZ - deviceLabel: Huan Plug9 + deviceLabel: ThirdReality Plug manufacturer: Third Reality, Inc model: 3RSP02028BZ deviceProfileName: switch-power-energy @@ -907,7 +937,7 @@ zigbeeManufacturer: deviceLabel: Samsung Light manufacturer: Samsung Electronics model: SAMSUNG-ITM-Z-002 - deviceProfileName: rgbw-bulb + deviceProfileName: rgbw-bulb-2700K-5000K - id: Juno/ABL-LIGHT-Z-201 deviceLabel: Juno Connect manufacturer: Juno @@ -1202,12 +1232,12 @@ zigbeeManufacturer: deviceLabel: Juno Connect manufacturer: Samsung Electronics model: ABL-LIGHT-Z-001 - deviceProfileName: color-temp-bulb-2700K-5000K + deviceProfileName: abl-light-z-001-bulb - id: Juno/ABL-LIGHT-Z-001 deviceLabel: Juno Connect manufacturer: Juno model: ABL-LIGHT-Z-001 - deviceProfileName: color-temp-bulb-2700K-5000K + deviceProfileName: abl-light-z-001-bulb - id: Samsung/SAMSUNG-ITM-Z-001 deviceLabel: Samsung Light manufacturer: Samsung Electronics @@ -1979,7 +2009,7 @@ zigbeeManufacturer: model: E13-N11 deviceProfileName: on-off-level-motion-sensor - id: "Third Reality/3RSNL02043Z" - deviceLabel: Weihuan + deviceLabel: ThirdReality Test manufacturer: Third Reality, Inc model: 3RSNL02043Z deviceProfileName: on-off-level-motion-sensor @@ -1998,6 +2028,22 @@ zigbeeManufacturer: manufacturer: GE model: Daylight deviceProfileName: ge-link-bulb + #ROBB (Sunricher) + - id: "ROBB smarrt/ROB_200-004-0" + manufacturer: ROBB smarrt + model: ROB_200-004-0 + deviceLabel: ROBB SMARRT Zigbee dimmer 3-wire + deviceProfileName: on-off-level + - id: "ROBB smarrt/ROB_200-014-0" + manufacturer: ROBB smarrt + model: ROB_200-014-0 + deviceLabel: ROBB SMARRT Zigbee rotary dimmer 2-wire + deviceProfileName: light-level-power-energy + - id: "ROBB smarrt/ROB_200-011-0" + manufacturer: ROBB smarrt + model: ROB_200-011-0 + deviceLabel: ROBB SMARRT Zigbee dimmer 2-wire + deviceProfileName: light-level-power-energy zigbeeGeneric: - id: "genericSwitch" deviceLabel: Zigbee Switch diff --git a/zigbee-switch/profiles/abl-light-z-001-bulb.yml b/zigbee-switch/profiles/abl-light-z-001-bulb.yml new file mode 100644 index 0000000..8a0d62b --- /dev/null +++ b/zigbee-switch/profiles/abl-light-z-001-bulb.yml @@ -0,0 +1,19 @@ +name: abl-light-z-001-bulb +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: switchLevel + version: 1 + - id: colorTemperature + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Light +metadata: + mnmn: Samsung Electronics + vid: ABL-LIGHT-Z-001 diff --git a/zigbee-switch/profiles/aqara-light.yml b/zigbee-switch/profiles/aqara-light.yml new file mode 100644 index 0000000..3b4462b --- /dev/null +++ b/zigbee-switch/profiles/aqara-light.yml @@ -0,0 +1,33 @@ +name: aqara-light +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: switchLevel + version: 1 + config: + values: + - key: "level.value" + range: [1, 100] + - id: colorTemperature + version: 1 + config: + values: + - key: "colorTemperature.value" + range: [2700, 6000] + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Light +preferences: + - preferenceId: stse.restorePowerState + explicit: true + - preferenceId: stse.turnOffIndicatorLight + explicit: true + - preferenceId: stse.lightFadeInTimeInSec + explicit: true + - preferenceId: stse.lightFadeOutTimeInSec + explicit: true diff --git a/zigbee-switch/profiles/aqara-switch-child.yml b/zigbee-switch/profiles/aqara-switch-child.yml new file mode 100644 index 0000000..34951f4 --- /dev/null +++ b/zigbee-switch/profiles/aqara-switch-child.yml @@ -0,0 +1,17 @@ +name: aqara-switch-child +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: button + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Switch +preferences: + - preferenceId: stse.changeToWirelessSwitch + explicit: true diff --git a/zigbee-switch/profiles/aqara-switch-module.yml b/zigbee-switch/profiles/aqara-switch-module.yml new file mode 100644 index 0000000..4dc2c9a --- /dev/null +++ b/zigbee-switch/profiles/aqara-switch-module.yml @@ -0,0 +1,26 @@ +name: aqara-switch-module +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Switch +preferences: + - preferenceId: stse.restorePowerState + explicit: true + - preferenceId: stse.electricSwitchType + explicit: true +metadata: + mnmn: SmartThingsCommunity + vid: 0f305a94-72e1-3fb8-af85-24eedff49d19 diff --git a/zigbee-switch/profiles/aqara-switch-power.yml b/zigbee-switch/profiles/aqara-switch-power.yml new file mode 100644 index 0000000..9983246 --- /dev/null +++ b/zigbee-switch/profiles/aqara-switch-power.yml @@ -0,0 +1,28 @@ +name: aqara-switch-power +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: button + version: 1 + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: powerConsumptionReport + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Switch +preferences: + - preferenceId: stse.restorePowerState + explicit: true + - preferenceId: stse.changeToWirelessSwitch + explicit: true +metadata: + mnmn: SmartThingsCommunity + vid: d3530dd0-6a53-370b-9b4f-36ac09f99a38 diff --git a/zigbee-switch/profiles/light-level-power-energy.yml b/zigbee-switch/profiles/light-level-power-energy.yml new file mode 100644 index 0000000..b45fc5e --- /dev/null +++ b/zigbee-switch/profiles/light-level-power-energy.yml @@ -0,0 +1,22 @@ +name: light-level-power-energy +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: switchLevel + version: 1 + config: + values: + - key: "level.value" + range: [1, 100] + - id: powerMeter + version: 1 + - id: energyMeter + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Light \ No newline at end of file diff --git a/zigbee-switch/src/aqara-light/init.lua b/zigbee-switch/src/aqara-light/init.lua new file mode 100644 index 0000000..64b3a31 --- /dev/null +++ b/zigbee-switch/src/aqara-light/init.lua @@ -0,0 +1,55 @@ +local clusters = require "st.zigbee.zcl.clusters" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" + +local OnOff = clusters.OnOff +local Level = clusters.Level +local ColorControl = clusters.ColorControl + +local PRIVATE_CLUSTER_ID = 0xFCC0 +local PRIVATE_ATTRIBUTE_ID = 0x0009 +local MFG_CODE = 0x115F + +local FINGERPRINTS = { + { mfr = "LUMI", model = "lumi.light.acn004" } +} + +local function is_aqara_products(opts, driver, device) + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true + end + end + return false +end + +local function do_refresh(self, device) + device:send(OnOff.attributes.OnOff:read(device)) + device:send(Level.attributes.CurrentLevel:read(device)) + device:send(ColorControl.attributes.ColorTemperatureMireds:read(device)) +end + +local function do_configure(self, device) + device:send(ColorControl.commands.MoveToColorTemperature(device, 200, 0x0000)) + device:configure() + do_refresh(self, device) +end + +local function device_added(driver, device, event) + device:send(cluster_base.write_manufacturer_specific_attribute(device, + PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 1)) -- private + + device:send(Level.attributes.OnTransitionTime:write(device, 0)) + device:send(Level.attributes.OffTransitionTime:write(device, 0)) +end + +local aqara_light_handler = { + NAME = "Aqara Light Handler", + lifecycle_handlers = { + added = device_added, + doConfigure = do_configure + }, + can_handle = is_aqara_products +} + +return aqara_light_handler diff --git a/zigbee-switch/src/aqara/aqara_version/init.lua b/zigbee-switch/src/aqara/aqara_version/init.lua deleted file mode 100644 index 43f74aa..0000000 --- a/zigbee-switch/src/aqara/aqara_version/init.lua +++ /dev/null @@ -1,60 +0,0 @@ -local capabilities = require "st.capabilities" -local clusters = require "st.zigbee.zcl.clusters" - -local OnOff = clusters.OnOff -local Basic = clusters.Basic -local AnalogInput = clusters.AnalogInput - -local ENDPOINT_POWER_METER = 0x15 -local ENDPOINT_ENERGY_METER = 0x1F - -local APPLICATION_VERSION = "application_version" - -local function is_aqara_version(opts, driver, device) - local softwareVersion = device:get_field(APPLICATION_VERSION) - return softwareVersion and softwareVersion == 32 -end - -local function on_off_handler(driver, device, value, zb_rx) - if value.value == true then - device:emit_event(capabilities.switch.switch.on()) - device:send(AnalogInput.attributes.PresentValue:read(device):to_endpoint(ENDPOINT_POWER_METER)) - device:send(AnalogInput.attributes.PresentValue:read(device):to_endpoint(ENDPOINT_ENERGY_METER)) - else - device:emit_event(capabilities.switch.switch.off()) - end -end - -local function do_refresh(self, device) - device:send(OnOff.attributes.OnOff:read(device)) - device:send(AnalogInput.attributes.PresentValue:read(device):to_endpoint(ENDPOINT_POWER_METER)) - device:send(AnalogInput.attributes.PresentValue:read(device):to_endpoint(ENDPOINT_ENERGY_METER)) -end - -local function do_configure(self, device) - device:configure() - device:send(Basic.attributes.ApplicationVersion:read(device)) - do_refresh(self, device) -end - -local aqara_smart_plug_version_handler = { - NAME = "Aqara Smart Plug Version Handler", - lifecycle_handlers = { - doConfigure = do_configure, - }, - capability_handlers = { - [capabilities.refresh.ID] = { - [capabilities.refresh.commands.refresh.NAME] = do_refresh, - } - }, - zigbee_handlers = { - attr = { - [OnOff.ID] = { - [OnOff.attributes.OnOff.ID] = on_off_handler - } - } - }, - can_handle = is_aqara_version, -} - -return aqara_smart_plug_version_handler diff --git a/zigbee-switch/src/aqara/init.lua b/zigbee-switch/src/aqara/init.lua index bf81d9c..c498928 100644 --- a/zigbee-switch/src/aqara/init.lua +++ b/zigbee-switch/src/aqara/init.lua @@ -2,63 +2,92 @@ local capabilities = require "st.capabilities" local clusters = require "st.zigbee.zcl.clusters" local cluster_base = require "st.zigbee.cluster_base" local data_types = require "st.zigbee.data_types" +local constants = require "st.zigbee.constants" local SinglePrecisionFloat = require "st.zigbee.data_types".SinglePrecisionFloat local OnOff = clusters.OnOff local ElectricalMeasurement = clusters.ElectricalMeasurement local SimpleMetering = clusters.SimpleMetering -local Basic = clusters.Basic -local AnalogInput = clusters.AnalogInput +local Groups = clusters.Groups -local MAX_POWER_ID = "stse.maxPower" -- maximum allowable power -local RESTORE_STATE_ID = "stse.restorePowerState" -- remember previous state +local maxPower = capabilities["stse.maxPower"] +local restorePowerState = capabilities["stse.restorePowerState"] +local electricSwitchType = capabilities["stse.electricSwitchType"] +local changeToWirelessSwitch = capabilities["stse.changeToWirelessSwitch"] -local MFG_CODE = 0x115F local PRIVATE_CLUSTER_ID = 0xFCC0 local PRIVATE_ATTRIBUTE_ID = 0x0009 +local MFG_CODE = 0x115F +local WIRELESS_SWITCH_CLUSTER_ID = 0x0012 +local WIRELESS_SWITCH_ATTRIBUTE_ID = 0x0055 +local RESTORE_POWER_STATE_ATTRIBUTE_ID = 0x0201 +local CHANGE_TO_WIRELESS_SWITCH_ATTRIBUTE_ID = 0x0200 +local MAX_POWER_ATTRIBUTE_ID = 0x020B +local ELECTRIC_SWITCH_TYPE_ATTRIBUTE_ID = 0x000A -local PREF_CLUSTER_ID = 0xFCC0 -local PREF_MAX_POWER_ATTR_ID = 0x020B -local PREF_RESTORE_STATE_ATTR_ID = 0x0201 - -local ENDPOINT_POWER_METER = 0x15 -local ENDPOINT_ENERGY_METER = 0x1F - -local APPLICATION_VERSION = "application_version" local LAST_REPORT_TIME = "LAST_REPORT_TIME" +local PRIVATE_MODE = "PRIVATE_MODE" local FINGERPRINTS = { - { mfr = "LUMI", model = "lumi.plug.maeu01" } + { mfr = "LUMI", model = "lumi.plug.maeu01" }, + { mfr = "LUMI", model = "lumi.switch.n0agl1" }, + { mfr = "LUMI", model = "lumi.switch.n1acn1" }, + { mfr = "LUMI", model = "lumi.switch.n2acn1" }, + { mfr = "LUMI", model = "lumi.switch.n3acn1" }, } --- Range from 100 to 2300 (100w ~ 2300w) --- Data type conversion table -local max_power_data_type_table = { - SinglePrecisionFloat(0, 6, 0.5625), - SinglePrecisionFloat(0, 7, 0.5625), - SinglePrecisionFloat(0, 8, 0.171875), - SinglePrecisionFloat(0, 8, 0.5625), - SinglePrecisionFloat(0, 8, 0.953125), - SinglePrecisionFloat(0, 9, 0.171875), - SinglePrecisionFloat(0, 9, 0.3671875), - SinglePrecisionFloat(0, 9, 0.5625), - SinglePrecisionFloat(0, 9, 0.7578125), - SinglePrecisionFloat(0, 9, 0.953125), - - SinglePrecisionFloat(0, 10, 0.07421875), - SinglePrecisionFloat(0, 10, 0.171875), - SinglePrecisionFloat(0, 10, 0.26953125), - SinglePrecisionFloat(0, 10, 0.3671875), - SinglePrecisionFloat(0, 10, 0.46484375), - SinglePrecisionFloat(0, 10, 0.5625), - SinglePrecisionFloat(0, 10, 0.66015625), - SinglePrecisionFloat(0, 10, 0.7578125), - SinglePrecisionFloat(0, 10, 0.85546875), - SinglePrecisionFloat(0, 10, 0.953125), - - SinglePrecisionFloat(0, 11, 0.025390625), - SinglePrecisionFloat(0, 11, 0.07421875), - SinglePrecisionFloat(0, 11, 0.123046875) +local preference_map = { + [restorePowerState.ID] = { + cluster_id = PRIVATE_CLUSTER_ID, + attribute_id = RESTORE_POWER_STATE_ATTRIBUTE_ID, + mfg_code = MFG_CODE, + data_type = data_types.Boolean, + }, + [changeToWirelessSwitch.ID] = { + cluster_id = PRIVATE_CLUSTER_ID, + attribute_id = CHANGE_TO_WIRELESS_SWITCH_ATTRIBUTE_ID, + mfg_code = MFG_CODE, + data_type = data_types.Uint8, + value_map = { [true] = 0x00,[false] = 0x01 }, + }, + [maxPower.ID] = { + cluster_id = PRIVATE_CLUSTER_ID, + attribute_id = MAX_POWER_ATTRIBUTE_ID, + mfg_code = MFG_CODE, + data_type = data_types.SinglePrecisionFloat, + value_map = { + ["1"] = SinglePrecisionFloat(0, 6, 0.5625), + ["2"] = SinglePrecisionFloat(0, 7, 0.5625), + ["3"] = SinglePrecisionFloat(0, 8, 0.171875), + ["4"] = SinglePrecisionFloat(0, 8, 0.5625), + ["5"] = SinglePrecisionFloat(0, 8, 0.953125), + ["6"] = SinglePrecisionFloat(0, 9, 0.171875), + ["7"] = SinglePrecisionFloat(0, 9, 0.3671875), + ["8"] = SinglePrecisionFloat(0, 9, 0.5625), + ["9"] = SinglePrecisionFloat(0, 9, 0.7578125), + ["10"] = SinglePrecisionFloat(0, 9, 0.953125), + ["11"] = SinglePrecisionFloat(0, 10, 0.07421875), + ["12"] = SinglePrecisionFloat(0, 10, 0.171875), + ["13"] = SinglePrecisionFloat(0, 10, 0.26953125), + ["14"] = SinglePrecisionFloat(0, 10, 0.3671875), + ["15"] = SinglePrecisionFloat(0, 10, 0.46484375), + ["16"] = SinglePrecisionFloat(0, 10, 0.5625), + ["17"] = SinglePrecisionFloat(0, 10, 0.66015625), + ["18"] = SinglePrecisionFloat(0, 10, 0.7578125), + ["19"] = SinglePrecisionFloat(0, 10, 0.85546875), + ["20"] = SinglePrecisionFloat(0, 10, 0.953125), + ["21"] = SinglePrecisionFloat(0, 11, 0.025390625), + ["22"] = SinglePrecisionFloat(0, 11, 0.07421875), + ["23"] = SinglePrecisionFloat(0, 11, 0.123046875) + }, + }, + [electricSwitchType.ID] = { + cluster_id = PRIVATE_CLUSTER_ID, + attribute_id = ELECTRIC_SWITCH_TYPE_ATTRIBUTE_ID, + mfg_code = MFG_CODE, + data_type = data_types.Uint8, + value_map = { rocker = 0x01, rebound = 0x02 }, + }, } local function is_aqara_products(opts, driver, device) @@ -70,9 +99,37 @@ local function is_aqara_products(opts, driver, device) return false end -local function emit_power_consumption_report_event(device, value) - local raw_value = value.value -- 'Wh' +local function private_mode_handler(driver, device, value, zb_rx) + device:set_field(PRIVATE_MODE, value.value, { persist = true }) + + if value.value ~= 1 then + device:send(SimpleMetering.attributes.CurrentSummationDelivered:configure_reporting(device, 900, 3600, 1)) -- minimal interval : 15min + device:set_field(constants.ELECTRICAL_MEASUREMENT_DIVISOR_KEY, 10, { persist = true }) + device:set_field(constants.SIMPLE_METERING_DIVISOR_KEY, 1000, { persist = true }) + end +end + +local function wireless_switch_handler(driver, device, value, zb_rx) + if value.value == 1 then + device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, + capabilities.button.button.pushed({ state_change = true })) + end +end + +local function energy_meter_power_consumption_report(device, raw_value) + -- energy meter + device:emit_event(capabilities.energyMeter.energy({ value = raw_value, unit = "Wh" })) + -- report interval + local current_time = os.time() + local last_time = device:get_field(LAST_REPORT_TIME) or 0 + local next_time = last_time + 60 * 15 -- 15 mins, the minimum interval allowed between reports + if current_time < next_time then + return + end + device:set_field(LAST_REPORT_TIME, current_time, { persist = true }) + + -- power consumption report local delta_energy = 0.0 local current_power_consumption = device:get_latest_state("main", capabilities.powerConsumptionReport.ID, capabilities.powerConsumptionReport.powerConsumption.NAME) @@ -82,11 +139,6 @@ local function emit_power_consumption_report_event(device, value) device:emit_event(capabilities.powerConsumptionReport.powerConsumption({ energy = raw_value, deltaEnergy = delta_energy })) -- the unit of these values should be 'Wh' end -local function application_version_handler(driver, device, value, zb_rx) - local version = tonumber(value.value) - device:set_field(APPLICATION_VERSION, version, { persist = true }) -end - local function power_meter_handler(driver, device, value, zb_rx) local raw_value = value.value -- '10W' raw_value = raw_value / 10 @@ -95,43 +147,7 @@ end local function energy_meter_handler(driver, device, value, zb_rx) local raw_value = value.value -- 'Wh' - -- energyMeter - device:emit_event(capabilities.energyMeter.energy({ value = raw_value, unit = "Wh" })) - -- powerConsumptionReport - emit_power_consumption_report_event(device, { value = raw_value }) -end - -local function round(num) - local mult = 10 - return math.floor(num * mult + 0.5) / mult -end - -local function present_value_handler(driver, device, value, zb_rx) - local src_endpoint = zb_rx.address_header.src_endpoint.value - if src_endpoint == ENDPOINT_POWER_METER then - -- powerMeter - local raw_value = value.value -- 'W' - raw_value = round(raw_value) - device:emit_event(capabilities.powerMeter.power({ value = raw_value, unit = "W" })) - elseif src_endpoint == ENDPOINT_ENERGY_METER then - -- energyMeter, powerConsumptionReport - local raw_value = value.value -- 'kWh' - raw_value = round(raw_value * 1000) - - -- check the minimum interval - local current_time = os.time() - local last_time = device:get_field(LAST_REPORT_TIME) or 0 - local next_time = last_time + 60 * 15 -- minimum interval of 15 mins - if current_time < next_time then - return - end - device:set_field(LAST_REPORT_TIME, current_time, { persist = true }) - - -- energyMeter - device:emit_event(capabilities.energyMeter.energy({ value = raw_value, unit = "Wh" })) - -- powerConsumptionReport - emit_power_consumption_report_event(device, { value = raw_value }) - end + energy_meter_power_consumption_report(device, raw_value) end local function do_refresh(self, device) @@ -141,41 +157,41 @@ local function do_refresh(self, device) end local function device_info_changed(driver, device, event, args) - if device.preferences ~= nil then - if device.preferences[MAX_POWER_ID] ~= nil and - device.preferences[MAX_POWER_ID] ~= args.old_st_store.preferences[MAX_POWER_ID] then - local value = tonumber(device.preferences[MAX_POWER_ID]) - device:send(cluster_base.write_manufacturer_specific_attribute(device, PREF_CLUSTER_ID, PREF_MAX_POWER_ATTR_ID, - MFG_CODE, data_types.SinglePrecisionFloat, max_power_data_type_table[value])) - end - - if device.preferences[RESTORE_STATE_ID] ~= nil and - device.preferences[RESTORE_STATE_ID] ~= args.old_st_store.preferences[RESTORE_STATE_ID] then - device:send(cluster_base.write_manufacturer_specific_attribute(device, PREF_CLUSTER_ID, - PREF_RESTORE_STATE_ATTR_ID, MFG_CODE, data_types.Boolean, device.preferences[RESTORE_STATE_ID])) + local preferences = device.preferences + local old_preferences = args.old_st_store.preferences + if preferences ~= nil then + for id, attr in pairs(preference_map) do + local old_value = old_preferences[id] + local value = preferences[id] + if value ~= nil and value ~= old_value then + if attr.value_map ~= nil then + value = attr.value_map[value] + end + device:send(cluster_base.write_manufacturer_specific_attribute(device, attr.cluster_id, attr.attribute_id, + attr.mfg_code, attr.data_type, value)) + end end end end local function do_configure(self, device) device:configure() - device:send(Basic.attributes.ApplicationVersion:read(device)) - device:send(SimpleMetering.attributes.CurrentSummationDelivered:configure_reporting(device, 900, 3600, 1)) + device:send(cluster_base.read_manufacturer_specific_attribute(device, + PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE)) + device:send(Groups.server.commands.RemoveAllGroups(device)) -- required do_refresh(self, device) end local function device_added(driver, device) - device:emit_event(capabilities.switch.switch.off()) device:emit_event(capabilities.powerMeter.power({ value = 0.0, unit = "W" })) device:emit_event(capabilities.energyMeter.energy({ value = 0.0, unit = "Wh" })) - -- Set private attribute - device:send(cluster_base.write_manufacturer_specific_attribute(device, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, - MFG_CODE, data_types.Uint8, 1)) + device:send(cluster_base.write_manufacturer_specific_attribute(device, + PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0x01)) -- private end -local aqara_smart_plug_handler = { - NAME = "Aqara Smart Plug Handler", +local aqara_switch_handler = { + NAME = "Aqara Switch Handler", lifecycle_handlers = { added = device_added, doConfigure = do_configure, @@ -183,27 +199,30 @@ local aqara_smart_plug_handler = { }, capability_handlers = { [capabilities.refresh.ID] = { - [capabilities.refresh.commands.refresh.NAME] = do_refresh, + [capabilities.refresh.commands.refresh.NAME] = do_refresh } }, zigbee_handlers = { attr = { - [Basic.ID] = { - [Basic.attributes.ApplicationVersion.ID] = application_version_handler - }, [ElectricalMeasurement.ID] = { [ElectricalMeasurement.attributes.ActivePower.ID] = power_meter_handler }, [SimpleMetering.ID] = { [SimpleMetering.attributes.CurrentSummationDelivered.ID] = energy_meter_handler }, - [AnalogInput.ID] = { - [AnalogInput.attributes.PresentValue.ID] = present_value_handler + [WIRELESS_SWITCH_CLUSTER_ID] = { + [WIRELESS_SWITCH_ATTRIBUTE_ID] = wireless_switch_handler + }, + [PRIVATE_CLUSTER_ID] = { + [PRIVATE_ATTRIBUTE_ID] = private_mode_handler } } }, - sub_drivers = { require("aqara.aqara_version") }, - can_handle = is_aqara_products, + sub_drivers = { + require("aqara.version"), + require("aqara.multi-switch") + }, + can_handle = is_aqara_products } -return aqara_smart_plug_handler +return aqara_switch_handler diff --git a/zigbee-switch/src/aqara/multi-switch/init.lua b/zigbee-switch/src/aqara/multi-switch/init.lua new file mode 100644 index 0000000..1e75ca8 --- /dev/null +++ b/zigbee-switch/src/aqara/multi-switch/init.lua @@ -0,0 +1,102 @@ +local device_lib = require "st.device" +local capabilities = require "st.capabilities" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" + +local PRIVATE_CLUSTER_ID = 0xFCC0 +local PRIVATE_ATTRIBUTE_ID = 0x0009 +local MFG_CODE = 0x115F + +local FINGERPRINTS = { + { mfr = "LUMI", model = "lumi.switch.n1acn1", children = 1, child_profile = "" }, + { mfr = "LUMI", model = "lumi.switch.n2acn1", children = 2, child_profile = "aqara-switch-child" }, + { mfr = "LUMI", model = "lumi.switch.n3acn1", children = 3, child_profile = "aqara-switch-child" }, +} + +local function is_aqara_products(opts, driver, device) + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true + end + end + return false +end + +local function get_children_amount(device) + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model then + return fingerprint.children + end + end +end + +local function get_child_profile_name(device) + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_model() == fingerprint.model then + return fingerprint.child_profile + end + end +end + +local function find_child(parent, ep_id) + -- Buttons 1-3 report using endpoints 0x29, 0x2A, 0x2B, respectively + if ep_id >= 0x29 then + ep_id = ep_id - 0x28 + end + return parent:get_child_by_parent_assigned_key(string.format("%02X", ep_id)) +end + +local function device_added(driver, device) + -- Only create children for the actual Zigbee device and not the children + if device.network_type == device_lib.NETWORK_TYPE_ZIGBEE then + local children_amount = get_children_amount(device) + if children_amount >= 2 then + for i = 2, children_amount, 1 do + if find_child(device, i) == nil then + local name = string.format("%s%d", string.sub(device.label, 0, -2), i) + local child_profile = get_child_profile_name(device) + local metadata = { + type = "EDGE_CHILD", + label = name, + profile = child_profile, + parent_device_id = device.id, + parent_assigned_child_key = string.format("%02X", i), + vendor_provided_label = name + } + driver:try_create_device(metadata) + end + end + end + + -- for wireless button + device:emit_event(capabilities.button.supportedButtonValues({ "pushed" }, + { visibility = { displayed = false } })) + device:emit_event(capabilities.button.numberOfButtons({ value = children_amount }, + { visibility = { displayed = false } })) + device:emit_event(capabilities.button.button.pushed({ state_change = false })) + + device:emit_event(capabilities.powerMeter.power({ value = 0.0, unit = "W" })) + device:emit_event(capabilities.energyMeter.energy({ value = 0.0, unit = "Wh" })) + + device:send(cluster_base.write_manufacturer_specific_attribute(device, + PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0x01)) -- private + end +end + +local function device_init(self, device) + -- for multiple switch + if device.network_type == device_lib.NETWORK_TYPE_ZIGBEE then + device:set_find_child(find_child) + end +end + +local aqara_multi_switch_handler = { + NAME = "Aqara Multi Switch Handler", + lifecycle_handlers = { + init = device_init, + added = device_added + }, + can_handle = is_aqara_products +} + +return aqara_multi_switch_handler diff --git a/zigbee-switch/src/aqara/version/init.lua b/zigbee-switch/src/aqara/version/init.lua new file mode 100644 index 0000000..41776ab --- /dev/null +++ b/zigbee-switch/src/aqara/version/init.lua @@ -0,0 +1,98 @@ +local capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" + +local OnOff = clusters.OnOff +local AnalogInput = clusters.AnalogInput + +local POWER_METER_ENDPOINT = 0x15 +local ENERGY_METER_ENDPOINT = 0x1F + +local LAST_REPORT_TIME = "LAST_REPORT_TIME" +local PRIVATE_MODE = "PRIVATE_MODE" + +local function on_off_handler(driver, device, value, zb_rx) + device:emit_event_for_endpoint( + zb_rx.address_header.src_endpoint.value, + value.value and capabilities.switch.switch.on() or capabilities.switch.switch.off() + ) + device.thread:call_with_delay(2, function(t) + device:send(AnalogInput.attributes.PresentValue:read(device):to_endpoint(POWER_METER_ENDPOINT)) + end) +end + +local function energy_meter_power_consumption_report(device, raw_value) + -- energy meter + device:emit_event(capabilities.energyMeter.energy({ value = raw_value, unit = "Wh" })) + + -- report interval + local current_time = os.time() + local last_time = device:get_field(LAST_REPORT_TIME) or 0 + local next_time = last_time + 60 * 15 -- 15 mins, the minimum interval allowed between reports + if current_time < next_time then + return + end + device:set_field(LAST_REPORT_TIME, current_time, { persist = true }) + + -- power consumption report + local delta_energy = 0.0 + local current_power_consumption = device:get_latest_state("main", capabilities.powerConsumptionReport.ID, + capabilities.powerConsumptionReport.powerConsumption.NAME) + if current_power_consumption ~= nil then + delta_energy = math.max(raw_value - current_power_consumption.energy, 0.0) + end + device:emit_event(capabilities.powerConsumptionReport.powerConsumption({ energy = raw_value, deltaEnergy = delta_energy })) -- the unit of these values should be 'Wh' +end + +local function round(num) + local mult = 10 + return math.floor(num * mult + 0.5) / mult +end + +local function present_value_handler(driver, device, value, zb_rx) + local src_endpoint = zb_rx.address_header.src_endpoint.value + if src_endpoint == POWER_METER_ENDPOINT then + -- power meter + local raw_value = value.value -- 'W' + raw_value = round(raw_value) + device:emit_event(capabilities.powerMeter.power({ value = raw_value, unit = "W" })) + + -- read energy meter + device:send(AnalogInput.attributes.PresentValue:read(device):to_endpoint(ENERGY_METER_ENDPOINT)) + elseif src_endpoint == ENERGY_METER_ENDPOINT then + -- energy meter, power consumption report + local raw_value = value.value -- 'kWh' + raw_value = round(raw_value * 1000) + energy_meter_power_consumption_report(device, raw_value) + end +end + +local function do_refresh(self, device) + device:send(OnOff.attributes.OnOff:read(device)) + device:send(AnalogInput.attributes.PresentValue:read(device):to_endpoint(POWER_METER_ENDPOINT)) + device:send(AnalogInput.attributes.PresentValue:read(device):to_endpoint(ENERGY_METER_ENDPOINT)) +end + +local aqara_switch_version_handler = { + NAME = "Aqara Switch Version Handler", + capability_handlers = { + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = do_refresh + } + }, + zigbee_handlers = { + attr = { + [AnalogInput.ID] = { + [AnalogInput.attributes.PresentValue.ID] = present_value_handler + }, + [OnOff.ID] = { + [OnOff.attributes.OnOff.ID] = on_off_handler + } + } + }, + can_handle = function (opts, driver, device) + local private_mode = device:get_field(PRIVATE_MODE) or 0 + return private_mode == 1 + end +} + +return aqara_switch_version_handler diff --git a/zigbee-switch/src/init.lua b/zigbee-switch/src/init.lua index bbbdbda..1bdc108 100644 --- a/zigbee-switch/src/init.lua +++ b/zigbee-switch/src/init.lua @@ -19,6 +19,11 @@ local clusters = require "st.zigbee.zcl.clusters" local configurationMap = require "configurations" local SimpleMetering = clusters.SimpleMetering local ElectricalMeasurement = clusters.ElectricalMeasurement +local preferences = require "preferences" + +local function info_changed(self, device, event, args) + preferences.update_preferences(self, device, args) +end local do_configure = function(self, device) device:refresh() @@ -79,6 +84,7 @@ local zigbee_switch_driver_template = { }, sub_drivers = { require("aqara"), + require("aqara-light"), require("ezex"), require("rexense"), require("sinope"), @@ -95,10 +101,12 @@ local zigbee_switch_driver_template = { require("zll-dimmer-bulb"), require("zigbee-switch-power"), require("ge-link-bulb"), - require("bad_on_off_data_type") + require("bad_on_off_data_type"), + require("robb") }, lifecycle_handlers = { init = device_init, + infoChanged = info_changed, doConfigure = do_configure } } diff --git a/zigbee-switch/src/preferences.lua b/zigbee-switch/src/preferences.lua new file mode 100644 index 0000000..20900d5 --- /dev/null +++ b/zigbee-switch/src/preferences.lua @@ -0,0 +1,66 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +local clusters = require "st.zigbee.zcl.clusters" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" + +local devices = { + AQARA_LIGHT = { + MATCHING_MATRIX = { mfr = "LUMI", model = "lumi.light.acn004" }, + PARAMETERS = { + ["stse.restorePowerState"] = function(device, value) + return cluster_base.write_manufacturer_specific_attribute(device, 0xFCC0, + 0x0201, 0x115F, data_types.Boolean, value) + end, + ["stse.turnOffIndicatorLight"] = function(device, value) + return cluster_base.write_manufacturer_specific_attribute(device, 0xFCC0, + 0x0203, 0x115F, data_types.Boolean, value) + end, + ["stse.lightFadeInTimeInSec"] = function(device, value) + local raw_value = value * 10 -- value unit: 1sec, transition time unit: 100ms + return clusters.Level.attributes.OnTransitionTime:write(device, raw_value) + end, + ["stse.lightFadeOutTimeInSec"] = function(device, value) + local raw_value = value * 10 -- value unit: 1sec, transition time unit: 100ms + return clusters.Level.attributes.OffTransitionTime:write(device, raw_value) + end + } + } +} +local preferences = {} + +preferences.update_preferences = function(driver, device, args) + local prefs = preferences.get_device_parameters(device) + if prefs ~= nil then + for id, value in pairs(device.preferences) do + if not (args and args.old_st_store) or (args.old_st_store.preferences[id] ~= value and prefs and prefs[id]) then + local message = prefs[id](device, value) + device:send(message) + end + end + end +end + +preferences.get_device_parameters = function(zigbee_device) + for _, device in pairs(devices) do + if zigbee_device:get_manufacturer() == device.MATCHING_MATRIX.mfr and + zigbee_device:get_model() == device.MATCHING_MATRIX.model then + return device.PARAMETERS + end + end + return nil +end + +return preferences diff --git a/zigbee-switch/src/rgbw-bulb/init.lua b/zigbee-switch/src/rgbw-bulb/init.lua index 7a55105..5ecb3a9 100644 --- a/zigbee-switch/src/rgbw-bulb/init.lua +++ b/zigbee-switch/src/rgbw-bulb/init.lua @@ -109,11 +109,15 @@ local function do_refresh(driver, device) end local function do_configure(driver, device) - device:send(ColorControl.commands.MoveToColorTemperature(device, 200, 0x0000)) device:configure() do_refresh(driver, device) end +-- This is only intended to ever happen once, before the device has a color temp +local function do_added(driver, device) + device:send(ColorControl.commands.MoveToColorTemperature(device, 200, 0x0000)) +end + local function set_color_temperature_handler(driver, device, cmd) switch_defaults.on(driver, device, cmd) local temp_in_mired = math.floor(1000000 / cmd.args.temperature) @@ -135,7 +139,8 @@ local rgbw_bulb = { } }, lifecycle_handlers = { - doConfigure = do_configure + doConfigure = do_configure, + added = do_added }, can_handle = can_handle_rgbw_bulb } diff --git a/zigbee-switch/src/robb/init.lua b/zigbee-switch/src/robb/init.lua new file mode 100644 index 0000000..c3bc6b7 --- /dev/null +++ b/zigbee-switch/src/robb/init.lua @@ -0,0 +1,71 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +local zcl_clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" + +local constants = require "st.zigbee.constants" + +local SimpleMetering = zcl_clusters.SimpleMetering +local ElectricalMeasurement = zcl_clusters.ElectricalMeasurement + +local ROBB_DIMMER_FINGERPRINTS = { + { mfr = "ROBB smarrt", model = "ROB_200-011-0" }, + { mfr = "ROBB smarrt", model = "ROB_200-014-0" } +} + +local function is_robb_dimmer(opts, driver, device) + for _, fingerprint in ipairs(ROBB_DIMMER_FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + return true + end + end + return false +end + +local function energy_meter_handler(driver, device, value, zb_rx) + local raw_value = value.value + local multiplier = device:get_field(constants.SIMPLE_METERING_MULTIPLIER_KEY) or 1 + local divisor = device:get_field(constants.SIMPLE_METERING_DIVISOR_KEY) or 1000000 + + raw_value = raw_value * multiplier / divisor + device:emit_event(capabilities.energyMeter.energy({ value = raw_value, unit = "kWh" })) +end + +local function power_meter_handler(driver, device, value, zb_rx) + local raw_value = value.value + local multiplier = device:get_field(constants.ELECTRICAL_MEASUREMENT_MULTIPLIER_KEY) or 1 + local divisor = device:get_field(constants.ELECTRICAL_MEASUREMENT_DIVISOR_KEY) or 10 + + raw_value = raw_value * multiplier / divisor + device:emit_event(capabilities.powerMeter.power({ value = raw_value, unit = "W" })) +end + +local robb_dimmer_handler = { + NAME = "ROBB smarrt dimmer", + zigbee_handlers = { + attr = { + [SimpleMetering.ID] = { + [SimpleMetering.attributes.InstantaneousDemand.ID] = power_meter_handler, + [SimpleMetering.attributes.CurrentSummationDelivered.ID] = energy_meter_handler + }, + [ElectricalMeasurement.ID] = { + [ElectricalMeasurement.attributes.ActivePower.ID] = power_meter_handler + } + } + }, + can_handle = is_robb_dimmer +} + +return robb_dimmer_handler diff --git a/zigbee-switch/src/test/test_aqara_light.lua b/zigbee-switch/src/test/test_aqara_light.lua new file mode 100644 index 0000000..0a6b63f --- /dev/null +++ b/zigbee-switch/src/test/test_aqara_light.lua @@ -0,0 +1,192 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local clusters = require "st.zigbee.zcl.clusters" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" + +local OnOff = clusters.OnOff +local Level = clusters.Level +local ColorControl = clusters.ColorControl + +local PRIVATE_CLUSTER_ID = 0xFCC0 +local PRIVATE_ATTRIBUTE_ID = 0x0009 +local MFG_CODE = 0x115F + +local RESTORE_POWER_STATE_ATTRIBUTE_ID = 0x0201 +local TURN_OFF_INDICATOR_ATTRIBUTE_ID = 0x0203 + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("aqara-light.yml"), + fingerprinted_endpoint_id = 0x01, + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "LUMI", + model = "lumi.light.acn004", + server_clusters = { 0x0006, 0x0008, 0x0300 } + } + } + } +) + +zigbee_test_utils.prepare_zigbee_env_info() + +local function test_init() + test.mock_device.add_test_device(mock_device) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Handle added lifecycle", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + + test.socket.zigbee:__expect_send( + { + mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID + , MFG_CODE, data_types.Uint8, 1) + } + ) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OnTransitionTime:write(mock_device, 0) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OffTransitionTime:write(mock_device, 0) }) + end +) + +test.register_coroutine_test( + "Configure should configure all necessary attributes and refresh device", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + test.socket.zigbee:__set_channel_ordering("relaxed") + + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.commands.MoveToColorTemperature(mock_device, 200, 0) + } + ) + + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, Level.ID) + }) + test.socket.zigbee:__expect_send( + { + mock_device.id, + Level.attributes.CurrentLevel:configure_reporting(mock_device, 1, 3600, 1) + } + ) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ColorControl.ID) + }) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:configure_reporting(mock_device, 1, 3600, 16) + } + ) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, OnOff.ID) + }) + test.socket.zigbee:__expect_send( + { + mock_device.id, + OnOff.attributes.OnOff:configure_reporting(mock_device, 0, 300, 1) + } + ) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.register_coroutine_test( + "Set Color Temperature command test", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "colorTemperature", component = "main", command = "setColorTemperature", args = { 200 } } }) + + local temp_in_mired = math.floor(1000000 / 200) + test.socket.zigbee:__expect_send( + { + mock_device.id, + OnOff.commands.On(mock_device) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.commands.MoveToColorTemperature(mock_device, temp_in_mired, 0x0000) + } + ) + end +) + +test.register_coroutine_test( + "Handle restorePowerState in infochanged", + function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { ["stse.restorePowerState"] = true } + })) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + RESTORE_POWER_STATE_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) + end +) + +test.register_coroutine_test( + "Handle turnOffIndicatorLight in infochanged", + function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { ["stse.turnOffIndicatorLight"] = true } + })) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + TURN_OFF_INDICATOR_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) + end +) + +test.register_coroutine_test( + "Handle lightFadeInTimeInSec in infochanged", + function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { ["stse.lightFadeInTimeInSec"] = 1 } + })) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OnTransitionTime:write(mock_device, 10) }) + end +) + +test.register_coroutine_test( + "Handle lightFadeOutTimeInSec in infochanged", + function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { ["stse.lightFadeOutTimeInSec"] = 1 } + })) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OffTransitionTime:write(mock_device, 10) }) + end +) + +test.run_registered_tests() diff --git a/zigbee-switch/src/test/test_aqara_smart_plug.lua b/zigbee-switch/src/test/test_aqara_smart_plug.lua index 91120be..0cc7e66 100644 --- a/zigbee-switch/src/test/test_aqara_smart_plug.lua +++ b/zigbee-switch/src/test/test_aqara_smart_plug.lua @@ -12,13 +12,11 @@ -- See the License for the specific language governing permissions and -- limitations under the License. -local base64 = require "st.base64" local clusters = require "st.zigbee.zcl.clusters" local cluster_base = require "st.zigbee.cluster_base" local data_types = require "st.zigbee.data_types" local capabilities = require "st.capabilities" local SinglePrecisionFloat = require "st.zigbee.data_types".SinglePrecisionFloat - local t_utils = require "integration_test.utils" local zigbee_test_utils = require "integration_test.zigbee_test_utils" local test = require "integration_test" @@ -28,20 +26,19 @@ local SimpleMetering = clusters.SimpleMetering local ElectricalMeasurement = clusters.ElectricalMeasurement local AnalogInput = clusters.AnalogInput local Basic = clusters.Basic +local Groups = clusters.Groups local MFG_CODE = 0x115F local PRIVATE_CLUSTER_ID = 0xFCC0 local PRIVATE_ATTRIBUTE_ID = 0x0009 +local RESTORE_POWER_STATE_ATTRIBUTE_ID = 0x0201 +local MAX_POWER_ATTRIBUTE_ID = 0x020B -local PREF_CLUSTER_ID = 0xFCC0 -local PREF_MAX_POWER_ATTR_ID = 0x020B -local PREF_RESTORE_STATE_ATTR_ID = 0x0201 - -local ENDPOINT_POWER_METER = 0x15 -local ENDPOINT_ENERGY_METER = 0x1F +local POWER_METER_ENDPOINT = 0x15 +local ENERGY_METER_ENDPOINT = 0x1F local LAST_REPORT_TIME = "LAST_REPORT_TIME" -local APPLICATION_VERSION = "application_version" +local PRIVATE_MODE = "PRIVATE_MODE" local mock_device = test.mock_device.build_test_zigbee_device( { @@ -51,13 +48,13 @@ local mock_device = test.mock_device.build_test_zigbee_device( id = 1, manufacturer = "LUMI", model = "lumi.plug.maeu01", - server_clusters = { OnOff.ID, SimpleMetering.ID, ElectricalMeasurement.ID } + server_clusters = { OnOff.ID, AnalogInput.ID } } } } ) -local mock_device_version = test.mock_device.build_test_zigbee_device( +local mock_standard = test.mock_device.build_test_zigbee_device( { profile = t_utils.get_profile_definition("switch-power-energy-consumption-report-aqara.yml"), zigbee_endpoints = { @@ -65,7 +62,7 @@ local mock_device_version = test.mock_device.build_test_zigbee_device( id = 1, manufacturer = "LUMI", model = "lumi.plug.maeu01", - server_clusters = { OnOff.ID, AnalogInput.ID } + server_clusters = { OnOff.ID, SimpleMetering.ID, ElectricalMeasurement.ID } } } } @@ -74,7 +71,7 @@ local mock_device_version = test.mock_device.build_test_zigbee_device( zigbee_test_utils.prepare_zigbee_env_info() local function test_init() test.mock_device.add_test_device(mock_device) - test.mock_device.add_test_device(mock_device_version) + test.mock_device.add_test_device(mock_standard) zigbee_test_utils.init_noop_health_check_timer() end @@ -84,9 +81,6 @@ test.register_coroutine_test( "Handle added lifecycle", function() test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.switch.switch.off()) - ) test.socket.capability:__expect_send( mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 0.0, unit = "W" })) ) @@ -96,8 +90,8 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send( { mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, - MFG_CODE, data_types.Uint8, 1) + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID + , MFG_CODE, data_types.Uint8, 1) } ) end @@ -107,48 +101,24 @@ test.register_coroutine_test( "Handle doConfigure lifecycle", function() test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) - test.socket.zigbee:__set_channel_ordering("relaxed") - test.socket.zigbee:__expect_send({ - mock_device.id, - zigbee_test_utils.build_bind_request(mock_device, - zigbee_test_utils.mock_hub_eui, - ElectricalMeasurement.ID) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - ElectricalMeasurement.attributes.ActivePower:configure_reporting(mock_device, 1, 3600, 5) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - zigbee_test_utils.build_bind_request(mock_device, - zigbee_test_utils.mock_hub_eui, - SimpleMetering.ID) - }) test.socket.zigbee:__expect_send({ mock_device.id, - SimpleMetering.attributes.InstantaneousDemand:configure_reporting(mock_device, 1, 3600, 5) + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, OnOff.ID) }) test.socket.zigbee:__expect_send({ mock_device.id, - SimpleMetering.attributes.CurrentSummationDelivered:configure_reporting(mock_device, 5, 3600, 1) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - SimpleMetering.attributes.CurrentSummationDelivered:configure_reporting(mock_device, 900, 3600, 1) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - zigbee_test_utils.build_bind_request(mock_device, - zigbee_test_utils.mock_hub_eui, - OnOff.ID) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - OnOff.attributes.OnOff:configure_reporting(mock_device, 0, 300) + OnOff.attributes.OnOff:configure_reporting(mock_device, 0, 300, 1) }) + test.socket.zigbee:__expect_send( + { + mock_device.id, + cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID + , MFG_CODE) + } + ) test.socket.zigbee:__expect_send({ mock_device.id, - Basic.attributes.ApplicationVersion:read(mock_device) + Groups.server.commands.RemoveAllGroups(mock_device) }) test.socket.zigbee:__expect_send({ mock_device.id, @@ -166,270 +136,212 @@ test.register_coroutine_test( end ) -test.register_message_test( - "Capability command On should be handled", - { - { - channel = "capability", - direction = "receive", - message = { mock_device.id, { capability = "switch", component = "main", command = "on", args = {} } } - }, - { - channel = "zigbee", - direction = "send", - message = { mock_device.id, OnOff.server.commands.On(mock_device) } - } - } +test.register_coroutine_test( + "Refresh device should read all necessary attributes", + function() + mock_device:set_field(PRIVATE_MODE, 1, { persist = true }) + + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "refresh", component = "main", command = "refresh", args = {} } }) + test.socket.zigbee:__expect_send({ mock_device.id, + OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, + AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) + test.socket.zigbee:__expect_send({ mock_device.id, + AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(ENERGY_METER_ENDPOINT) }) + end ) -test.register_message_test( - "Capability command Off should be handled", - { - { - channel = "capability", - direction = "receive", - message = { mock_device.id, { capability = "switch", component = "main", command = "off", args = {} } } - }, - { - channel = "zigbee", - direction = "send", - message = { mock_device.id, OnOff.server.commands.Off(mock_device) } - } - } +test.register_coroutine_test( + "Reported on status should be handled", + function() + mock_device:set_field(PRIVATE_MODE, 1, { persist = true }) + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.zigbee:__queue_receive({ mock_device.id, + OnOff.attributes.OnOff:build_test_attr_report(mock_device, true):from_endpoint(0x01) }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.on())) + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ mock_device.id, + AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) + end ) test.register_coroutine_test( - "On attribute handled", + "Reported off status should be handled", function() - test.socket.zigbee:__queue_receive( - { - mock_device.id, - OnOff.attributes.OnOff:build_test_attr_report(mock_device, true) - } - ) - test.socket.capability:__set_channel_ordering("relaxed") - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.switch.switch.on()) - ) + mock_device:set_field(PRIVATE_MODE, 1, { persist = true }) + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.zigbee:__queue_receive({ mock_device.id, + OnOff.attributes.OnOff:build_test_attr_report(mock_device, false):from_endpoint(0x01) }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ mock_device.id, + AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) end ) test.register_coroutine_test( - "Off attribute handled", + "Capability on command should be handled", function() - test.socket.zigbee:__queue_receive( - { - mock_device.id, - OnOff.attributes.OnOff:build_test_attr_report(mock_device, false) - } - ) - test.socket.capability:__set_channel_ordering("relaxed") - test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.switch.switch.off()) - ) + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "switch", component = "main", command = "on", args = {} } }) + test.socket.zigbee:__expect_send({ mock_device.id, + OnOff.server.commands.On(mock_device) }) + end +) + +test.register_coroutine_test( + "Capability off command should be handled", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "switch", component = "main", command = "off", args = {} } }) + test.socket.zigbee:__expect_send({ mock_device.id, + OnOff.server.commands.Off(mock_device) }) end ) test.register_coroutine_test( - "Handle power meter", + "Power meter handled", function() + mock_device:set_field(PRIVATE_MODE, 1, { persist = true }) + test.socket.zigbee:__queue_receive({ mock_device.id, - SimpleMetering.attributes.InstantaneousDemand:build_test_attr_report(mock_device, 10) + AnalogInput.attributes.PresentValue:build_test_attr_report(mock_device, + SinglePrecisionFloat(0, 9, 0.953125)):from_endpoint(POWER_METER_ENDPOINT) }) test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 10000.0, unit = "W" })) + mock_device:generate_test_message("main", + capabilities.powerMeter.power({ value = 1000.0, unit = "W" })) ) + test.socket.zigbee:__expect_send({ mock_device.id, + AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(ENERGY_METER_ENDPOINT) }) end ) test.register_coroutine_test( - "Handle energy meter", + "Energy meter handled", function() + mock_device:set_field(PRIVATE_MODE, 1, { persist = true }) + + test.socket.zigbee:__queue_receive( + { + mock_device.id, + Basic.attributes.ApplicationVersion:build_test_attr_report(mock_device, 32) + } + ) + test.wait_for_events() + local current_time = os.time() - 60 * 20 mock_device:set_field(LAST_REPORT_TIME, current_time) test.socket.zigbee:__queue_receive({ mock_device.id, - SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 10) + AnalogInput.attributes.PresentValue:build_test_attr_report(mock_device, + SinglePrecisionFloat(0, 9, 0.953125)):from_endpoint(ENERGY_METER_ENDPOINT) }) test.socket.capability:__expect_send( - mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 10, unit = "Wh" })) + mock_device:generate_test_message("main", + capabilities.energyMeter.energy({ value = 1000000.0, unit = "Wh" })) ) test.socket.capability:__expect_send( mock_device:generate_test_message("main", - capabilities.powerConsumptionReport.powerConsumption({ deltaEnergy = 0.0, energy = 10 })) + capabilities.powerConsumptionReport.powerConsumption({ deltaEnergy = 0.0, energy = 1000000.0 })) ) end ) test.register_coroutine_test( - "Handle maxPower in infochanged", + "Handle restorePowerState in infochanged", function() - test.socket.environment_update:__queue_receive({ "zigbee", - { hub_zigbee_id = base64.encode(zigbee_test_utils.mock_hub_eui) } }) - local updates = { - preferences = { - } - } - updates.preferences["stse.maxPower"] = 23 - test.wait_for_events() - test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed(updates)) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PREF_CLUSTER_ID, PREF_MAX_POWER_ATTR_ID, - MFG_CODE, - data_types.SinglePrecisionFloat, - SinglePrecisionFloat(0, 11, 0.123046875)) }) - updates.preferences["stse.maxPower"] = 1 - test.wait_for_events() - test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed(updates)) + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { ["stse.restorePowerState"] = true } + })) test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PREF_CLUSTER_ID, PREF_MAX_POWER_ATTR_ID, - MFG_CODE, - data_types.SinglePrecisionFloat, - SinglePrecisionFloat(0, 6, 0.5625)) }) + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + RESTORE_POWER_STATE_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) end ) test.register_coroutine_test( - "Handle restorePowerState in infochanged", + "Handle maxPower in infochanged", function() - test.socket.environment_update:__queue_receive({ "zigbee", - { hub_zigbee_id = base64.encode(zigbee_test_utils.mock_hub_eui) } }) - local updates = { - preferences = { - } - } - updates.preferences["stse.restorePowerState"] = true - test.wait_for_events() - test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed(updates)) + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { ["stse.maxPower"] = "1" } + })) test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PREF_CLUSTER_ID, - PREF_RESTORE_STATE_ATTR_ID, MFG_CODE, - data_types.Boolean, - true) }) - updates.preferences["stse.restorePowerState"] = false - test.wait_for_events() - test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed(updates)) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PREF_CLUSTER_ID, - PREF_RESTORE_STATE_ATTR_ID, MFG_CODE, - data_types.Boolean, - false) }) + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, MAX_POWER_ATTRIBUTE_ID, + MFG_CODE, data_types.SinglePrecisionFloat, SinglePrecisionFloat(0, 6, 0.5625)) }) end ) --- mock_device_version +-- with standard cluster test.register_coroutine_test( - "On attribute handled with application version handler", + "Refresh device should read all necessary attributes with standard cluster", function() - test.socket.zigbee:__queue_receive( - { - mock_device_version.id, - Basic.attributes.ApplicationVersion:build_test_attr_report(mock_device_version, 32) - } - ) - mock_device_version:set_field(APPLICATION_VERSION, 32, { persist = true }) - test.wait_for_events() + mock_standard:set_field(PRIVATE_MODE, 0, { persist = true }) test.socket.zigbee:__queue_receive( { - mock_device_version.id, - OnOff.attributes.OnOff:build_test_attr_report(mock_device_version, true) - } - ) - test.socket.capability:__set_channel_ordering("relaxed") - test.socket.capability:__expect_send( - mock_device_version:generate_test_message("main", capabilities.switch.switch.on()) - ) - test.socket.zigbee:__expect_send({ - mock_device_version.id, - AnalogInput.attributes.PresentValue:read(mock_device_version):to_endpoint(ENDPOINT_POWER_METER) - }) - test.socket.zigbee:__expect_send({ - mock_device_version.id, - AnalogInput.attributes.PresentValue:read(mock_device_version):to_endpoint(ENDPOINT_ENERGY_METER) - }) - end -) - -test.register_coroutine_test( - "Off attribute handled with application version handler", - function() - test.socket.zigbee:__queue_receive( - { - mock_device_version.id, - Basic.attributes.ApplicationVersion:build_test_attr_report(mock_device_version, 32) + mock_standard.id, + Basic.attributes.ApplicationVersion:build_test_attr_report(mock_standard, 41) } ) - mock_device_version:set_field(APPLICATION_VERSION, 32, { persist = true }) test.wait_for_events() - test.socket.zigbee:__queue_receive( - { - mock_device_version.id, - OnOff.attributes.OnOff:build_test_attr_report(mock_device_version, false) - } - ) - test.socket.capability:__set_channel_ordering("relaxed") - test.socket.capability:__expect_send( - mock_device_version:generate_test_message("main", capabilities.switch.switch.off()) - ) + test.socket.capability:__queue_receive({ mock_standard.id, + { capability = "refresh", component = "main", command = "refresh", args = {} } }) + test.socket.zigbee:__expect_send({ mock_standard.id, + OnOff.attributes.OnOff:read(mock_standard) }) + test.socket.zigbee:__expect_send({ mock_standard.id, + ElectricalMeasurement.attributes.ActivePower:read(mock_standard) }) + test.socket.zigbee:__expect_send({ mock_standard.id, + SimpleMetering.attributes.CurrentSummationDelivered:read(mock_standard) }) end ) test.register_coroutine_test( - "Power meter handled with application version handler", + "Handle power meter with standard cluster", function() + mock_standard:set_field(PRIVATE_MODE, 0, { persist = true }) + test.socket.zigbee:__queue_receive( { - mock_device_version.id, - Basic.attributes.ApplicationVersion:build_test_attr_report(mock_device_version, 32) + mock_standard.id, + Basic.attributes.ApplicationVersion:build_test_attr_report(mock_standard, 41) } ) - mock_device_version:set_field(APPLICATION_VERSION, 32, { persist = true }) test.wait_for_events() test.socket.zigbee:__queue_receive({ - mock_device_version.id, - AnalogInput.attributes.PresentValue:build_test_attr_report(mock_device_version, - SinglePrecisionFloat(0, 9, 0.953125)):from_endpoint(ENDPOINT_POWER_METER) + mock_standard.id, + ElectricalMeasurement.attributes.ActivePower:build_test_attr_report(mock_standard, 100) }) test.socket.capability:__expect_send( - mock_device_version:generate_test_message("main", - capabilities.powerMeter.power({ value = 1000.0, unit = "W" })) + mock_standard:generate_test_message("main", capabilities.powerMeter.power({ value = 10.0, unit = "W" })) ) end ) test.register_coroutine_test( - "Energy meter handled with application version handler", + "Handle energy meter with standard cluster", function() - test.socket.zigbee:__queue_receive( - { - mock_device_version.id, - Basic.attributes.ApplicationVersion:build_test_attr_report(mock_device_version, 32) - } - ) - mock_device_version:set_field(APPLICATION_VERSION, 32, { persist = true }) - test.wait_for_events() + mock_standard:set_field(PRIVATE_MODE, 0, { persist = true }) local current_time = os.time() - 60 * 20 - mock_device_version:set_field(LAST_REPORT_TIME, current_time) + mock_standard:set_field(LAST_REPORT_TIME, current_time) test.socket.zigbee:__queue_receive({ - mock_device_version.id, - AnalogInput.attributes.PresentValue:build_test_attr_report(mock_device_version, - SinglePrecisionFloat(0, 9, 0.953125)):from_endpoint(ENDPOINT_ENERGY_METER) + mock_standard.id, + SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_standard, 10) }) test.socket.capability:__expect_send( - mock_device_version:generate_test_message("main", - capabilities.energyMeter.energy({ value = 1000000.0, unit = "Wh" })) + mock_standard:generate_test_message("main", capabilities.energyMeter.energy({ value = 10, unit = "Wh" })) ) test.socket.capability:__expect_send( - mock_device_version:generate_test_message("main", - capabilities.powerConsumptionReport.powerConsumption({ deltaEnergy = 0.0, energy = 1000000.0 })) + mock_standard:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ deltaEnergy = 0.0, energy = 10 })) ) end ) diff --git a/zigbee-switch/src/test/test_aqara_switch_module.lua b/zigbee-switch/src/test/test_aqara_switch_module.lua new file mode 100644 index 0000000..3d9c9cc --- /dev/null +++ b/zigbee-switch/src/test/test_aqara_switch_module.lua @@ -0,0 +1,196 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +local clusters = require "st.zigbee.zcl.clusters" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" +local capabilities = require "st.capabilities" +local SinglePrecisionFloat = require "st.zigbee.data_types".SinglePrecisionFloat +local t_utils = require "integration_test.utils" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local test = require "integration_test" + +local OnOff = clusters.OnOff +local AnalogInput = clusters.AnalogInput + +local MFG_CODE = 0x115F +local PRIVATE_CLUSTER_ID = 0xFCC0 +local PRIVATE_ATTRIBUTE_ID = 0x0009 +local RESTORE_POWER_STATE_ATTRIBUTE_ID = 0x0201 +local ELECTRIC_SWITCH_TYPE_ATTRIBUTE_ID = 0x000A + +local POWER_METER_ENDPOINT = 0x15 +local ENERGY_METER_ENDPOINT = 0x1F + +local LAST_REPORT_TIME = "LAST_REPORT_TIME" +local PRIVATE_MODE = "PRIVATE_MODE" + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("aqara-switch-module.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "LUMI", + model = "lumi.switch.n0agl1", + server_clusters = { OnOff.ID, AnalogInput.ID } + } + } + } +) + +zigbee_test_utils.prepare_zigbee_env_info() +local function test_init() + test.mock_device.add_test_device(mock_device) + zigbee_test_utils.init_noop_health_check_timer() +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Handle added lifecycle", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 0.0, unit = "W" })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", capabilities.energyMeter.energy({ value = 0.0, unit = "Wh" })) + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID + , MFG_CODE, data_types.Uint8, 1) + } + ) + end +) + +test.register_coroutine_test( + "Reported on status should be handled", + function() + mock_device:set_field(PRIVATE_MODE, 1, { persist = true }) + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.zigbee:__queue_receive({ mock_device.id, + OnOff.attributes.OnOff:build_test_attr_report(mock_device, true):from_endpoint(0x01) }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.on())) + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ mock_device.id, + AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) + end +) + +test.register_coroutine_test( + "Reported off status should be handled", + function() + mock_device:set_field(PRIVATE_MODE, 1, { persist = true }) + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.zigbee:__queue_receive({ mock_device.id, + OnOff.attributes.OnOff:build_test_attr_report(mock_device, false):from_endpoint(0x01) }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ mock_device.id, + AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) + end +) + +test.register_coroutine_test( + "Capability on command should be handled", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "switch", component = "main", command = "on", args = {} } }) + test.socket.zigbee:__expect_send({ mock_device.id, + OnOff.server.commands.On(mock_device) }) + end +) + +test.register_coroutine_test( + "Capability off command should be handled", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "switch", component = "main", command = "off", args = {} } }) + test.socket.zigbee:__expect_send({ mock_device.id, + OnOff.server.commands.Off(mock_device) }) + end +) + +test.register_coroutine_test( + "Power meter handled", + function() + mock_device:set_field(PRIVATE_MODE, 1, { persist = true }) + + test.socket.zigbee:__queue_receive({ + mock_device.id, + AnalogInput.attributes.PresentValue:build_test_attr_report(mock_device, + SinglePrecisionFloat(0, 9, 0.953125)):from_endpoint(POWER_METER_ENDPOINT) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.powerMeter.power({ value = 1000.0, unit = "W" })) + ) + test.socket.zigbee:__expect_send({ mock_device.id, + AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(ENERGY_METER_ENDPOINT) }) + end +) + +test.register_coroutine_test( + "Energy meter handled", + function() + mock_device:set_field(PRIVATE_MODE, 1, { persist = true }) + + local current_time = os.time() - 60 * 20 + mock_device:set_field(LAST_REPORT_TIME, current_time) + + test.socket.zigbee:__queue_receive({ + mock_device.id, + AnalogInput.attributes.PresentValue:build_test_attr_report(mock_device, + SinglePrecisionFloat(0, 9, 0.953125)):from_endpoint(ENERGY_METER_ENDPOINT) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.energyMeter.energy({ value = 1000000.0, unit = "Wh" })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ deltaEnergy = 0.0, energy = 1000000.0 })) + ) + end +) + +test.register_coroutine_test( + "Handle restorePowerState in infochanged", + function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { ["stse.restorePowerState"] = true } + })) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + RESTORE_POWER_STATE_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) + end +) + +test.register_coroutine_test( + "Handle electricSwitchType in infochanged", + function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { ["stse.electricSwitchType"] = 'rocker' } + })) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + ELECTRIC_SWITCH_TYPE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 1) }) + end +) + +test.run_registered_tests() diff --git a/zigbee-switch/src/test/test_aqara_switch_power.lua b/zigbee-switch/src/test/test_aqara_switch_power.lua new file mode 100644 index 0000000..8b01b3c --- /dev/null +++ b/zigbee-switch/src/test/test_aqara_switch_power.lua @@ -0,0 +1,280 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local clusters = require "st.zigbee.zcl.clusters" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" +local capabilities = require "st.capabilities" +local SinglePrecisionFloat = require "st.zigbee.data_types".SinglePrecisionFloat +local zigbee_test_utils = require "integration_test.zigbee_test_utils" + +local OnOff = clusters.OnOff +local AnalogInput = clusters.AnalogInput + +local PRIVATE_CLUSTER_ID = 0xFCC0 +local MFG_CODE = 0x115F +local RESTORE_POWER_STATE_ATTRIBUTE_ID = 0x0201 +local CHANGE_TO_WIRELESS_SWITCH_ATTRIBUTE_ID = 0x0200 +local POWER_METER_ENDPOINT = 0x15 +local ENERGY_METER_ENDPOINT = 0x1F +local WIRELESS_SWITCH_CLUSTER_ID = 0x0012 +local WIRELESS_SWITCH_ATTRIBUTE_ID = 0x0055 +local WIRELESS_SWITCH_PUSHED_VALUE = 1 +local BUTTON_1_ENDPOINT = 0x29 +local BUTTON_2_ENDPOINT = 0x2A + +local LAST_REPORT_TIME = "LAST_REPORT_TIME" +local PRIVATE_MODE = "PRIVATE_MODE" + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("aqara-switch-power.yml"), + fingerprinted_endpoint_id = 0x01, + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "LUMI", + model = "lumi.switch.n3acn1", + server_clusters = { 0x0006 } + } + } + } +) + +local mock_child = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("aqara-switch-child.yml"), + device_network_id = string.format("%04X:%02X", mock_device:get_short_address(), 2), + parent_device_id = mock_device.id, + parent_assigned_child_key = string.format("%02X", 2) +}) + +zigbee_test_utils.prepare_zigbee_env_info() + +local function test_init() + test.mock_device.add_test_device(mock_device) + test.mock_device.add_test_device(mock_child) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Refresh device should read all necessary attributes", + function() + mock_device:set_field(PRIVATE_MODE, 1, { persist = true }) + + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "refresh", component = "main", command = "refresh", args = {} } }) + test.socket.zigbee:__expect_send({ mock_device.id, + OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, + AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) + test.socket.zigbee:__expect_send({ mock_device.id, + AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(ENERGY_METER_ENDPOINT) }) + end +) + +test.register_coroutine_test( + "Reported on status should be handled : parent device", + function() + mock_device:set_field(PRIVATE_MODE, 1, { persist = true }) + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.zigbee:__queue_receive({ mock_device.id, + OnOff.attributes.OnOff:build_test_attr_report(mock_device, true):from_endpoint(0x01) }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.on())) + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ mock_device.id, + AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) + end +) + +test.register_coroutine_test( + "Reported on status should be handled : child device", + function() + mock_device:set_field(PRIVATE_MODE, 1, { persist = true }) + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.zigbee:__queue_receive({ mock_device.id, + OnOff.attributes.OnOff:build_test_attr_report(mock_device, true):from_endpoint(0x02) }) + test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.switch.switch.on())) + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ mock_device.id, + AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) + end +) + +test.register_coroutine_test( + "Reported off status should be handled by parent device", + function() + mock_device:set_field(PRIVATE_MODE, 1, { persist = true }) + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.zigbee:__queue_receive({ mock_device.id, + OnOff.attributes.OnOff:build_test_attr_report(mock_device, false):from_endpoint(0x01) }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.switch.switch.off())) + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ mock_device.id, + AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) + end +) + +test.register_coroutine_test( + "Reported off status should be handled by child device", + function() + mock_device:set_field(PRIVATE_MODE, 1, { persist = true }) + test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot") + test.socket.zigbee:__queue_receive({ mock_device.id, + OnOff.attributes.OnOff:build_test_attr_report(mock_device, false):from_endpoint(0x02) }) + test.socket.capability:__expect_send(mock_child:generate_test_message("main", capabilities.switch.switch.off())) + test.mock_time.advance_time(2) + test.socket.zigbee:__expect_send({ mock_device.id, + AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(POWER_METER_ENDPOINT) }) + end +) + +test.register_coroutine_test( + "Capability on command should be handled : parent device", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "switch", component = "main", command = "on", args = {} } }) + test.socket.zigbee:__expect_send({ mock_device.id, + OnOff.server.commands.On(mock_device) }) + end +) + +test.register_coroutine_test( + "Capability on command should be handled : child device", + function() + test.socket.capability:__queue_receive({ mock_child.id, + { capability = "switch", component = "main", command = "on", args = {} } }) + test.socket.zigbee:__expect_send({ mock_device.id, + OnOff.server.commands.On(mock_device):to_endpoint(0x02) }) + end +) + +test.register_coroutine_test( + "Capability off command should be handled : parent device", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "switch", component = "main", command = "off", args = {} } }) + test.socket.zigbee:__expect_send({ mock_device.id, + OnOff.server.commands.Off(mock_device) }) + end +) + +test.register_coroutine_test( + "Capability off command should be handled : child device", + function() + test.socket.capability:__queue_receive({ mock_child.id, + { capability = "switch", component = "main", command = "off", args = {} } }) + test.socket.zigbee:__expect_send({ mock_device.id, + OnOff.server.commands.Off(mock_device):to_endpoint(0x02) }) + end +) + +test.register_coroutine_test( + "Wireless button pushed report should be correctly handled : parent device", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, WIRELESS_SWITCH_CLUSTER_ID, { + { WIRELESS_SWITCH_ATTRIBUTE_ID, data_types.Uint16.ID, WIRELESS_SWITCH_PUSHED_VALUE } + }, MFG_CODE):from_endpoint(BUTTON_1_ENDPOINT) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.button.button.pushed({ state_change = true }))) + end +) + +test.register_coroutine_test( + "Wireless button pushed report should be correctly handled : child device", + function() + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, WIRELESS_SWITCH_CLUSTER_ID, { + { WIRELESS_SWITCH_ATTRIBUTE_ID, data_types.Uint16.ID, WIRELESS_SWITCH_PUSHED_VALUE } + }, MFG_CODE):from_endpoint(BUTTON_2_ENDPOINT) + }) + test.socket.capability:__expect_send(mock_child:generate_test_message("main", + capabilities.button.button.pushed({ state_change = true }))) + end +) + +test.register_coroutine_test( + "Power meter handled", + function() + mock_device:set_field(PRIVATE_MODE, 1, { persist = true }) + + test.socket.zigbee:__queue_receive({ + mock_device.id, + AnalogInput.attributes.PresentValue:build_test_attr_report(mock_device, + SinglePrecisionFloat(0, 9, 0.953125)):from_endpoint(POWER_METER_ENDPOINT) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.powerMeter.power({ value = 1000.0, unit = "W" })) + ) + test.socket.zigbee:__expect_send({ mock_device.id, + AnalogInput.attributes.PresentValue:read(mock_device):to_endpoint(ENERGY_METER_ENDPOINT) }) + end +) + +test.register_coroutine_test( + "Energy meter handled", + function() + mock_device:set_field(PRIVATE_MODE, 1, { persist = true }) + + local current_time = os.time() - 60 * 20 + mock_device:set_field(LAST_REPORT_TIME, current_time) + + test.socket.zigbee:__queue_receive({ + mock_device.id, + AnalogInput.attributes.PresentValue:build_test_attr_report(mock_device, + SinglePrecisionFloat(0, 9, 0.953125)):from_endpoint(ENERGY_METER_ENDPOINT) + }) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.energyMeter.energy({ value = 1000000.0, unit = "Wh" })) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message("main", + capabilities.powerConsumptionReport.powerConsumption({ deltaEnergy = 0.0, energy = 1000000.0 })) + ) + end +) + +test.register_coroutine_test( + "Handle restorePowerState in infochanged", + function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { ["stse.restorePowerState"] = true } + })) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + RESTORE_POWER_STATE_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) + end +) + +test.register_coroutine_test( + "Handle changeToWirelessSwitch in infochanged", + function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { ["stse.changeToWirelessSwitch"] = true } + })) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + CHANGE_TO_WIRELESS_SWITCH_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0) }) + end +) + +test.run_registered_tests() diff --git a/zigbee-switch/src/test/test_rgbw_bulb.lua b/zigbee-switch/src/test/test_rgbw_bulb.lua index 756fe02..32e8956 100644 --- a/zigbee-switch/src/test/test_rgbw_bulb.lua +++ b/zigbee-switch/src/test/test_rgbw_bulb.lua @@ -62,12 +62,6 @@ test.register_coroutine_test( zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ColorControl.ID) }) - test.socket.zigbee:__expect_send( - { - mock_device.id, - ColorControl.commands.MoveToColorTemperature(mock_device, 200, 0) - } - ) test.socket.zigbee:__expect_send( { mock_device.id, @@ -256,4 +250,17 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "Added lifecycle event should set color temperature", + function () + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.commands.MoveToColorTemperature(mock_device, 200, 0) + } + ) + end +) + test.run_registered_tests() diff --git a/zigbee-switch/src/test/test_robb_smarrt_2-wire_dimmer.lua b/zigbee-switch/src/test/test_robb_smarrt_2-wire_dimmer.lua new file mode 100644 index 0000000..39f8a49 --- /dev/null +++ b/zigbee-switch/src/test/test_robb_smarrt_2-wire_dimmer.lua @@ -0,0 +1,115 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- Mock out globals +local test = require "integration_test" +local clusters = require "st.zigbee.zcl.clusters" +local OnOff = clusters.OnOff +local SimpleMetering = clusters.SimpleMetering +local ElectricalMeasurement = clusters.ElectricalMeasurement +local capabilities = require "st.capabilities" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local t_utils = require "integration_test.utils" + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("switch-power-energy.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "ROBB smarrt", + model = "ROB_200-011-0", + server_clusters = { 0x0000, 0x0003, 0x0004, 0x0005, 0x0006, 0x0008, 0x0702, 0x0B04, 0x0B05 }, + client_clusters = { 0x0019 } + } + } + } +) + +zigbee_test_utils.prepare_zigbee_env_info() +local function test_init() + test.mock_device.add_test_device(mock_device) + zigbee_test_utils.init_noop_health_check_timer() +end + +test.set_test_init_function(test_init) + +test.register_message_test( + "Capability command On should be handled", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "switch", component = "main", command = "on", args = {} } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, OnOff.server.commands.On(mock_device) } + } + } +) + +test.register_message_test( + "Capability command Off should be handled", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "switch", component = "main", command = "off", args = {} } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, OnOff.server.commands.Off(mock_device) } + } + } +) + +test.register_message_test( + "Handle Power meter", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, + ElectricalMeasurement.attributes.ActivePower:build_test_attr_report(mock_device, 90) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 9.0, unit = "W" })) + } + } +) + +test.register_message_test( + "Handle Energy meter", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 0x000000E4E1C0) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.energyMeter.energy({ value = 15.0, unit = "kWh" })) + } + } +) + +test.run_registered_tests() diff --git a/zigbee-switch/src/test/test_robb_smarrt_knob_dimmer.lua b/zigbee-switch/src/test/test_robb_smarrt_knob_dimmer.lua new file mode 100644 index 0000000..852854b --- /dev/null +++ b/zigbee-switch/src/test/test_robb_smarrt_knob_dimmer.lua @@ -0,0 +1,115 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- Mock out globals +local test = require "integration_test" +local clusters = require "st.zigbee.zcl.clusters" +local OnOff = clusters.OnOff +local SimpleMetering = clusters.SimpleMetering +local ElectricalMeasurement = clusters.ElectricalMeasurement +local capabilities = require "st.capabilities" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local t_utils = require "integration_test.utils" + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("switch-power-energy.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "ROBB smarrt", + model = "ROB_200-014-0", + server_clusters = { 0x0000, 0x0003, 0x0004, 0x0005, 0x0006, 0x0008, 0x0702, 0x0B04, 0x0B05 }, + client_clusters = { 0x0019 } + } + } + } +) + +zigbee_test_utils.prepare_zigbee_env_info() +local function test_init() + test.mock_device.add_test_device(mock_device) + zigbee_test_utils.init_noop_health_check_timer() +end + +test.set_test_init_function(test_init) + +test.register_message_test( + "Capability command On should be handled", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "switch", component = "main", command = "on", args = {} } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, OnOff.server.commands.On(mock_device) } + } + } +) + +test.register_message_test( + "Capability command Off should be handled", + { + { + channel = "capability", + direction = "receive", + message = { mock_device.id, { capability = "switch", component = "main", command = "off", args = {} } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_device.id, OnOff.server.commands.Off(mock_device) } + } + } +) + +test.register_message_test( + "Handle Power meter", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, + ElectricalMeasurement.attributes.ActivePower:build_test_attr_report(mock_device, 90) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.powerMeter.power({ value = 9.0, unit = "W" })) + } + } +) + +test.register_message_test( + "Handle Energy meter", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, + SimpleMetering.attributes.CurrentSummationDelivered:build_test_attr_report(mock_device, 0x000000E4E1C0) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", + capabilities.energyMeter.energy({ value = 15.0, unit = "kWh" })) + } + } +) + +test.run_registered_tests()