From adf3d9436d8a0c12bec23facd7b2202d413283b9 Mon Sep 17 00:00:00 2001 From: Ivo Wouters | Nexmosphere Date: Thu, 16 Oct 2025 10:17:48 +0200 Subject: [PATCH] Nexmosphere --- drivers/DeepSmart/deepsmart/src/config.lua | 102 +- drivers/DeepSmart/deepsmart/src/discovery.lua | 90 +- drivers/DeepSmart/deepsmart/src/ssdp.lua | 178 +- drivers/Nexmosphere/config.yml | 5 + .../Nexmosphere/profiles/NexMother.v1.yaml | 166 ++ drivers/Nexmosphere/profiles/XEA20.v4.yaml | 66 + drivers/Nexmosphere/profiles/XET50.v4.yaml | 48 + drivers/Nexmosphere/profiles/XQL2.v6.yaml | 107 + drivers/Nexmosphere/profiles/XRDR1.v4.yaml | 42 + drivers/Nexmosphere/profiles/XRDR2.v4.yaml | 78 + .../Nexmosphere/profiles/XY200SERIES.v5.yaml | 72 + drivers/Nexmosphere/src/discovery.lua | 23 + drivers/Nexmosphere/src/init.lua | 1429 +++++++++++++ drivers/SmartThings/jbl/src/discovery.lua | 202 +- .../cook-surface-one-cook-surface-two-tl.yml | 48 +- .../cook-surface-one-cook-surface-two.yml | 44 +- ...ook-surface-one-tl-cook-surface-two-tl.yml | 52 +- .../cook-surface-one-tl-cook-surface-two.yml | 48 +- .../profiles/cook-surface-one-tl.yml | 38 +- .../profiles/cook-surface-one.yml | 34 +- .../matter-appliance/profiles/cook-top.yml | 24 +- .../src/matter-cook-top/init.lua | 194 +- ...nergy-meas-power-meas-energy-mgmt-mode.yml | 64 +- drivers/SmartThings/matter-lock/config.yml | 0 .../SmartThings/matter-lock/fingerprints.yml | 0 .../profiles/base-lock-batteryLevel.yml | 0 .../profiles/base-lock-nobattery.yml | 0 .../matter-lock/profiles/base-lock.yml | 0 .../profiles/lock-lockalarm-batteryLevel.yml | 0 .../matter-lock/profiles/lock-lockalarm.yml | 0 .../lock-without-codes-batteryLevel.yml | 0 .../profiles/lock-without-codes-nobattery.yml | 0 .../profiles/lock-without-codes.yml | 0 drivers/SmartThings/matter-lock/src/init.lua | 0 drivers/SmartThings/matter-pump/config.yml | 6 +- .../SmartThings/matter-pump/fingerprints.yml | 10 +- .../matter-pump/profiles/pump-level.yml | 40 +- .../matter-pump/profiles/pump-only.yml | 36 +- drivers/SmartThings/matter-pump/src/init.lua | 654 +++--- .../light-color-level-1800K-6500K.yml | 52 +- ...ht-level-power-energy-powerConsumption.yml | 0 ...h2o-meas-pm10-pm25-ch2o-no2-tvoc-level.yml | 61 - drivers/SmartThings/philips-hue/run_specs.sh | 0 .../zigbee-air-quality-detector/config.yml | 0 .../fingerprints.yml | 0 .../profiles/air-quality-detector-MultiIR.yml | 0 .../src/MultiIR/custom_clusters.lua | 0 .../src/MultiIR/init.lua | 0 .../zigbee-air-quality-detector/src/init.lua | 0 .../test_MultiIR_air_quality_detector.lua | 0 .../zigbee-bed/capabilities/aiMode.yaml | 0 .../capabilities/autoInflation.yaml | 0 .../zigbee-bed/capabilities/leftControl.yaml | 0 .../capabilities/mattressHardness.yaml | 0 .../zigbee-bed/capabilities/rightControl.yaml | 0 .../capabilities/strongExpMode.yaml | 0 .../zigbee-bed/capabilities/yoga.yaml | 0 drivers/SmartThings/zigbee-bed/config.yml | 0 .../SmartThings/zigbee-bed/fingerprints.yml | 0 .../profiles/shus-smart-mattress.yml | 0 drivers/SmartThings/zigbee-bed/src/init.lua | 0 .../src/shus-mattress/custom_capabilities.lua | 0 .../src/shus-mattress/custom_clusters.lua | 0 .../zigbee-bed/src/shus-mattress/init.lua | 0 .../src/test/test_shus_mattress.lua | 1848 ++++++++--------- .../profiles/generic-motion-illuminance.yml | 0 .../zigbee-sensor/src/contact/init.lua | 0 .../src/motion-illuminance/init.lua | 0 .../zigbee-sensor/src/motion/init.lua | 0 .../zigbee-sensor/src/waterleak/init.lua | 0 .../switch-smart-bath-heater-laisiao.yml | 124 +- .../zigbee-switch/src/laisiao/init.lua | 168 +- .../src/test/test_inovelli-vzm31-sn.lua | 0 .../src/test/test_laisiao_bath_heather.lua | 0 .../src/test/test_sinope_switch.lua | 0 .../src/test/test_tuya_multi.lua | 186 +- .../capabilities/deviceInitialization.yaml | 90 +- ...ndow-treatment-aqara-curtain-driver-e1.yml | 58 +- ...ow-treatment-aqara-roller-shade-rotate.yml | 42 +- .../profiles/window-treatment-aqara.yml | 42 +- .../src/aqara/curtain-driver-e1/init.lua | 464 ++--- .../src/aqara/init.lua | 456 ++-- .../src/aqara/roller-shade/init.lua | 282 +-- .../test_zigbee_window_treatment_axis.lua | 0 .../profiles/shelly-wave-motion.yml | 116 +- .../base-radiator-thermostat-10to30C.yml | 0 .../profiles/base-radiator-thermostat.yml | 0 .../profiles/popp-radiator-thermostat.yml | 0 .../src/aeotec-radiator-thermostat/init.lua | 0 .../SmartThings/zwave-thermostat/src/init.lua | 0 .../src/popp-radiator-thermostat/init.lua | 0 .../test/test_aeotec_radiator_thermostat.lua | 0 .../test/test_popp_radiator_thermostat.lua | 0 tools/run_driver_tests.py | 0 94 files changed, 4932 insertions(+), 2957 deletions(-) create mode 100644 drivers/Nexmosphere/config.yml create mode 100644 drivers/Nexmosphere/profiles/NexMother.v1.yaml create mode 100644 drivers/Nexmosphere/profiles/XEA20.v4.yaml create mode 100644 drivers/Nexmosphere/profiles/XET50.v4.yaml create mode 100644 drivers/Nexmosphere/profiles/XQL2.v6.yaml create mode 100644 drivers/Nexmosphere/profiles/XRDR1.v4.yaml create mode 100644 drivers/Nexmosphere/profiles/XRDR2.v4.yaml create mode 100644 drivers/Nexmosphere/profiles/XY200SERIES.v5.yaml create mode 100644 drivers/Nexmosphere/src/discovery.lua create mode 100644 drivers/Nexmosphere/src/init.lua mode change 100755 => 100644 drivers/SmartThings/matter-lock/config.yml mode change 100755 => 100644 drivers/SmartThings/matter-lock/fingerprints.yml mode change 100755 => 100644 drivers/SmartThings/matter-lock/profiles/base-lock-batteryLevel.yml mode change 100755 => 100644 drivers/SmartThings/matter-lock/profiles/base-lock-nobattery.yml mode change 100755 => 100644 drivers/SmartThings/matter-lock/profiles/base-lock.yml mode change 100755 => 100644 drivers/SmartThings/matter-lock/profiles/lock-lockalarm-batteryLevel.yml mode change 100755 => 100644 drivers/SmartThings/matter-lock/profiles/lock-lockalarm.yml mode change 100755 => 100644 drivers/SmartThings/matter-lock/profiles/lock-without-codes-batteryLevel.yml mode change 100755 => 100644 drivers/SmartThings/matter-lock/profiles/lock-without-codes-nobattery.yml mode change 100755 => 100644 drivers/SmartThings/matter-lock/profiles/lock-without-codes.yml mode change 100755 => 100644 drivers/SmartThings/matter-lock/src/init.lua mode change 100755 => 100644 drivers/SmartThings/matter-switch/profiles/light-color-level-1800K-6500K.yml mode change 100755 => 100644 drivers/SmartThings/matter-switch/profiles/light-level-power-energy-powerConsumption.yml delete mode 100644 drivers/SmartThings/matter-thermostat/profiles/air-purifier-hepa-ac-rock-wind-thermostat-humidity-fan-heating-only-nostate-nobattery-aqs-pm10-pm25-ch2o-meas-pm10-pm25-ch2o-no2-tvoc-level.yml mode change 100755 => 100644 drivers/SmartThings/philips-hue/run_specs.sh mode change 100755 => 100644 drivers/SmartThings/zigbee-air-quality-detector/config.yml mode change 100755 => 100644 drivers/SmartThings/zigbee-air-quality-detector/fingerprints.yml mode change 100755 => 100644 drivers/SmartThings/zigbee-air-quality-detector/profiles/air-quality-detector-MultiIR.yml mode change 100755 => 100644 drivers/SmartThings/zigbee-air-quality-detector/src/MultiIR/custom_clusters.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-air-quality-detector/src/MultiIR/init.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-air-quality-detector/src/init.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-air-quality-detector/src/test/test_MultiIR_air_quality_detector.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-bed/capabilities/aiMode.yaml mode change 100755 => 100644 drivers/SmartThings/zigbee-bed/capabilities/autoInflation.yaml mode change 100755 => 100644 drivers/SmartThings/zigbee-bed/capabilities/leftControl.yaml mode change 100755 => 100644 drivers/SmartThings/zigbee-bed/capabilities/mattressHardness.yaml mode change 100755 => 100644 drivers/SmartThings/zigbee-bed/capabilities/rightControl.yaml mode change 100755 => 100644 drivers/SmartThings/zigbee-bed/capabilities/strongExpMode.yaml mode change 100755 => 100644 drivers/SmartThings/zigbee-bed/capabilities/yoga.yaml mode change 100755 => 100644 drivers/SmartThings/zigbee-bed/config.yml mode change 100755 => 100644 drivers/SmartThings/zigbee-bed/fingerprints.yml mode change 100755 => 100644 drivers/SmartThings/zigbee-bed/profiles/shus-smart-mattress.yml mode change 100755 => 100644 drivers/SmartThings/zigbee-bed/src/init.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-bed/src/shus-mattress/custom_capabilities.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-bed/src/shus-mattress/custom_clusters.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-bed/src/shus-mattress/init.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-sensor/profiles/generic-motion-illuminance.yml mode change 100755 => 100644 drivers/SmartThings/zigbee-sensor/src/contact/init.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-sensor/src/motion-illuminance/init.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-sensor/src/motion/init.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-sensor/src/waterleak/init.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-switch/profiles/switch-smart-bath-heater-laisiao.yml mode change 100755 => 100644 drivers/SmartThings/zigbee-switch/src/laisiao/init.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-switch/src/test/test_inovelli-vzm31-sn.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-switch/src/test/test_laisiao_bath_heather.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-switch/src/test/test_sinope_switch.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-switch/src/test/test_tuya_multi.lua mode change 100755 => 100644 drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_axis.lua mode change 100755 => 100644 drivers/SmartThings/zwave-thermostat/profiles/base-radiator-thermostat-10to30C.yml mode change 100755 => 100644 drivers/SmartThings/zwave-thermostat/profiles/base-radiator-thermostat.yml mode change 100755 => 100644 drivers/SmartThings/zwave-thermostat/profiles/popp-radiator-thermostat.yml mode change 100755 => 100644 drivers/SmartThings/zwave-thermostat/src/aeotec-radiator-thermostat/init.lua mode change 100755 => 100644 drivers/SmartThings/zwave-thermostat/src/init.lua mode change 100755 => 100644 drivers/SmartThings/zwave-thermostat/src/popp-radiator-thermostat/init.lua mode change 100755 => 100644 drivers/SmartThings/zwave-thermostat/src/test/test_aeotec_radiator_thermostat.lua mode change 100755 => 100644 drivers/SmartThings/zwave-thermostat/src/test/test_popp_radiator_thermostat.lua mode change 100755 => 100644 tools/run_driver_tests.py diff --git a/drivers/DeepSmart/deepsmart/src/config.lua b/drivers/DeepSmart/deepsmart/src/config.lua index 46a958d313..e44c25d9ef 100644 --- a/drivers/DeepSmart/deepsmart/src/config.lua +++ b/drivers/DeepSmart/deepsmart/src/config.lua @@ -1,51 +1,51 @@ -local config = {} --- device info --- NOTE: In the future this information --- may be submitted through the Developer --- Workspace to avoid hardcoded values. -config.DEVICE_PROFILE={} -config.DEVICE_PROFILE[3]='Ac.v1' -config.DEVICE_PROFILE[4]='Heater.v1' -config.DEVICE_PROFILE[53]='Newfan.v1' -config.DEVICE_TYPE='LAN' - --- SSDP Config -config.MC_ADDRESS='239.255.255.250' -config.MC_PORT=1900 -config.MC_TIMEOUT=6 - -config.ENUM = {} -config.ENUM.AC = 3 -config.ENUM.HEATER = 4 -config.ENUM.NEWFAN = 53 - - ---device addrtype -config.AC = {} -config.AC.ONOFF = 0 -config.AC.MODE = 1 -config.AC.FAN = 2 -config.AC.SETTEMP = 3 -config.AC.TEMP = 4 - -config.HEATER = {} -config.HEATER.ONOFF = 0 -config.HEATER.SETTEMP = 2 -config.HEATER.TEMP = 3 - -config.NEWFAN = {} -config.NEWFAN.ONOFF = 0 -config.NEWFAN.FAN = 1 - -config.DEVICE = {} -config.DEVICE.ONOFF = 0 - -config.FIELD = {} -config.FIELD.DP2KNX = "dp2knx" -config.FIELD.DPENUM = "dpenum" -config.FIELD.DEVICES = "devices" -config.FIELD.IP = "ip" -config.FIELD.INVALID = "invalid" - - -return config +local config = {} +-- device info +-- NOTE: In the future this information +-- may be submitted through the Developer +-- Workspace to avoid hardcoded values. +config.DEVICE_PROFILE={} +config.DEVICE_PROFILE[3]='Ac.v1' +config.DEVICE_PROFILE[4]='Heater.v1' +config.DEVICE_PROFILE[53]='Newfan.v1' +config.DEVICE_TYPE='LAN' + +-- SSDP Config +config.MC_ADDRESS='239.255.255.250' +config.MC_PORT=1900 +config.MC_TIMEOUT=6 + +config.ENUM = {} +config.ENUM.AC = 3 +config.ENUM.HEATER = 4 +config.ENUM.NEWFAN = 53 + + +--device addrtype +config.AC = {} +config.AC.ONOFF = 0 +config.AC.MODE = 1 +config.AC.FAN = 2 +config.AC.SETTEMP = 3 +config.AC.TEMP = 4 + +config.HEATER = {} +config.HEATER.ONOFF = 0 +config.HEATER.SETTEMP = 2 +config.HEATER.TEMP = 3 + +config.NEWFAN = {} +config.NEWFAN.ONOFF = 0 +config.NEWFAN.FAN = 1 + +config.DEVICE = {} +config.DEVICE.ONOFF = 0 + +config.FIELD = {} +config.FIELD.DP2KNX = "dp2knx" +config.FIELD.DPENUM = "dpenum" +config.FIELD.DEVICES = "devices" +config.FIELD.IP = "ip" +config.FIELD.INVALID = "invalid" + + +return config diff --git a/drivers/DeepSmart/deepsmart/src/discovery.lua b/drivers/DeepSmart/deepsmart/src/discovery.lua index f824c57615..cdb3f139bd 100644 --- a/drivers/DeepSmart/deepsmart/src/discovery.lua +++ b/drivers/DeepSmart/deepsmart/src/discovery.lua @@ -1,45 +1,45 @@ -local log = require('log') -local Wisers = require "deepsmart.wisers" -local ssdp = require('ssdp') -local net_utils = require('st.net_utils') - -local disco = {} ------------------------ --- app discover new bridges or new devices -disco.ssdp_discovery_callback = function(uuid, ip) - if (not net_utils.validate_ipv4_string(ip) or uuid == nil) then - log.warn(string.format('ip (%s) or uuid (%s) is invalid', ip, uuid)) - return - end - -- add discovered wiser - Wisers.add_wiser(uuid, ip, nil) - Wisers.refresh_wiser(uuid) -end --- check whether bridge ip changed -disco.ssdp_checkip_callback = function(uuid, ip) - if (not net_utils.validate_ipv4_string(ip) or uuid == nil) then - log.warn(string.format('ip (%s) or uuid (%s) is invalid', ip, uuid)) - return - end - -- check uuid(if added) - Wisers.update_wiser_ip(uuid, ip) -end --- Discovery service which will --- invoke the above private functions. --- - find_device --- - parse_ssdp --- - fetch_device_info --- - create_device --- --- This resource is linked to --- driver.discovery and it is --- automatically called when --- user scan devices from the --- SmartThings App. -function disco.start(driver, opts, cons) - log.info('in discover start...') - ssdp.search(disco.ssdp_discovery_callback) - log.info('===== DEVICE DISCOVER DEVICES OVER') -end - -return disco +local log = require('log') +local Wisers = require "deepsmart.wisers" +local ssdp = require('ssdp') +local net_utils = require('st.net_utils') + +local disco = {} +----------------------- +-- app discover new bridges or new devices +disco.ssdp_discovery_callback = function(uuid, ip) + if (not net_utils.validate_ipv4_string(ip) or uuid == nil) then + log.warn(string.format('ip (%s) or uuid (%s) is invalid', ip, uuid)) + return + end + -- add discovered wiser + Wisers.add_wiser(uuid, ip, nil) + Wisers.refresh_wiser(uuid) +end +-- check whether bridge ip changed +disco.ssdp_checkip_callback = function(uuid, ip) + if (not net_utils.validate_ipv4_string(ip) or uuid == nil) then + log.warn(string.format('ip (%s) or uuid (%s) is invalid', ip, uuid)) + return + end + -- check uuid(if added) + Wisers.update_wiser_ip(uuid, ip) +end +-- Discovery service which will +-- invoke the above private functions. +-- - find_device +-- - parse_ssdp +-- - fetch_device_info +-- - create_device +-- +-- This resource is linked to +-- driver.discovery and it is +-- automatically called when +-- user scan devices from the +-- SmartThings App. +function disco.start(driver, opts, cons) + log.info('in discover start...') + ssdp.search(disco.ssdp_discovery_callback) + log.info('===== DEVICE DISCOVER DEVICES OVER') +end + +return disco diff --git a/drivers/DeepSmart/deepsmart/src/ssdp.lua b/drivers/DeepSmart/deepsmart/src/ssdp.lua index 4bbde9150c..45a50fed33 100644 --- a/drivers/DeepSmart/deepsmart/src/ssdp.lua +++ b/drivers/DeepSmart/deepsmart/src/ssdp.lua @@ -1,89 +1,89 @@ -local socket = require('socket') -local log = require('log') -local config = require('config') - - - - -local SSDP = {} - -local DEEPSMART_SSDP_SEARCH_TERM = "DEEPSMART-ARM" ------------------------ --- SSDP Response parser -local function parse_ssdp(data) - local res = {} - res.status = data:sub(0, data:find('\r\n')) - for k, v in data:gmatch('([%w-]+): ([%a+-: /=]+)') do - local key = k:lower() - log.info('parse key '..key..' val '..v) - res[key] = v - end - return res -end - - --- This function enables a UDP --- Socket and broadcast a single --- M-SEARCH request, i.e., it --- must be looped appart. -function SSDP.search(callback) - local bridges = {} - -- UDP socket initialization - local upnp = socket.udp() - local _,err = upnp:setsockname('0.0.0.0', 0) - if err then - log.error(string.format("udp socket failure setsockname: %s", err)) - return false,bridges,err - end - -- use broadcast to find wisers as wifi router's group cast is forbidden by default(need login to router to start group cast) - upnp:setoption('broadcast', true) - local timeout = socket.gettime() + config.MC_TIMEOUT + 1 - local mx = 2 - -- broadcasting request - log.info('===== SCANNING NETWORK...') - local multicast_msg = table.concat({ - "M-SEARCH * HTTP/1.1", - "HOST: 255.255.255.255:1900", - 'MAN: "ssdp:discover"', -- yes, there are really supposed to be quotes in this one - string.format("MX: %s", mx), - string.format("ST: %s", DEEPSMART_SSDP_SEARCH_TERM), - "\r\n" - }, "\r\n") - local _, err = upnp:sendto(multicast_msg, config.MC_ADDRESS, config.MC_PORT) - if err then - log.error(string.format("udp socket failure sendto: %s", err)) - return false,bridges,err - end - while true do - -- Socket will wait n seconds - -- based on the s:setoption(n) - -- to receive a response back. - local time_remaining = math.max(0, timeout - socket.gettime()) - upnp:settimeout(time_remaining) - local res,ip = upnp:receivefrom() - if (res ~= nil) then - log.info('recv wiser '..res..' from '..ip) - local headers = parse_ssdp(res) - -- Device metadata - local usn = headers.usn - local uuid = usn:match("uuid:(%d+)::") - if (uuid ~= nil) then - log.info('usn '..usn..' uuid '..uuid..' ip '..ip) - if callback ~= nil and type(callback) == "function" then - callback(uuid, ip) - end - bridges[uuid] = ip - end - else - break - end - end - log.info('===== SCANNING NETWORK OVER') - -- close udp socket - upnp:close() - - return true,bridges,nil -end - - -return SSDP +local socket = require('socket') +local log = require('log') +local config = require('config') + + + + +local SSDP = {} + +local DEEPSMART_SSDP_SEARCH_TERM = "DEEPSMART-ARM" +----------------------- +-- SSDP Response parser +local function parse_ssdp(data) + local res = {} + res.status = data:sub(0, data:find('\r\n')) + for k, v in data:gmatch('([%w-]+): ([%a+-: /=]+)') do + local key = k:lower() + log.info('parse key '..key..' val '..v) + res[key] = v + end + return res +end + + +-- This function enables a UDP +-- Socket and broadcast a single +-- M-SEARCH request, i.e., it +-- must be looped appart. +function SSDP.search(callback) + local bridges = {} + -- UDP socket initialization + local upnp = socket.udp() + local _,err = upnp:setsockname('0.0.0.0', 0) + if err then + log.error(string.format("udp socket failure setsockname: %s", err)) + return false,bridges,err + end + -- use broadcast to find wisers as wifi router's group cast is forbidden by default(need login to router to start group cast) + upnp:setoption('broadcast', true) + local timeout = socket.gettime() + config.MC_TIMEOUT + 1 + local mx = 2 + -- broadcasting request + log.info('===== SCANNING NETWORK...') + local multicast_msg = table.concat({ + "M-SEARCH * HTTP/1.1", + "HOST: 255.255.255.255:1900", + 'MAN: "ssdp:discover"', -- yes, there are really supposed to be quotes in this one + string.format("MX: %s", mx), + string.format("ST: %s", DEEPSMART_SSDP_SEARCH_TERM), + "\r\n" + }, "\r\n") + local _, err = upnp:sendto(multicast_msg, config.MC_ADDRESS, config.MC_PORT) + if err then + log.error(string.format("udp socket failure sendto: %s", err)) + return false,bridges,err + end + while true do + -- Socket will wait n seconds + -- based on the s:setoption(n) + -- to receive a response back. + local time_remaining = math.max(0, timeout - socket.gettime()) + upnp:settimeout(time_remaining) + local res,ip = upnp:receivefrom() + if (res ~= nil) then + log.info('recv wiser '..res..' from '..ip) + local headers = parse_ssdp(res) + -- Device metadata + local usn = headers.usn + local uuid = usn:match("uuid:(%d+)::") + if (uuid ~= nil) then + log.info('usn '..usn..' uuid '..uuid..' ip '..ip) + if callback ~= nil and type(callback) == "function" then + callback(uuid, ip) + end + bridges[uuid] = ip + end + else + break + end + end + log.info('===== SCANNING NETWORK OVER') + -- close udp socket + upnp:close() + + return true,bridges,nil +end + + +return SSDP diff --git a/drivers/Nexmosphere/config.yml b/drivers/Nexmosphere/config.yml new file mode 100644 index 0000000000..8d36f13355 --- /dev/null +++ b/drivers/Nexmosphere/config.yml @@ -0,0 +1,5 @@ +name: "NexmosphereEdgeDriver_A" +packageKey: "NexmosphereEdgeDriver_A" +permissions: + lan: {} + discovery: {} diff --git a/drivers/Nexmosphere/profiles/NexMother.v1.yaml b/drivers/Nexmosphere/profiles/NexMother.v1.yaml new file mode 100644 index 0000000000..fdff6717d4 --- /dev/null +++ b/drivers/Nexmosphere/profiles/NexMother.v1.yaml @@ -0,0 +1,166 @@ +{ + "id": "82cbade3-4b14-4ee9-b196-726a7198e535", + "name": "NexMother.v1", + "metadata": { + "vid": "a4e1cdb5-a7c5-3e5b-b4c8-187c9989173e", + "mnmn": "Nexmosphere" + }, + "migrationStatus": "NOT_MIGRATED", + "status": "DEVELOPMENT", + "preferences": [ + + { + "preferenceId": "controllerIP", + "name": "controllerIP", + "title": "Controller IP", + "description": "Check when installed.", + "required": false, + "explicit": false, + "preferenceType": "string", + "definition": { + "stringType": "text", + "default": "0.0.0.0" + } + }, + + { + "preferenceId": "controllerPort", + "name": "controllerPort", + "title": "Controller Port", + "description": "Check when installed.", + "required": false, + "explicit": false, + "preferenceType": "string", + "definition": { + "stringType": "text", + "default": "5000" + } + }, + + { + "preferenceId": "setupCom", + "name": "setupCom", + "title": "Setup Communication", + "description": "Only when you install", + "required": false, + "explicit": false, + "preferenceType": "enumeration", + "definition": { + "options":{ + "START": "Start", + "STOP": "Stop" + }, + "default": "STOP" + } + }, + + { + "preferenceId": "setDebug", + "name": "setDebug", + "title": "Setup Debug Mode", + "description": "Only when you debug", + "required": false, + "explicit": false, + "preferenceType": "enumeration", + "definition": { + "options":{ + "START": "Debug mode ON", + "STOP": "Debug mode OFF" + }, + "default": "STOP" + } + } + ], + + "components": [ + { + "label": "main", + "id": "main", + "capabilities": [ + { + "id": "nexmosphere.connectionStatus", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "nexmosphere.hubIp", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "nexmosphere.hubPort", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "nexmosphere.xtalk001", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "nexmosphere.xtalk002", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "nexmosphere.xtalk003", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "nexmosphere.xtalk004", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "nexmosphere.xtalk005", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "nexmosphere.xtalk006", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "nexmosphere.xtalk007", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "nexmosphere.xtalk008", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "refresh", + "version": 1, + "optional": false, + "ephemeral": false + } + ], + "categories": [ + { + "name": "Switch", + "categoryType": "manufacturer" + } + ], + "optional": false + } + ], + "owner": { + "ownerType": "USER", + "ownerId": "f15ba00b-eb6e-6aec-8074-2c7a20433f5e" + } +} diff --git a/drivers/Nexmosphere/profiles/XEA20.v4.yaml b/drivers/Nexmosphere/profiles/XEA20.v4.yaml new file mode 100644 index 0000000000..9b8934d868 --- /dev/null +++ b/drivers/Nexmosphere/profiles/XEA20.v4.yaml @@ -0,0 +1,66 @@ +{ + "id": "d189de39-44d7-4f3c-9adb-0ddbf0881455", + "name": "XEA20.v4", + "metadata": { + "vid": "338c0969-cad4-3ad3-a043-2561382e0ac8", + "mnmn": "Nexmosphere" + }, + "migrationStatus": "NOT_MIGRATED", + "status": "DEVELOPMENT", + "preferences": [], + "components": [ + { + "label": "main", + "id": "main", + "capabilities": [ + { + "id": "nexmosphere.summary", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "mode", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "nexmosphere.luxValue", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "nexmosphere.luxRange", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "nexmosphere.lightChangeForTrigger", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "refresh", + "version": 1, + "optional": false, + "ephemeral": false + } + ], + "categories": [ + { + "name": "LightSensor", + "categoryType": "manufacturer" + } + ], + "optional": false + } + ], + "owner": { + "ownerType": "USER", + "ownerId": "f15ba00b-eb6e-6aec-8074-2c7a20433f5e" + } +} \ No newline at end of file diff --git a/drivers/Nexmosphere/profiles/XET50.v4.yaml b/drivers/Nexmosphere/profiles/XET50.v4.yaml new file mode 100644 index 0000000000..c01424466e --- /dev/null +++ b/drivers/Nexmosphere/profiles/XET50.v4.yaml @@ -0,0 +1,48 @@ +{ + "id": "7ffd59e5-ecdf-4846-84c0-08110b6aa60f", + "name": "XET50.v4", + "metadata": { + "vid": "9c95e626-4420-372e-a5d6-d4405143a785", + "mnmn": "Nexmosphere" + }, + "migrationStatus": "NOT_MIGRATED", + "status": "DEVELOPMENT", + "preferences": [], + "components": [ + { + "label": "main", + "id": "main", + "capabilities": [ + { + "id": "temperatureMeasurement", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "relativeHumidityMeasurement", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "refresh", + "version": 1, + "optional": false, + "ephemeral": false + } + ], + "categories": [ + { + "name": "TempHumiditySensor", + "categoryType": "manufacturer" + } + ], + "optional": false + } + ], + "owner": { + "ownerType": "USER", + "ownerId": "f15ba00b-eb6e-6aec-8074-2c7a20433f5e" + } +} \ No newline at end of file diff --git a/drivers/Nexmosphere/profiles/XQL2.v6.yaml b/drivers/Nexmosphere/profiles/XQL2.v6.yaml new file mode 100644 index 0000000000..48811237f1 --- /dev/null +++ b/drivers/Nexmosphere/profiles/XQL2.v6.yaml @@ -0,0 +1,107 @@ +{ + "id": "550e5fcc-fb6a-46ea-8f44-4f85d188abea", + "name": "XQL2.v6", + "metadata": { + "vid": "004e5cd9-57a5-3367-bbac-f450426a563d", + "mnmn": "Nexmosphere" + }, + "migrationStatus": "NOT_MIGRATED", + "status": "DEVELOPMENT", + "preferences": [ + { + "preferenceId": "DFOI", + "name": "dFOI", + "title": "Define Field of Interest", + "description": "(x, y), Max 10 Corners", + "required": false, + "explicit": false, + "preferenceType": "string", + "definition": { + "stringType": "text", + "default": "(-010,+010), (+010,+010), (+010,-010), (-010,-010)" + } + }, + { + "preferenceId": "RECALFOI", + "name": "reCALFOI", + "title": "Recalculate FOI", + "description": "Set Field of Interest", + "required": false, + "explicit": false, + "preferenceType": "enumeration", + "definition": { + "options": { + "SET": "Set", + "READY": "Ready" + }, + "default": "READY" + } + }, + { + "preferenceId": "DAZ", + "name": "dAZ", + "title": "Define Activation zones", + "description": "(x, y, w, h), Max 24 Activation zones", + "required": false, + "explicit": false, + "preferenceType": "string", + "definition": { + "stringType": "text", + "default": "(-010,+000,010,010), (+000,+000,010,010), (+000,-010,010,010), (-010,-010,010,010)" + } + }, + { + "preferenceId": "SETAZ", + "name": "setAZ", + "title": "Set Activation Zones", + "description": "Check coordination", + "required": false, + "explicit": false, + "preferenceType": "enumeration", + "definition": { + "options": { + "SET": "Set", + "READY": "Ready" + }, + "default": "READY" + } + } + ], + "components": [ + { + "label": "main", + "id": "main", + "capabilities": [ + { + "id": "mode", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "nexmosphere.numberAndStatus", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "refresh", + "version": 1, + "optional": false, + "ephemeral": false + } + ], + "categories": [ + { + "name": "ZoneSensor", + "categoryType": "manufacturer" + } + ], + "optional": false + } + ], + "owner": { + "ownerType": "USER", + "ownerId": "f15ba00b-eb6e-6aec-8074-2c7a20433f5e" + } +} \ No newline at end of file diff --git a/drivers/Nexmosphere/profiles/XRDR1.v4.yaml b/drivers/Nexmosphere/profiles/XRDR1.v4.yaml new file mode 100644 index 0000000000..37094e2fba --- /dev/null +++ b/drivers/Nexmosphere/profiles/XRDR1.v4.yaml @@ -0,0 +1,42 @@ +{ + "id": "93885eee-d3ed-4382-9bca-e22c70e87550", + "name": "XRDR1.v4", + "metadata": { + "vid": "1eec65a4-4313-3c7d-a53a-b9cd6a2289c5", + "mnmn": "Nexmosphere" + }, + "migrationStatus": "NOT_MIGRATED", + "status": "DEVELOPMENT", + "preferences": [], + "components": [ + { + "label": "main", + "id": "main", + "capabilities": [ + { + "id": "nexmosphere.numberAndStatus", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "refresh", + "version": 1, + "optional": false, + "ephemeral": false + } + ], + "categories": [ + { + "name": "Scanner", + "categoryType": "manufacturer" + } + ], + "optional": false + } + ], + "owner": { + "ownerType": "USER", + "ownerId": "f15ba00b-eb6e-6aec-8074-2c7a20433f5e" + } +} \ No newline at end of file diff --git a/drivers/Nexmosphere/profiles/XRDR2.v4.yaml b/drivers/Nexmosphere/profiles/XRDR2.v4.yaml new file mode 100644 index 0000000000..c141cbd8a7 --- /dev/null +++ b/drivers/Nexmosphere/profiles/XRDR2.v4.yaml @@ -0,0 +1,78 @@ +{ + "id": "17ae81ca-ed19-4b39-9591-5d88f291b808", + "name": "XRDR2.v4", + "metadata": { + "vid": "49cbf14e-c1d9-3040-92a5-57b85ab4c1cb", + "mnmn": "Nexmosphere" + }, + "migrationStatus": "NOT_MIGRATED", + "status": "DEVELOPMENT", + "preferences": [], + "components": [ + { + "label": "main", + "id": "main", + "capabilities": [ + { + "id": "nexmosphere.summary", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "mode", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "nexmosphere.uid", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "nexmosphere.number", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "nexmosphere.label1", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "nexmosphere.label2", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "nexmosphere.label3", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "refresh", + "version": 1, + "optional": false, + "ephemeral": false + } + ], + "categories": [ + { + "name": "TagReader", + "categoryType": "manufacturer" + } + ], + "optional": false + } + ], + "owner": { + "ownerType": "USER", + "ownerId": "f15ba00b-eb6e-6aec-8074-2c7a20433f5e" + } +} \ No newline at end of file diff --git a/drivers/Nexmosphere/profiles/XY200SERIES.v5.yaml b/drivers/Nexmosphere/profiles/XY200SERIES.v5.yaml new file mode 100644 index 0000000000..11375629a5 --- /dev/null +++ b/drivers/Nexmosphere/profiles/XY200SERIES.v5.yaml @@ -0,0 +1,72 @@ +{ + "id": "c8a276eb-abd9-40d8-944a-082f67e18715", + "name": "XY200SERIES.v5", + "metadata": { + "vid": "147dcac7-87f4-3fec-b6ca-68864d32dff0", + "mnmn": "Nexmosphere" + }, + "migrationStatus": "NOT_MIGRATED", + "status": "DEVELOPMENT", + "preferences": [], + "components": [ + { + "label": "main", + "id": "main", + "capabilities": [ + { + "id": "nexmosphere.summary", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "mode", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "nexmosphere.distanceZone", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "nexmosphere.absoluteDistance", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "nexmosphere.changeForTrigger", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "nexmosphere.rangeOfInterest", + "version": 1, + "optional": false, + "ephemeral": false + }, + { + "id": "refresh", + "version": 1, + "optional": false, + "ephemeral": false + } + ], + "categories": [ + { + "name": "PresenceSensor", + "categoryType": "manufacturer" + } + ], + "optional": false + } + ], + "owner": { + "ownerType": "USER", + "ownerId": "f15ba00b-eb6e-6aec-8074-2c7a20433f5e" + } +} \ No newline at end of file diff --git a/drivers/Nexmosphere/src/discovery.lua b/drivers/Nexmosphere/src/discovery.lua new file mode 100644 index 0000000000..475604c0f5 --- /dev/null +++ b/drivers/Nexmosphere/src/discovery.lua @@ -0,0 +1,23 @@ +local log = require "log" +local discovery = {} + + +local metadata_nexController = { + type = "LAN", + -- the DNI must be unique across your hub, using static ID here so that we + -- only ever have a single instance of this "device" + device_network_id = "XC101U_1_A", + label = "[A] Nexmosphere controller", + profile = "NexMother.v1", + manufacturer = "Nexmosphere", + model = "v1", + vendor_provided_label = nil +} + +-- handle discovery events, normally you'd try to discover devices on your +-- network in a loop until calling `should_continue()` returns false. +function discovery.handle_discovery(driver, _should_continue) + log.info("☆☆☆ Starting LAN UDP discovery ☆☆☆") + driver:try_create_device(metadata_nexController) +end +return discovery \ No newline at end of file diff --git a/drivers/Nexmosphere/src/init.lua b/drivers/Nexmosphere/src/init.lua new file mode 100644 index 0000000000..6f3f1a73cf --- /dev/null +++ b/drivers/Nexmosphere/src/init.lua @@ -0,0 +1,1429 @@ +-- require st provided libraries +local capabilities = require "st.capabilities" +local Driver = require "st.driver" +local log = require "log" +-- local socket = require("socket") +local socket = require("cosock.socket") +local udp_client +local nexmoDriver + +-- require custom handlers from driver package +--local command_handlers = require "command_handlers" +local discovery = require "discovery" +local settingsSend = false +local sensorSwap = false +local UDPDebugMode = false + +local controllerLabel = "A" + +local controllerIP +local controllerPort +local hubIp +local hubPort + +local networkSettingsGenerated = false + +local deviceForPort = {} + +local deviceController + +local storedSettingPresenceMode = {} +local storedSettingPresenceChangeForTrigger = {} +local storedSettingPresenceRangeOfInterest = {} + +local storedSettingAmbientMode = {} +local storedSettingAmbientChangeForTrigger = {} + +local storedSettingNFCMode = {} + + +local nfcUID +local nfcNumber +local nfcLabel1 +local nfcLabel2 +local nfcLabel3 + + +local callOnSchedule +local consecutiveMessageCounter = 0 +local noComCounter = 0 +local noResponseCounter = 0 +local checkIfControllerAlive = false + +local genericSensorNexmosphereOutputMode = capabilities["mode"] +local genericSensorNexmosphereRefresh = capabilities["refresh"] +local genericSensorNexmosphereSummary = capabilities["nexmosphere.summary"] +local presenceSensorNexmosphereChangeForTrigger = capabilities["nexmosphere.changeForTrigger"] +local presenceSensorNexmosphereDistanceZone = capabilities["nexmosphere.distanceZone"] +local presenceSensorNexmosphereAbsoluteDistance = capabilities["nexmosphere.absoluteDistance"] +local presenceSensorNexmosphereRangeOfInterest = capabilities["nexmosphere.rangeOfInterest"] +local genericSensorNexmosphereNumberAndStatus = capabilities["nexmosphere.numberAndStatus"] +local ambientLightSensorNexmosphereChangeForTrigger = capabilities["nexmosphere.lightChangeForTrigger"] +local ambientLightSensorNexmosphereLuxValue = capabilities["nexmosphere.luxValue"] +local ambientLightSensorNexmosphereLuxRange = capabilities["nexmosphere.luxRange"] +local tempHumiSensorNexmosphereTemperatureMeasurement = capabilities["temperatureMeasurement"] +local tempHumiSensorNexmosphereHumidityMeasurement = capabilities["relativeHumidityMeasurement"] +local nfcReaderNexmosphereUID = capabilities["nexmosphere.uid"] +local nfcReaderNexmosphereTagNumber = capabilities["nexmosphere.number"] +local nfcReaderNexmosphereLabel1 = capabilities["nexmosphere.label1"] +local nfcReaderNexmosphereLabel2 = capabilities["nexmosphere.label2"] +local nfcReaderNexmosphereLabel3 = capabilities["nexmosphere.label3"] + +local xtalk = {} +xtalk["001"] = capabilities["nexmosphere.xtalk001"] +xtalk["002"] = capabilities["nexmosphere.xtalk002"] +xtalk["003"] = capabilities["nexmosphere.xtalk003"] +xtalk["004"] = capabilities["nexmosphere.xtalk004"] +xtalk["005"] = capabilities["nexmosphere.xtalk005"] +xtalk["006"] = capabilities["nexmosphere.xtalk006"] +xtalk["007"] = capabilities["nexmosphere.xtalk007"] +xtalk["008"] = capabilities["nexmosphere.xtalk008"] +local hubPortSet = capabilities["nexmosphere.hubPort"] +local hubIPSet = capabilities["nexmosphere.hubIp"] +local connectionStatus = capabilities["nexmosphere.connectionStatus"] +local supportedDevices = {"None", "Presence", "Temperature", "NFC", "Lidar", "RFID", "Ambient light"} + +local supportedModesForPresence = {"Absolute distance", "Distance zones"} +local supportedModesForAmbient = {"Lux value", "Lux range"} +local supportedModesForLidar = {"Single Detection", "Multi Detection"} +local supportedModesForNFC = {"UID", "Number", "Label 1", "Label 2", "Label 3", "UID, Number and Label 1", "Label 1, 2 and 3"} + +local initiateSensors +local startUDPCommunication +local handle_udp_read_loop +local handler_xtalk +local set_Mode_handler +local set_Change_handler_presence +local set_Change_handler_ambient +local set_Range_handler_presence +local refresh_handler + +local function stable_delay() + --socket.sleep(0.2) +end + +----------------------------------------------------------------- +-- local functions +----------------------------------------------------------------- +-- this is called once a device is added by the cloud and synchronized down to the hub +local function device_added(driver, device) + log.info("[" .. device.id .. "] Adding new Nexmosphere device") + + -- set default values for Controller parameters + if string.find(tostring(device), "controller") then + for i = 1, 8 do + local xtalkport = tostring("00" ..i) + device:emit_event(xtalk[xtalkport].supportedDevices(supportedDevices)) + stable_delay() + device:emit_event(xtalk[xtalkport].device("None")) + stable_delay() + end + device:emit_event(hubIPSet.hubip("0.0.0.0")) + stable_delay() + device:emit_event(hubPortSet.hubport("00000")) + stable_delay() + device:emit_event(connectionStatus.UDP("Unconnected")) + stable_delay() + + controllerIP = device.preferences.controllerIP + controllerPort = tonumber(device.preferences.controllerPort) + + deviceController = device + end + +-- bookeeping of sensor device to xtalkport mapping when a a sensor device is added + local xtalkport + if string.find(device.device_network_id, controllerLabel .."%[(.-)%]", 0, false) then + log.info("device.device_network_id is " ..device.device_network_id) + local i, j = string.find(device.device_network_id, controllerLabel .."%[(.-)%]", 0, false) + xtalkport = string.sub(device.device_network_id, i+2, j-1) + --Check if X-talk port was set already set to another device, if so, remove old device + if deviceForPort[xtalkport] ~= nil then + sensorSwap = true + log.info("trying to delete device " ..deviceForPort[xtalkport].id) + driver:try_delete_device(deviceForPort[xtalkport].id) + end + deviceForPort[xtalkport] = device + log.info("deviceForPort " ..xtalkport.. " is " ..tostring(deviceForPort[xtalkport])) + end + + log.info(device) + + -- set a default or queried state for each capability attribute + -- also, send default settings, but only if UDP connection is already established + if string.find(tostring(device), "Presence") then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereOutputMode.supportedModes(supportedModesForPresence)) + stable_delay() + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("0 cm, zone XX")) -- Default summary for Presence sensor + stable_delay() + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereOutputMode.mode("Distance zones")) -- Default output mode for Presence sensor + stable_delay() + deviceForPort[xtalkport]:emit_event(presenceSensorNexmosphereDistanceZone.distanceZone("XX")) -- Default distance zone + stable_delay() + deviceForPort[xtalkport]:emit_event(presenceSensorNexmosphereAbsoluteDistance.absoluteDistance(0)) -- Default absolute value + stable_delay() + deviceForPort[xtalkport]:emit_event(presenceSensorNexmosphereChangeForTrigger.change({value = 10, unit = "cm"})) -- Default Change for trigger in cm + stable_delay() + deviceForPort[xtalkport]:emit_event(presenceSensorNexmosphereRangeOfInterest.range(170)) -- Default Range of interest + stable_delay() + if udp_client ~= nil then + udp_client:sendto("X" .. xtalkport .. "B[ZONE?]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "B[TEMP?] request send to UDP controller") + socket.sleep(0.5) + end + + elseif string.find(tostring(device), "RFID") then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereNumberAndStatus.status("lifted")) -- Default RFID status + stable_delay() + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereNumberAndStatus.number(0)) -- Default RFID Tag number + stable_delay() + + elseif string.find(tostring(device), "Ambient") then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereOutputMode.supportedModes(supportedModesForAmbient)) + stable_delay() + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("0 lux, range 0")) -- Default summary for Presence sensor + stable_delay() + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereOutputMode.mode("Lux value")) -- Default output mode for Ambient light sensor + stable_delay() + deviceForPort[xtalkport]:emit_event(ambientLightSensorNexmosphereLuxValue.luxValue(0)) -- Default Lux Value + stable_delay() + deviceForPort[xtalkport]:emit_event(ambientLightSensorNexmosphereLuxRange.luxRange(0)) -- Default Lux Range + stable_delay() + deviceForPort[xtalkport]:emit_event(ambientLightSensorNexmosphereChangeForTrigger.change({value = 20, unit = "%"})) -- Default Change for trigger in % + stable_delay() + if udp_client ~= nil then + udp_client:sendto("X" .. xtalkport .. "S[4:2]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "S[4:2] setting send to UDP controller") + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkport .. "S[5:20]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "S[5:10] setting send to UDP controller") + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkport .. "B[LUX?]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "B[LUX?] request send to UDP controller") + socket.sleep(0.5) + end + + elseif string.find(tostring(device), "Temperature") then + deviceForPort[xtalkport]:emit_event(tempHumiSensorNexmosphereTemperatureMeasurement.temperature({value = 0.0, unit = "C"})) -- Default temperature + stable_delay() + deviceForPort[xtalkport]:emit_event(tempHumiSensorNexmosphereHumidityMeasurement.humidity({value = 0.0, unit = "%"})) -- Default humidity level + stable_delay() + if udp_client ~= nil then + udp_client:sendto("X" .. xtalkport .. "S[4:3]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "S[4:3] setting send to UDP controller") + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkport .. "S[5:3]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "S[5:3] setting send to UDP controller") + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkport .. "B[TEMP?]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "B[TEMP?] request send to UDP controller") + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkport .. "B[HUMI?]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "B[HUMI?] request send to UDP controller") + socket.sleep(0.5) + end + + elseif string.find(tostring(device), "NFC") then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereOutputMode.supportedModes(supportedModesForNFC)) + stable_delay() + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereOutputMode.mode("UID")) -- Default output mode for Presence sensor + stable_delay() + deviceForPort[xtalkport]:emit_event(nfcReaderNexmosphereUID.UID("XXXXXXXXXXXXXX")) -- Default UID + stable_delay() + deviceForPort[xtalkport]:emit_event(nfcReaderNexmosphereTagNumber.number(0)) -- Default TagNumber + stable_delay() + deviceForPort[xtalkport]:emit_event(nfcReaderNexmosphereLabel1.text("-")) -- Default label 1 + stable_delay() + deviceForPort[xtalkport]:emit_event(nfcReaderNexmosphereLabel2.text("-")) -- Default label 2 + stable_delay() + deviceForPort[xtalkport]:emit_event(nfcReaderNexmosphereLabel3.text("-")) -- Default label 3 + stable_delay() + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("-")) + stable_delay() + if udp_client ~= nil then + udp_client:sendto("X" .. xtalkport .. "S[10:1]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "S[10:1] setting send to UDP controller") + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkport .. "S[19:2]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "S[19:2] setting send to UDP controller") + socket.sleep(0.5) + end + + elseif string.find(tostring(device), "Lidar") then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereOutputMode.supportedModes(supportedModesForLidar)) + stable_delay() + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereOutputMode.mode("Single Detection")) -- Default output mode for Lidar sensor + stable_delay() + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereNumberAndStatus.status("exit")) -- Default Zone status + stable_delay() + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereNumberAndStatus.number(0)) -- Default Zone number + stable_delay() + if udp_client ~= nil then + udp_client:sendto("X" .. xtalkport .. "S[4:1]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "S[4:1] setting send to UDP controller") + socket.sleep(0.5) + end + end +end + + +local function device_removed(driver, device) + log.info("Removing "..device.device_network_id) + + -- bookeeping of sensor device to xtalkport mapping when a a sensor device is added + local xtalkport + if string.find(device.device_network_id, controllerLabel .."%[(.-)%]", 0, false) then + local i, j = string.find(device.device_network_id, controllerLabel .."%[(.-)%]", 0, false) + xtalkport = string.sub(device.device_network_id, i+2, j-1) + if sensorSwap == false then + deviceForPort[xtalkport] = nil + if deviceController ~= nil then + deviceController:emit_event(xtalk[xtalkport].device("None")) + stable_delay() + end + end + log.info("deviceForPort " ..xtalkport.. " is " ..tostring(deviceForPort[xtalkport])) + end + sensorSwap = false +end + + +-- this is called both when a device is added (but after `added`) and after a hub reboots. +local function device_init(driver, device) + + log.info("device init " .. device.device_network_id) + + -- After hub reboots, check if Nexmosphere Controller device has been added + if string.find(tostring(device), "controller") then + controllerIP = device.preferences.controllerIP + controllerPort = tonumber(device.preferences.controllerPort) + deviceController = device + -- after hub reboots, check if the IP has been configured + -- if yes, autostart UDP communication + if controllerIP ~= "0.0.0.0" then + startUDPCommunication() + end + end + initiateSensors(driver, device) +end + +function initiateSensors(driver, device) + -- After hub reboots, check if Sensor device have been added + local xtalkport + if string.find(device.device_network_id, controllerLabel .. "%[(.-)%]", 0, false) then + log.info("device.device_network_id is " ..device.device_network_id) + local i, j = string.find(device.device_network_id, controllerLabel .."%[(.-)%]", 0, false) + xtalkport = string.sub(device.device_network_id, i+2, j-1) + deviceForPort[xtalkport] = device + log.info("deviceForPort " ..xtalkport.. " is " ..tostring(deviceForPort[xtalkport])) + log.info("[" .. device.id .. "] Initializing Nexmosphere device") + device:online() + end + + if string.find(tostring(device), "Presence") then + local settingsValue + + settingsValue = deviceForPort[xtalkport]:get_latest_state("main", genericSensorNexmosphereOutputMode.ID, "mode", "no value", "no value") + if settingsValue == "Distance zones" then + storedSettingPresenceMode[xtalkport] = "1" + elseif settingsValue == "Absolute distance" then + storedSettingPresenceMode[xtalkport] = "2" + end + + settingsValue = deviceForPort[xtalkport]:get_latest_state("main", presenceSensorNexmosphereChangeForTrigger.ID, "change", "no value", "no value") + storedSettingPresenceChangeForTrigger[xtalkport] = settingsValue + + settingsValue = deviceForPort[xtalkport]:get_latest_state("main", presenceSensorNexmosphereRangeOfInterest.ID, "range", "no value", "no value") + local i, j = string.find(settingsValue, "0") + local settingsValueForSensor= string.sub(settingsValue, 0, i-1) + storedSettingPresenceRangeOfInterest[xtalkport] = settingsValueForSensor + + + if udp_client ~= nil then + udp_client:sendto("X" .. xtalkport .. "S[4:" .. storedSettingPresenceMode[xtalkport] .."]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "S[4:" .. storedSettingPresenceMode[xtalkport] .."] setting send to UDP controller") + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkport .. "S[5:" .. storedSettingPresenceChangeForTrigger[xtalkport] .."]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "S[5:" .. storedSettingPresenceChangeForTrigger[xtalkport] .."] setting send to UDP controller") + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkport .. "S[6:" .. storedSettingPresenceRangeOfInterest[xtalkport] .."]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "S[6:" .. storedSettingPresenceRangeOfInterest[xtalkport] .."] setting send to UDP controller") + socket.sleep(0.5) + if settingsValue == "Distance zones" then + udp_client:sendto("X" .. xtalkport .. "B[ZONE?]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "B[ZONE?] request send to UDP controller") + socket.sleep(0.5) + elseif settingsValue == "Absolute distance" then + udp_client:sendto("X" .. xtalkport .. "B[DIST?]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "B[DIST?] request send to UDP controller") + socket.sleep(0.5) + end + end + + elseif string.find(tostring(device), "RFID") then + + elseif string.find(tostring(device), "Ambient") then + local settingsValue + settingsValue = deviceForPort[xtalkport]:get_latest_state("main", genericSensorNexmosphereOutputMode.ID, "mode", "no value", "no value") + if settingsValue == "Lux range" then + storedSettingAmbientMode[xtalkport] = "1" + elseif settingsValue == "Lux value" then + storedSettingAmbientMode[xtalkport] = "2" + end + + settingsValue = deviceForPort[xtalkport]:get_latest_state("main", ambientLightSensorNexmosphereChangeForTrigger.ID, "change", "no value", "no value") + storedSettingAmbientChangeForTrigger[xtalkport] = settingsValue + + if udp_client ~= nil then + udp_client:sendto("X" .. xtalkport .. "S[4:" .. storedSettingAmbientMode[xtalkport] .."]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "S[4:" .. storedSettingAmbientMode[xtalkport] .."] setting send to UDP controller") + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkport .. "S[5:" .. storedSettingAmbientChangeForTrigger[xtalkport] .."]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "S[5:" .. storedSettingAmbientChangeForTrigger[xtalkport] .."] setting send to UDP controller") + socket.sleep(0.5) + if settingsValue == "Lux value" then + udp_client:sendto("X" .. xtalkport .. "B[LUX?]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "B[LUX?] request send to UDP controller") + socket.sleep(0.5) + end + end + + elseif string.find(tostring(device), "Temperature") then + if udp_client ~= nil then + udp_client:sendto("X" .. xtalkport .. "S[4:3]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "S[4:3] setting send to UDP controller") + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkport .. "S[5:3]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "S[5:3] setting send to UDP controller") + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkport .. "B[TEMP?]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "B[TEMP?] setting send to UDP controller") + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkport .. "B[HUMI?]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "B[HUMI?] setting send to UDP controller") + socket.sleep(0.5) + end + + elseif string.find(tostring(device), "NFC") then + local settingsValue + settingsValue = deviceForPort[xtalkport]:get_latest_state("main", genericSensorNexmosphereOutputMode.ID, "mode", "no value", "no value") + + if settingsValue == "UID" then + storedSettingNFCMode[xtalkport] = "1" + elseif settingsValue == "Number" then + storedSettingNFCMode[xtalkport] = "2" + elseif settingsValue == "Label 1" then + storedSettingNFCMode[xtalkport] = "3" + elseif settingsValue == "Label 2" then + storedSettingNFCMode[xtalkport] = "4" + elseif settingsValue == "Label 3" then + storedSettingNFCMode[xtalkport] = "5" + elseif settingsValue == "UID, Number and Label 1" then + storedSettingNFCMode[xtalkport] = "6" + elseif settingsValue == "Label 1, 2 and 3" then + storedSettingNFCMode[xtalkport] = "7" + end + + if udp_client ~= nil then + udp_client:sendto("X" .. xtalkport .. "S[10:" .. storedSettingNFCMode[xtalkport] .."]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "S[10:" .. storedSettingNFCMode[xtalkport] .."] setting send to UDP controller") + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkport .. "S[19:2]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "S[19:2] setting send to UDP controller") + socket.sleep(0.5) + end + + elseif string.find(tostring(device), "Lidar") then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereOutputMode.mode("Single Detection")) -- Default output mode for Lidar sensor + stable_delay() + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereNumberAndStatus.status("exit")) -- Default Zone status + stable_delay() + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereNumberAndStatus.number(0)) -- Default Zone number + stable_delay() + if udp_client ~= nil then + udp_client:sendto("X" .. xtalkport .. "S[4:1]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "S[4:1] setting send to UDP controller") + socket.sleep(0.5) + end + end +end + +function startUDPCommunication() + if networkSettingsGenerated == false then -- only generate newSettings when a hub reboots + log.info("*** Create UDP socket ***") + udp_client = socket.udp() + udp_client:settimeout("1") + udp_client:setsockname('*', 0) + udp_client:setoption('broadcast', true) + log.info(udp_client) + log.info(udp_client:getsockname()) + hubIp, hubPort = udp_client:getsockname() + networkSettingsGenerated = true + end + + log.info("IP of Hub is " .. hubIp) + log.info("Port of Hub is " .. tostring(hubPort)) + log.info("IP of Controller is set to " .. controllerIP) + log.info("Port of Contoller is set to " .. controllerPort) + log.info("Intializing UDP communication settings of Nexmosphere controller") + deviceController:emit_event(hubIPSet.hubip(tostring(hubIp))) + stable_delay() + deviceController:emit_event(hubPortSet.hubport(tostring(hubPort))) + stable_delay() + + -- Set Nexmosphere controller Destination IP and port + udp_client:sendto("N000B[PORTOUT=".. hubPort .."]", controllerIP, controllerPort) + socket.sleep(0.5) + udp_client:sendto("N000B[DESTIP=".. hubIp .."]", controllerIP, controllerPort) + socket.sleep(0.5) + udp_client:sendto("N000B[SAVE!]", controllerIP, controllerPort) + socket.sleep(0.5) + + callOnSchedule = nexmoDriver:call_on_schedule(1, handle_udp_read_loop) + stable_delay() + + if UDPDebugMode == false then + deviceController:emit_event(connectionStatus.UDP("Connecting...")) + stable_delay() + else + -- To simulate the sensor value when no sensor is connected. + -- Always connected. + checkIfControllerAlive = true + noResponseCounter = 0 + settingsSend = true + noComCounter = 0 + deviceController:emit_event(connectionStatus.UDP("Connected")) + stable_delay() + end +end + +-- Called when changing settings in SmartThings mobile app +local function prefs_update(driver, device, event, args) + if args.old_st_store.preferences.controllerIP ~= device.preferences.controllerIP then + controllerIP = device.preferences.controllerIP + end + + if args.old_st_store.preferences.controllerPort ~= device.preferences.controllerPort then + controllerPort = tonumber(device.preferences.controllerPort) + end + + if args.old_st_store.preferences.setupCom ~= device.preferences.setupCom then + log.info(tostring(device.preferences.setupCom)) + end + + if (device.preferences.setupCom == "START") then + startUDPCommunication() + elseif (device.preferences.setupCom == "STOP" and udp_client ~= nil) then + log.info("close udp communication") + --udp_client:close() + nexmoDriver:cancel_timer(callOnSchedule) + deviceController:emit_event(connectionStatus.UDP("Unconnected")) + stable_delay() + settingsSend = false + end + + if args.old_st_store.preferences.setDebug ~= device.preferences.setDebug then + if (device.preferences.setDebug == "START") then + UDPDebugMode = true + print("Debug Mode ON") + elseif (device.preferences.setDebug == "STOP") then + UDPDebugMode = false + print("Debug Mode OFF") + end + end + + if string.find(tostring(device), "Lidar") then + print(driver, device, event, args) + + local k, l = string.find(device.device_network_id, controllerLabel .."%[(.-)%]", 0, false) + local xtalkport = string.sub(device.device_network_id, k+2, l-1) + + if args.old_st_store.preferences.RECALFOI ~= device.preferences.RECALFOI then + if (device.preferences.RECALFOI == "SET") then + print("ReCALFOI Callded") + -- Example input string + local input = device.preferences.DFOI + -- Table to store extracted coordinates + local coordinates = {} + -- Extract coordinates using pattern matching + for x, y in input:gmatch("%(?%s*([%-+]?%d+)%s*,%s*([%-+]?%d+)%s*%)") do + if #coordinates < 10 then + table.insert(coordinates, {x = tonumber(x), y = tonumber(y)}) + else + break + end + end + + -- Output the formatted result + for i, coord in ipairs(coordinates) do + -- Format with sing and 3 digits (e.g., +010, -005) + local x_str = string.format("%+04d", coord.x) + local y_str = string.format("%+04d", coord.y) + -- Format and print the output line + local index = string.format("%02d", i) + local foi = "X" .. xtalkport .. "B[FOICORNER" .. index .. "=" .. x_str .. "," .. y_str .. "]" + print(foi) + udp_client:sendto(foi, controllerIP, controllerPort) + socket.sleep(0.5) + end + + local recalfoi = "X" .. xtalkport .. "B[RECALCULATEFOI]" + print(recalfoi) + udp_client:sendto(recalfoi, controllerIP, controllerPort) + socket.sleep(0.5) + end + end + + if args.old_st_store.preferences.SETAZ ~= device.preferences.SETAZ then + if (device.preferences.SETAZ == "SET") then + print("setAZ Callded") + + -- Example input string + local input = device.preferences.DAZ + -- Table to store parsed data + local data_list = {} + + -- Extract up to 24 sets of (x, y, a, b) + for x, y, a, b in input:gmatch("%(?%s*([%-+]?%d+)%s*,%s*([%-+]?%d+)%s*,%s*([%-+]?%d+)%s*,%s*([%-+]?%d+)%s*%)") do + if #data_list < 24 then + table.insert(data_list, { + x = tonumber(x), + y = tonumber(y), + a = tonumber(a), + b = tonumber(b) + }) + else + break + end + end + + -- Format each entry + for i, item in ipairs(data_list) do + local x_str = string.format("%+04d", item.x) + local y_str = string.format("%+04d", item.y) + local a_str = string.format("%03d", item.a) + local b_str = string.format("%03d", item.b) + local index = string.format("%02d", i) + local activezone = "X" .. xtalkport .. "B[ZONE" .. index .. "=" .. x_str .. "," .. y_str .. "," .. a_str .. "," .. b_str .. "]" + print(activezone) + udp_client:sendto(activezone, controllerIP, controllerPort) + socket.sleep(0.5) + end + end + end + end +end + +local function initializeNFC(xtalkport) + deviceForPort[xtalkport]:emit_event(nfcReaderNexmosphereUID.UID("-", {state_change = true})) + stable_delay() + deviceForPort[xtalkport]:emit_event(nfcReaderNexmosphereTagNumber.number(0, {state_change = true})) + stable_delay() + deviceForPort[xtalkport]:emit_event(nfcReaderNexmosphereLabel1.text("-", {state_change = true})) + stable_delay() + deviceForPort[xtalkport]:emit_event(nfcReaderNexmosphereLabel2.text("-", {state_change = true})) + stable_delay() + deviceForPort[xtalkport]:emit_event(nfcReaderNexmosphereLabel3.text("-", {state_change = true})) + stable_delay() + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("-", {state_change = true})) + stable_delay() +end + +-- Interval loop for reading broadcasted messages +function handle_udp_read_loop() + log.info("Start UDP read loop") + + while true do + if consecutiveMessageCounter == 10 then + consecutiveMessageCounter = 0 + break + end + local data, dataIP, dataPort = udp_client:receivefrom() + local xtalkport + log.info(data) + + -- To simulate the sensor value when no sensor is connected. + -- Always connected. + log.info("UDP Debug Mode = " .. tostring(UDPDebugMode)) + if UDPDebugMode == true then + checkIfControllerAlive = true + noResponseCounter = 0 + noComCounter = 0 + end + +-- check X-talk address of data, so that events can be send to the correct device + if data ~= nil and string.find(data, "X(.-)%[", 0, false) then + local i, j = string.find(data, "X(.-)%[", 0, false) + xtalkport = string.sub(data, i+1, j-2) + if i+1 == j-1 then -- in case no X-talk address X00x but instead XR (RFID sensor) + xtalkport = "XR" + end + log.info("Incoming data from xtalk port " ..xtalkport.. " belonging to device " ..tostring(deviceForPort[xtalkport])) + consecutiveMessageCounter = consecutiveMessageCounter + 1 + noComCounter = 0 + end + + -- Data coming from device that has not yet been created. + if data ~= nil and xtalkport ~= nil and xtalkport ~= "XR" and deviceForPort[xtalkport] == nil then + log.info("Incoming data from a sensor for which no device has been created (yet)") + + -- Presence sensor + -- Distance in zones + elseif data ~= nil and string.find(data, "Dz=") then + local i, j = string.find(data, "Dz=") + local sensorValue = string.sub(data, j+1, j+2) + deviceForPort[xtalkport]:emit_event(presenceSensorNexmosphereDistanceZone.distanceZone(sensorValue)) + stable_delay() + if sensorValue == "AB" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("0-10 cm, zone AB")) -- Summary for Presence sensor + elseif sensorValue == "01" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("11-25 cm, zone 01")) -- Summary for Presence sensor + elseif sensorValue == "02" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("25-50 cm, zone 02")) -- Summary for Presence sensor + elseif sensorValue == "03" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("50-75 cm, zone 03")) -- Summary for Presence sensor + elseif sensorValue == "04" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("75-100 cm, zone 04")) -- Summary for Presence sensor + elseif sensorValue == "05" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("100-125 cm, zone 05")) -- Summary for Presence sensor + elseif sensorValue == "06" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("125-150 cm, zone 06")) -- Summary for Presence sensor + elseif sensorValue == "07" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("150-175 cm, zone 07")) -- Summary for Presence sensor + elseif sensorValue == "08" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("175-200 cm, zone 08")) -- Summary for Presence sensor + elseif sensorValue == "09" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("200-225 cm, zone 09")) -- Summary for Presence sensor + elseif sensorValue == "10" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("225-250 cm, zone 10")) -- Summary for Presence sensor + elseif sensorValue == "XX" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("Out of range, zone XX")) -- Summary for Presence sensor + end + stable_delay() + + -- Distance in absolute values + elseif data ~= nil and string.find(data, "Dv=") then + local i, j = string.find(data, "Dv=") + local sensorValue = string.sub(data, j+1, j+3) + local sensorValueNumber = tonumber(sensorValue) + + if sensorValue == "XXX" then + deviceForPort[xtalkport]:emit_event(presenceSensorNexmosphereAbsoluteDistance.absoluteDistance(251)) + stable_delay() + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("Out of range, zone XX")) -- Summary for Presence sensor + else + deviceForPort[xtalkport]:emit_event(presenceSensorNexmosphereAbsoluteDistance.absoluteDistance(tonumber(sensorValue))) + stable_delay() + + if sensorValueNumber >= 0 and sensorValueNumber <= 10 then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("".. sensorValue .." cm, zone AB")) + elseif sensorValueNumber >= 11 and sensorValueNumber <= 25 then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("".. sensorValue .." cm, zone 01")) + elseif sensorValueNumber >= 26 and sensorValueNumber <= 50 then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("".. sensorValue .." cm, zone 02")) + elseif sensorValueNumber >= 51 and sensorValueNumber <= 75 then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("".. sensorValue .." cm, zone 03")) + elseif sensorValueNumber >= 76 and sensorValueNumber <= 100 then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("".. sensorValue .." cm, zone 04")) + elseif sensorValueNumber >= 101 and sensorValueNumber <= 125 then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("".. sensorValue .." cm, zone 05")) + elseif sensorValueNumber >= 126 and sensorValueNumber <= 150 then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("".. sensorValue .." cm, zone 06")) + elseif sensorValueNumber >= 151 and sensorValueNumber <= 175 then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("".. sensorValue .." cm, zone 07")) + elseif sensorValueNumber >= 176 and sensorValueNumber <= 200 then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("".. sensorValue .." cm, zone 08")) + elseif sensorValueNumber >= 201 and sensorValueNumber <= 225 then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("".. sensorValue .." cm, zone 09")) + elseif sensorValueNumber >= 226 and sensorValueNumber <= 250 then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("".. sensorValue .." cm, zone 10")) + end + stable_delay() + end + + -- RFID sensor + + -- Tag Lifted + elseif data ~= nil and string.find(data, "PU") then + local i, j = string.find(data, "PU") + local sensorValue = string.sub(data, j+1, j+3) + + -- In case of multiple RFID sensors, these sensors will act as 1 sensor. + -- This means all RFID sensors get updated in case a tag is lifted or placed. + -- IMPROVE: try to implement mechanism that RFID sensors can work as separate units + for i = 1, 8 do + local xtalkportRFID = tostring("00" ..i) + if string.find(tostring(deviceForPort[xtalkportRFID]), "RFID") then + deviceForPort[xtalkportRFID]:emit_event(genericSensorNexmosphereNumberAndStatus.number(tonumber(sensorValue))) + stable_delay() + deviceForPort[xtalkportRFID]:emit_event(genericSensorNexmosphereNumberAndStatus.status("lifted")) + stable_delay() + end + end + + -- Tag Placed + elseif data ~= nil and string.find(data, "PB") then + local i, j = string.find(data, "PB") + local sensorValue = string.sub(data, j+1, j+3) + + -- In case of multiple RFID sensors, these sensors will act as 1 sensor. + -- This means all RFID sensors get updated in case a tag is lifted or placed. + -- IMPROVE: try to implement mechanism that RFID sensors can work as separate units + + for i = 1, 8 do + local xtalkportRFID = tostring("00" ..i) + if string.find(tostring(deviceForPort[xtalkportRFID]), "RFID") then + deviceForPort[xtalkportRFID]:emit_event(genericSensorNexmosphereNumberAndStatus.number(tonumber(sensorValue))) + stable_delay() + deviceForPort[xtalkportRFID]:emit_event(genericSensorNexmosphereNumberAndStatus.status("placed")) + stable_delay() + end + end + -- Ambient light sensor + + -- Lux Value + elseif data ~= nil and string.find(data, "Av=") then + local i, j = string.find(data, "Av=") + local sensorValue = string.sub(data, j+1, j+6) + local sensorValueNumber = tonumber(sensorValue) + deviceForPort[xtalkport]:emit_event(ambientLightSensorNexmosphereLuxValue.luxValue(tonumber(sensorValue))) + stable_delay() + if sensorValueNumber >= 0 and sensorValueNumber <= 1 then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("".. sensorValue .." lux, range 1")) + elseif sensorValueNumber >= 2 and sensorValueNumber <= 50 then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("".. sensorValue .." lux, range 2")) + elseif sensorValueNumber >= 51 and sensorValueNumber <= 250 then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("".. sensorValue .." lux, range 3")) + elseif sensorValueNumber >= 251 and sensorValueNumber <= 1000 then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("".. sensorValue .." lux, range 4")) + elseif sensorValueNumber >= 1000 and sensorValueNumber <= 5000 then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("".. sensorValue .." lux, range 5")) + elseif sensorValueNumber >= 5000 and sensorValueNumber <= 15000 then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("".. sensorValue .." lux, range 6")) + elseif sensorValueNumber >= 15000 and sensorValueNumber <= 40000 then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("".. sensorValue .." lux, range 7")) + elseif sensorValueNumber >= 40000 and sensorValueNumber <= 80000 then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("".. sensorValue .." lux, range 8")) + elseif sensorValueNumber >= 80000 and sensorValueNumber <= 120000 then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("".. sensorValue .." lux, range 9")) + end + stable_delay() + + -- Lux Range + elseif data ~= nil and string.find(data, "Ar=") then + local i, j = string.find(data, "Ar=") + local sensorValue = string.sub(data, j+1, j+1) + deviceForPort[xtalkport]:emit_event(ambientLightSensorNexmosphereLuxRange.luxRange(tonumber(sensorValue))) + stable_delay() + if sensorValue == "1" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("0-1 lux, range 1")) + elseif sensorValue == "2" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("1-50 lux, range 2")) + elseif sensorValue == "3" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("50-250 lux, range 3")) + elseif sensorValue == "4" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("250-1K lux, range 4")) + elseif sensorValue == "5" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("1K-5K lux, range 5")) + elseif sensorValue == "6" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("5K-15K lux, range 6")) + elseif sensorValue == "7" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("15K-40K lux, range 7")) + elseif sensorValue == "8" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("50K-80K lux, range 8")) + elseif sensorValue == "9" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text("80K-120K lux, range 9")) + end + stable_delay() + -- Temperature and Humidity sensor + + -- Temperature measurement + elseif data ~= nil and string.find(data, "Tv=") then + local i, j = string.find(data, "Tv=") + local rawSensorValue = string.sub(data, j+1, j+5) + local sensorValue = rawSensorValue:gsub( ",", ".") + deviceForPort[xtalkport]:emit_event(tempHumiSensorNexmosphereTemperatureMeasurement.temperature({value = tonumber(sensorValue), unit = "C"})) + stable_delay() + + -- Humidity Measurement + elseif data ~= nil and string.find(data, "Hv=") then + local i, j = string.find(data, "Hv=") + local sensorValue = string.sub(data, j+1, j+2) + deviceForPort[xtalkport]:emit_event(tempHumiSensorNexmosphereHumidityMeasurement.humidity({value = tonumber(sensorValue), unit = "%"})) + stable_delay() + + + -- NFC Reader + + -- UID + elseif data ~= nil and string.find(data, "TD=UID:") then + local i, j = string.find(data, "TD=UID:") + local sensorValue = string.sub(data, j+1, j+14) + print(data) + deviceForPort[xtalkport]:emit_event(nfcReaderNexmosphereUID.UID(sensorValue)) + stable_delay() + nfcUID = sensorValue + + -- Tag number + elseif data ~= nil and string.find(data, "TD=TNR:") then + local i, j = string.find(data, "TD=TNR:") + local sensorValue = string.sub(data, j+1, j+5) + deviceForPort[xtalkport]:emit_event(nfcReaderNexmosphereTagNumber.number(tonumber(sensorValue))) + stable_delay() + nfcNumber = sensorValue + + + -- Text label 1 + elseif data ~= nil and string.find(data, "TD=LB1:") then + local i, j = string.find(data, "TD=LB1:") + local k, l = string.find(data, "]") + local sensorValue = string.sub(data, j+1, k-1) + if j+1 == k then + sensorValue = "" + end + deviceForPort[xtalkport]:emit_event(nfcReaderNexmosphereLabel1.text(sensorValue)) + stable_delay() + nfcLabel1 = sensorValue + + + -- Text label 2 + elseif data ~= nil and string.find(data, "TD=LB2:") then + local i, j = string.find(data, "TD=LB2:") + local k, l = string.find(data, "]") + local sensorValue = string.sub(data, j+1, k-1) + if j+1 == k then + sensorValue = "" + end + deviceForPort[xtalkport]:emit_event(nfcReaderNexmosphereLabel2.text(sensorValue)) + stable_delay() + nfcLabel2 = sensorValue + + + -- Text label 3 + elseif data ~= nil and string.find(data, "TD=LB3:") then + local i, j = string.find(data, "TD=LB3:") + local k, l = string.find(data, "]") + local sensorValue = string.sub(data, j+1, k-1) + if j+1 == k then + sensorValue = "" + end + deviceForPort[xtalkport]:emit_event(nfcReaderNexmosphereLabel3.text(sensorValue)) + stable_delay() + nfcLabel3 = sensorValue + + -- update Summary on tag removal + elseif data ~= nil and string.find(data, "TR=") then + initializeNFC(xtalkport) + + + + -- Lidar sensor + -- Zone enter + elseif data ~= nil and string.find(data, "ENTER") then + local i, j = string.find(data, "ZONE") + local sensorValue = string.sub(data, j+1, j+2) + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereNumberAndStatus.number(tonumber(sensorValue))) + stable_delay() + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereNumberAndStatus.status("enter")) + stable_delay() + + -- Zone exit + elseif data ~= nil and string.find(data, "EXIT") then + local i, j = string.find(data, "ZONE") + local sensorValue = string.sub(data, j+1, j+2) + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereNumberAndStatus.number(tonumber(sensorValue))) + stable_delay() + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereNumberAndStatus.status("exit")) + stable_delay() + + + -- Check if controller alive at startup + elseif data == nil and settingsSend == false then + udp_client:sendto("N000B[IDENTIFY=ALL]", controllerIP, controllerPort) + log.info("Identifying Nexmosphere UDP controllers...") + break + + -- Check if controller alive at startup + elseif data == nil and checkIfControllerAlive == true then + udp_client:sendto("N000B[MAC?]", controllerIP, controllerPort) + log.info("Checking if controller is still alive...") + noResponseCounter = noResponseCounter + 1 + + if noResponseCounter > 2 then + settingsSend = false + checkIfControllerAlive = false + noResponseCounter = 0 + deviceController:emit_event(connectionStatus.UDP("Connection lost. Trying to reconnect...")) + stable_delay() + end + break + + elseif data ~= nil and string.find(data, "MAC") then + checkIfControllerAlive = false + noResponseCounter = 0 + noComCounter = 0 + log.info("Controller connection verified") + + -- Send sensor settings once at startup + elseif data ~= nil and string.find(data, "N000B[IP=", 1, true) then + log.info("*** UDP Controller Identified ***") + log.info("Sending sensor settings in case:") + log.info("- sensors were added before UDP connection was alive") + log.info("- the Nexmosphere controller repowered") + + deviceController:emit_event(connectionStatus.UDP("Initiating settings")) + stable_delay() + + for i = 1, 8 do + + local xtalkportSet = tostring("00" ..i) + + if string.find(tostring(deviceForPort[xtalkportSet]), "Presence") then + udp_client:sendto("X" .. xtalkportSet .. "S[4:"..storedSettingPresenceMode[xtalkportSet].."]", controllerIP, controllerPort) + log.info("X" .. xtalkportSet .. "S[4:"..storedSettingPresenceMode[xtalkportSet].."] setting send to UDP controller") + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkportSet .. "S[5:"..storedSettingPresenceChangeForTrigger[xtalkportSet].."]", controllerIP, controllerPort) + log.info("X" .. xtalkportSet .. "S[5:"..storedSettingPresenceChangeForTrigger[xtalkportSet].."] setting send to UDP controller") + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkportSet .. "S[6:"..storedSettingPresenceRangeOfInterest[xtalkportSet].."]", controllerIP, controllerPort) + log.info("X" .. xtalkportSet .. "S[6:"..storedSettingPresenceRangeOfInterest[xtalkportSet].."] setting send to UDP controller") + socket.sleep(0.5) + if storedSettingPresenceMode[xtalkportSet] == "1" then + udp_client:sendto("X" .. xtalkportSet .. "B[ZONE?]", controllerIP, controllerPort) + log.info("X" .. xtalkportSet .. "B[ZONE?] request send to UDP controller") + socket.sleep(0.5) + elseif storedSettingPresenceMode[xtalkportSet] == "2" then + udp_client:sendto("X" .. xtalkportSet .. "B[DIST?]", controllerIP, controllerPort) + log.info("X" .. xtalkportSet .. "B[DIST?] request send to UDP controller") + socket.sleep(0.5) + + end + elseif string.find(tostring(deviceForPort[xtalkportSet]), "Ambient") then + udp_client:sendto("X" .. xtalkportSet .. "S[4:"..storedSettingAmbientMode[xtalkportSet].."]", controllerIP, controllerPort) + log.info("X" .. xtalkportSet .. "S[4:"..storedSettingAmbientMode[xtalkportSet].."] setting send to UDP controller") + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkportSet .. "S[5:"..storedSettingAmbientChangeForTrigger[xtalkportSet].."]", controllerIP, controllerPort) + log.info("X" .. xtalkportSet .. "S[5:"..storedSettingAmbientChangeForTrigger[xtalkportSet].."] setting send to UDP controller") + socket.sleep(0.5) + if storedSettingAmbientMode[xtalkportSet] == "2" then + udp_client:sendto("X" .. xtalkportSet .. "B[LUX?]", controllerIP, controllerPort) + log.info("X" .. xtalkportSet .. "B[LUX?] request send to UDP controller") + socket.sleep(0.5) + end + elseif string.find(tostring(deviceForPort[xtalkportSet]), "Temperature") then + udp_client:sendto("X" .. xtalkportSet .. "S[4:3]", controllerIP, controllerPort) + log.info("X" .. xtalkportSet .. "S[4:3] setting send to UDP controller") + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkportSet .. "S[5:3]", controllerIP, controllerPort) + log.info("X" .. xtalkportSet .. "S[5:3] setting send to UDP controller") + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkportSet .. "B[TEMP?]", controllerIP, controllerPort) + log.info("X" .. xtalkportSet .. "B[TEMP?] request send to UDP controller") + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkportSet .. "B[HUMI?]", controllerIP, controllerPort) + log.info("X" .. xtalkportSet .. "B[HUMI?] request send to UDP controller") + socket.sleep(0.5) + elseif string.find(tostring(deviceForPort[xtalkportSet]), "NFC") then + udp_client:sendto("X" .. xtalkportSet .. "S[10:" .. storedSettingNFCMode[xtalkportSet] .."]", controllerIP, controllerPort) + log.info("X" .. xtalkportSet .. "S[10:" .. storedSettingNFCMode[xtalkportSet] .."] setting send to UDP controller") + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkportSet .. "S[19:2]", controllerIP, controllerPort) + log.info("X" .. xtalkportSet .. "S[19:2] setting send to UDP controller") + socket.sleep(0.5) + elseif string.find(tostring(deviceForPort[xtalkportSet]), "Lidar") then + udp_client:sendto("X" .. xtalkportSet .. "S[4:1]", controllerIP, controllerPort) + log.info("X" .. xtalkportSet .. "S[4:1] setting send to UDP controller") + socket.sleep(0.5) + end + end + settingsSend = true + noComCounter = 0 + deviceController:emit_event(connectionStatus.UDP("Connected")) + stable_delay() + + else + consecutiveMessageCounter = 0 + + if settingsSend == true and noComCounter >= 5 and checkIfControllerAlive == false then + checkIfControllerAlive = true + else + noComCounter = noComCounter + 1 + end + break + end + + --NFC Summary + if data ~= nil and xtalkport ~= nil and string.find(data, "TD=") then + local settingsValue = deviceForPort[xtalkport]:get_latest_state("main", genericSensorNexmosphereOutputMode.ID, "mode", "no value", "no value") + if settingsValue == "UID" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text(nfcUID)) + elseif settingsValue == "Number" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text(nfcNumber)) + elseif settingsValue == "Label 1" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text(nfcLabel1)) + elseif settingsValue == "Label 2" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text(nfcLabel2)) + elseif settingsValue == "Label 3" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text(nfcLabel3)) + elseif settingsValue == "UID, Number and Label 1" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text(nfcUID.." / "..nfcNumber.." / "..nfcLabel1)) + elseif settingsValue == "Label 1, 2 and 3" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereSummary.text(nfcLabel1.." / "..nfcLabel2.." / "..nfcLabel3)) + end + stable_delay() + end + end +end + + + +local cap2xtalk = { + [xtalk["001"].ID] = "001", + [xtalk["002"].ID] = "002", + [xtalk["003"].ID] = "003", + [xtalk["004"].ID] = "004", + [xtalk["005"].ID] = "005", + [xtalk["006"].ID] = "006", + [xtalk["007"].ID] = "007", + [xtalk["008"].ID] = "008", + +} + + +function handler_xtalk(driver, device, command) + log.info("handler_xtalk") + log.debug(command) + + local newdevice = command.args.device + device:emit_event(capabilities[command.capability].device(newdevice, {state_change = true})) + stable_delay() + + local xtalk + xtalk = cap2xtalk[command.capability] + + if newdevice == "Presence" then + local metadata_Presence = { + type = "LAN", + device_network_id = controllerLabel .."[" .. xtalk .. "] NEX_XY241", + label = controllerLabel .."[" .. xtalk .. "]" .. " Presence sensor", + profile = "XY200SERIES.v5", + manufacturer = "Nexmosphere", + model = "v1", + vendor_provided_label = nil + } + driver:try_create_device(metadata_Presence) + + elseif newdevice == "RFID" then + local metadata_rfid = { + type = "LAN", + device_network_id = controllerLabel .."[" .. xtalk .. "] NEX_XRDR1", + label = controllerLabel .."[" .. xtalk .. "]" .. " RFID sensor", + profile = "XRDR1.v4", + manufacturer = "Nexmosphere", + model = "v1", + vendor_provided_label = nil + } + driver:try_create_device(metadata_rfid) + + + elseif newdevice == "Ambient light" then + local metadata_ambientlight = { + type = "LAN", + device_network_id = controllerLabel .."[" .. xtalk .. "] NEX_XEA20", + label = controllerLabel .."[" .. xtalk .. "] Ambient light sensor", + profile = "XEA20.v4", + manufacturer = "Nexmosphere", + model = "v1", + vendor_provided_label = nil + } + driver:try_create_device(metadata_ambientlight) + + elseif newdevice == "Temperature" then + local metadata_temphumi = { + type = "LAN", + device_network_id = controllerLabel .."[" .. xtalk .. "] NEX_XET50", + label = controllerLabel .."[" .. xtalk .. "] Temperature sensor", + profile = "XET50.v4", + manufacturer = "Nexmosphere", + model = "v1", + vendor_provided_label = nil + } + driver:try_create_device(metadata_temphumi) + + elseif newdevice == "NFC" then + local metadata_nfc = { + type = "LAN", + device_network_id = controllerLabel .. "[" .. xtalk .. "] NEX_XRDR2", + label = controllerLabel .. "[" .. xtalk .. "] NFC Reader", + profile = "XRDR2.v4", + manufacturer = "Nexmosphere", + model = "v1", + vendor_provided_label = nil + } + driver:try_create_device(metadata_nfc) + + + elseif newdevice == "Lidar" then + local metadata_lidar = { + type = "LAN", + device_network_id = controllerLabel .."[" .. xtalk .. "]" .. "NEX_XQL2", + label = controllerLabel .."[" .. xtalk .. "]" .. " Lidar sensor", + profile = "XQL2.v6", + manufacturer = "Nexmosphere", + model = "v1", + vendor_provided_label = nil + } + driver:try_create_device(metadata_lidar) + + + elseif newdevice == "None" then + if deviceForPort[xtalk] ~= nil then + log.info("trying to delete device " ..deviceForPort[xtalk].id) + driver:try_delete_device(deviceForPort[xtalk].id) + end + end +end + +function set_Mode_handler(driver, device, command) + log.debug("set_Mode_handler") + + local k, l = string.find(device.device_network_id, controllerLabel .."%[(.-)%]", 0, false) + local xtalkport = string.sub(device.device_network_id, k+2, l-1) + +--Presence sensor + if command.args.mode == "Distance zones" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereOutputMode.mode("Distance zones", {state_change = true})) + stable_delay() + udp_client:sendto("X" .. xtalkport .. "S[4:1]", controllerIP, controllerPort) + storedSettingPresenceMode[xtalkport] = "1" + end + + if command.args.mode == "Absolute distance" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereOutputMode.mode("Absolute distance", {state_change = true})) + stable_delay() + udp_client:sendto("X" .. xtalkport .. "S[4:2]", controllerIP, controllerPort) + storedSettingPresenceMode[xtalkport] = "2" + end + +--Ambient light sensor + if command.args.mode == "Lux value" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereOutputMode.mode("Lux value", {state_change = true})) + stable_delay() + udp_client:sendto("X" .. xtalkport .. "S[4:2]", controllerIP, controllerPort) + storedSettingAmbientMode[xtalkport] = "2" + end + + if command.args.mode == "Lux range" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereOutputMode.mode("Lux range", {state_change = true})) + stable_delay() + udp_client:sendto("X" .. xtalkport .. "S[4:1]", controllerIP, controllerPort) + storedSettingAmbientMode[xtalkport] = "1" + end + +--NFC Reader + if command.args.mode == "UID" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereOutputMode.mode("UID", {state_change = true})) + stable_delay() + initializeNFC(xtalkport) + udp_client:sendto("X" .. xtalkport .. "S[10:1]", controllerIP, controllerPort) + storedSettingNFCMode[xtalkport] = "1" + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkport .. "B[UID?]", controllerIP, controllerPort) + end + + if command.args.mode == "Number" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereOutputMode.mode("Number", {state_change = true})) + stable_delay() + initializeNFC(xtalkport) + udp_client:sendto("X" .. xtalkport .. "S[10:2]", controllerIP, controllerPort) + storedSettingNFCMode[xtalkport] = "2" + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkport .. "B[TNR?]", controllerIP, controllerPort) + end + + if command.args.mode == "Label 1" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereOutputMode.mode("Label 1", {state_change = true})) + stable_delay() + initializeNFC(xtalkport) + udp_client:sendto("X" .. xtalkport .. "S[10:3]", controllerIP, controllerPort) + storedSettingNFCMode[xtalkport] = "3" + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkport .. "B[LB1?]", controllerIP, controllerPort) + end + + if command.args.mode == "Label 2" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereOutputMode.mode("Label 2", {state_change = true})) + stable_delay() + initializeNFC(xtalkport) + udp_client:sendto("X" .. xtalkport .. "S[10:4]", controllerIP, controllerPort) + storedSettingNFCMode[xtalkport] = "4" + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkport .. "B[LB2?]", controllerIP, controllerPort) + end + + if command.args.mode == "Label 3" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereOutputMode.mode("Label 3", {state_change = true})) + stable_delay() + initializeNFC(xtalkport) + initializeNFC(xtalkport) + udp_client:sendto("X" .. xtalkport .. "S[10:5]", controllerIP, controllerPort) + storedSettingNFCMode[xtalkport] = "5" + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkport .. "B[LB3?]", controllerIP, controllerPort) + end + + if command.args.mode == "UID, Number and Label 1" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereOutputMode.mode("UID, Number and Label 1", {state_change = true})) + stable_delay() + initializeNFC(xtalkport) + udp_client:sendto("X" .. xtalkport .. "S[10:6]", controllerIP, controllerPort) + storedSettingNFCMode[xtalkport] = "6" + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkport .. "B[UID?]", controllerIP, controllerPort) + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkport .. "B[TNR?]", controllerIP, controllerPort) + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkport .. "B[LB1?]", controllerIP, controllerPort) + end + + if command.args.mode == "Label 1, 2 and 3" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereOutputMode.mode("Label 1, 2 and 3", {state_change = true})) + stable_delay() + initializeNFC(xtalkport) + udp_client:sendto("X" .. xtalkport .. "S[10:7]", controllerIP, controllerPort) + storedSettingNFCMode[xtalkport] = "7" + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkport .. "B[LB1?]", controllerIP, controllerPort) + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkport .. "B[LB2?]", controllerIP, controllerPort) + socket.sleep(0.5) + udp_client:sendto("X" .. xtalkport .. "B[LB3?]", controllerIP, controllerPort) + end + + --Lidar sensor + if command.args.mode == "Single Detection" then + stable_delay() + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereOutputMode.mode("Single Detection", {state_change = true})) + udp_client:sendto("X" .. xtalkport .. "S[4:1]", controllerIP, controllerPort) + end + + if command.args.mode == "Multi Detection" then + deviceForPort[xtalkport]:emit_event(genericSensorNexmosphereOutputMode.mode("Multi Detection", {state_change = true})) + stable_delay() + udp_client:sendto("X" .. xtalkport .. "S[4:2]", controllerIP, controllerPort) + end + +end + + +function set_Change_handler_presence(driver, device, command) + log.debug("set_Change_handler_presence") + + local k, l = string.find(device.device_network_id, controllerLabel .."%[(.-)%]", 0, false) + local xtalkport = string.sub(device.device_network_id, k+2, l-1) + + --Presence sensor + local settingValue = command.args.change + deviceForPort[xtalkport]:emit_event(presenceSensorNexmosphereChangeForTrigger.change({value = tonumber(settingValue), unit = "cm"})) + stable_delay() + udp_client:sendto("X" .. xtalkport .. "S[5:".. settingValue .."]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "S[5:".. settingValue .."] setting send to UDP controller") + storedSettingPresenceChangeForTrigger[xtalkport] = settingValue +end + + +function set_Change_handler_ambient(driver, device, command) + log.debug("set_Change_handler_ambient") + + local k, l = string.find(device.device_network_id, controllerLabel .."%[(.-)%]", 0, false) + local xtalkport = string.sub(device.device_network_id, k+2, l-1) + + --Ambient light sensor + local settingValue = command.args.change + deviceForPort[xtalkport]:emit_event(ambientLightSensorNexmosphereChangeForTrigger.change({value = tonumber(settingValue), unit = "%"})) + stable_delay() + udp_client:sendto("X" .. xtalkport .. "S[5:".. settingValue .."]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "S[5:".. settingValue .."] setting send to UDP controller") + storedSettingAmbientChangeForTrigger[xtalkport] = settingValue +end + + + +function set_Range_handler_presence(driver, device, command) + log.debug("set_Range_handler_presence") + + local k, l = string.find(device.device_network_id, controllerLabel .."%[(.-)%]", 0, false) + local xtalkport = string.sub(device.device_network_id, k+2, l-1) + + local rawSettingValue = command.args.range + local i, j = string.find(rawSettingValue, "0") + local settingValue = string.sub(rawSettingValue, 0, i-1) + deviceForPort[xtalkport]:emit_event(presenceSensorNexmosphereRangeOfInterest.range(tonumber(rawSettingValue))) + stable_delay() + udp_client:sendto("X" .. xtalkport .. "S[6:".. settingValue .."]", controllerIP, controllerPort) + log.info("X" .. xtalkport .. "S[6:".. settingValue .."] setting send to UDP controller") + storedSettingPresenceRangeOfInterest[xtalkport] = settingValue +end + + +function refresh_handler(driver, device, command) + log.debug("refresh_handler") + initiateSensors(driver, device) +end + + +-- create the driver object +nexmoDriver = Driver("NexmoSensors", { + discovery = discovery.handle_discovery, + lifecycle_handlers = { + added = device_added, + init = device_init, + removed = device_removed, + infoChanged = prefs_update + }, + capability_handlers = { + [xtalk["001"].ID] = { + [xtalk["001"].commands.setDevice.NAME] = handler_xtalk + }, + [xtalk["002"].ID] = { + [xtalk["002"].commands.setDevice.NAME] = handler_xtalk + }, + [xtalk["003"].ID] = { + [xtalk["003"].commands.setDevice.NAME] = handler_xtalk + }, + [xtalk["004"].ID] = { + [xtalk["004"].commands.setDevice.NAME] = handler_xtalk + }, + [xtalk["005"].ID] = { + [xtalk["005"].commands.setDevice.NAME] = handler_xtalk + }, + [xtalk["006"].ID] = { + [xtalk["006"].commands.setDevice.NAME] = handler_xtalk + }, + [xtalk["007"].ID] = { + [xtalk["007"].commands.setDevice.NAME] = handler_xtalk + }, + [xtalk["008"].ID] = { + [xtalk["008"].commands.setDevice.NAME] = handler_xtalk + }, + + [genericSensorNexmosphereOutputMode.ID] = { + [genericSensorNexmosphereOutputMode.commands.setMode.NAME] = set_Mode_handler + }, + [presenceSensorNexmosphereChangeForTrigger.ID] = { + [presenceSensorNexmosphereChangeForTrigger.commands.setChange.NAME] = set_Change_handler_presence + }, + [ambientLightSensorNexmosphereChangeForTrigger.ID] = { + [ambientLightSensorNexmosphereChangeForTrigger.commands.setChange.NAME] = set_Change_handler_ambient + }, + [presenceSensorNexmosphereRangeOfInterest.ID] = { + [presenceSensorNexmosphereRangeOfInterest.commands.setRange.NAME] = set_Range_handler_presence + }, + [genericSensorNexmosphereRefresh.ID] = { + [genericSensorNexmosphereRefresh.commands.refresh.NAME] = refresh_handler + } + } +}) + +-- run the driver +nexmoDriver:run() diff --git a/drivers/SmartThings/jbl/src/discovery.lua b/drivers/SmartThings/jbl/src/discovery.lua index b698ca66b1..8efdf97bbe 100644 --- a/drivers/SmartThings/jbl/src/discovery.lua +++ b/drivers/SmartThings/jbl/src/discovery.lua @@ -1,101 +1,101 @@ -local log = require "log" - -local fields = require "fields" -local discovery_mdns = require "discovery_mdns" - -local socket = require "cosock.socket" -local st_utils = require "st.utils" - -local discovery = {} - --- mapping from device DNI to info needed at discovery/init time -local joined_device = {} - -function discovery.set_device_field(driver, device) - - log.info(string.format("set_device_field : %s", device.device_network_id)) - local device_cache_value = driver.datastore.discovery_cache[device.device_network_id] - - -- persistent fields - device:set_field(fields.DEVICE_IPV4, device_cache_value.ip, {persist = true}) - device:set_field(fields.CREDENTIAL, device_cache_value.credential , {persist = true}) - device:set_field(fields.DEVICE_INFO, device_cache_value.device_info , {persist = true}) - driver.datastore.discovery_cache[device.device_network_id] = nil -end - -local function update_device_discovery_cache(driver, dni, ip, credential) - log.info(string.format("update_device_discovery_cache for device dni: %s, %s", dni, ip)) - local device_info = driver.discovery_helper.get_device_info(driver, dni, ip) - driver.datastore.discovery_cache[dni] = { - ip = ip, - device_info = device_info, - credential = credential, - } -end - -local function try_add_device(driver, device_dni, device_ip) - log.trace(string.format("try_add_device : dni=%s, ip=%s", device_dni, device_ip)) - - local credential = driver.discovery_helper.get_credential(driver, device_dni, device_ip) - - if not credential then - log.error(string.format("failed to get credential. dni=%s, ip=%s", device_dni, device_ip)) - joined_device[device_dni] = nil - return - end - - update_device_discovery_cache(driver, device_dni, device_ip, credential) - local create_device_msg = driver.discovery_helper.get_device_create_msg(driver, device_dni, device_ip) - driver:try_create_device(create_device_msg) -end - -function discovery.device_added(driver, device) - log.info("device_added : dni = " .. tostring(device.device_network_id)) - - discovery.set_device_field(driver, device) - joined_device[device.device_network_id] = nil - driver.lifecycle_handlers.init(driver, device) -end - -function discovery.find_ip_table(driver) - local ip_table= discovery_mdns.find_ip_table_by_mdns(driver) - return ip_table -end - - -local function discovery_device(driver) - local known_devices = {} - - for _, device in pairs(driver:get_devices()) do - known_devices[device.device_network_id] = device - end - - local ip_table = discovery.find_ip_table(driver) - - log.debug(st_utils.stringify_table(ip_table, "DNI IP Table after processing mDNS Discovery Response", true)) - - for dni, ip in pairs(ip_table) do - log.info(string.format("discovery_device dni, ip = %s, %s", dni, ip)) - if not known_devices or not known_devices[dni] then - log.trace(string.format("unknown dni= %s, ip= %s", dni, ip)) - if not joined_device[dni] then - try_add_device(driver, dni, ip) - joined_device[dni] = true - end - else - log.trace(string.format("known dni= %s, ip= %s", dni, ip)) - end - end -end - -function discovery.do_network_discovery(driver, _, should_continue) - log.info("discovery.do_network_discovery :Starting mDNS discovery") - - while should_continue() do - discovery_device(driver) - socket.sleep(0.2) - end - log.info("discovery.do_network_discovery: Ending mDNS discovery") -end - -return discovery +local log = require "log" + +local fields = require "fields" +local discovery_mdns = require "discovery_mdns" + +local socket = require "cosock.socket" +local st_utils = require "st.utils" + +local discovery = {} + +-- mapping from device DNI to info needed at discovery/init time +local joined_device = {} + +function discovery.set_device_field(driver, device) + + log.info(string.format("set_device_field : %s", device.device_network_id)) + local device_cache_value = driver.datastore.discovery_cache[device.device_network_id] + + -- persistent fields + device:set_field(fields.DEVICE_IPV4, device_cache_value.ip, {persist = true}) + device:set_field(fields.CREDENTIAL, device_cache_value.credential , {persist = true}) + device:set_field(fields.DEVICE_INFO, device_cache_value.device_info , {persist = true}) + driver.datastore.discovery_cache[device.device_network_id] = nil +end + +local function update_device_discovery_cache(driver, dni, ip, credential) + log.info(string.format("update_device_discovery_cache for device dni: %s, %s", dni, ip)) + local device_info = driver.discovery_helper.get_device_info(driver, dni, ip) + driver.datastore.discovery_cache[dni] = { + ip = ip, + device_info = device_info, + credential = credential, + } +end + +local function try_add_device(driver, device_dni, device_ip) + log.trace(string.format("try_add_device : dni=%s, ip=%s", device_dni, device_ip)) + + local credential = driver.discovery_helper.get_credential(driver, device_dni, device_ip) + + if not credential then + log.error(string.format("failed to get credential. dni=%s, ip=%s", device_dni, device_ip)) + joined_device[device_dni] = nil + return + end + + update_device_discovery_cache(driver, device_dni, device_ip, credential) + local create_device_msg = driver.discovery_helper.get_device_create_msg(driver, device_dni, device_ip) + driver:try_create_device(create_device_msg) +end + +function discovery.device_added(driver, device) + log.info("device_added : dni = " .. tostring(device.device_network_id)) + + discovery.set_device_field(driver, device) + joined_device[device.device_network_id] = nil + driver.lifecycle_handlers.init(driver, device) +end + +function discovery.find_ip_table(driver) + local ip_table= discovery_mdns.find_ip_table_by_mdns(driver) + return ip_table +end + + +local function discovery_device(driver) + local known_devices = {} + + for _, device in pairs(driver:get_devices()) do + known_devices[device.device_network_id] = device + end + + local ip_table = discovery.find_ip_table(driver) + + log.debug(st_utils.stringify_table(ip_table, "DNI IP Table after processing mDNS Discovery Response", true)) + + for dni, ip in pairs(ip_table) do + log.info(string.format("discovery_device dni, ip = %s, %s", dni, ip)) + if not known_devices or not known_devices[dni] then + log.trace(string.format("unknown dni= %s, ip= %s", dni, ip)) + if not joined_device[dni] then + try_add_device(driver, dni, ip) + joined_device[dni] = true + end + else + log.trace(string.format("known dni= %s, ip= %s", dni, ip)) + end + end +end + +function discovery.do_network_discovery(driver, _, should_continue) + log.info("discovery.do_network_discovery :Starting mDNS discovery") + + while should_continue() do + discovery_device(driver) + socket.sleep(0.2) + end + log.info("discovery.do_network_discovery: Ending mDNS discovery") +end + +return discovery diff --git a/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-cook-surface-two-tl.yml b/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-cook-surface-two-tl.yml index b36eccc624..d83313d8e4 100644 --- a/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-cook-surface-two-tl.yml +++ b/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-cook-surface-two-tl.yml @@ -1,24 +1,24 @@ -name: cook-surface-one-cook-surface-two-tl -components: -- id: main - capabilities: - - id: switch - version: 1 - - id: firmwareUpdate - version: 1 - - id: refresh - version: 1 - categories: - - name: Cooktop -- id: cookSurfaceOne - label: Cook Surface 1 - capabilities: - - id: temperatureMeasurement - version: 1 -- id: cookSurfaceTwo - label: Cook Surface 2 - capabilities: - - id: temperatureLevel - version: 1 - - id: temperatureMeasurement - version: 1 +name: cook-surface-one-cook-surface-two-tl +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Cooktop +- id: cookSurfaceOne + label: Cook Surface 1 + capabilities: + - id: temperatureMeasurement + version: 1 +- id: cookSurfaceTwo + label: Cook Surface 2 + capabilities: + - id: temperatureLevel + version: 1 + - id: temperatureMeasurement + version: 1 diff --git a/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-cook-surface-two.yml b/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-cook-surface-two.yml index 83d6298bb1..5ab4c35ca4 100644 --- a/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-cook-surface-two.yml +++ b/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-cook-surface-two.yml @@ -1,22 +1,22 @@ -name: cook-surface-one-cook-surface-two -components: -- id: main - capabilities: - - id: switch - version: 1 - - id: firmwareUpdate - version: 1 - - id: refresh - version: 1 - categories: - - name: Cooktop -- id: cookSurfaceOne - label: Cook Surface 1 - capabilities: - - id: temperatureMeasurement - version: 1 -- id: cookSurfaceTwo - label: Cook Surface 2 - capabilities: - - id: temperatureMeasurement - version: 1 +name: cook-surface-one-cook-surface-two +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Cooktop +- id: cookSurfaceOne + label: Cook Surface 1 + capabilities: + - id: temperatureMeasurement + version: 1 +- id: cookSurfaceTwo + label: Cook Surface 2 + capabilities: + - id: temperatureMeasurement + version: 1 diff --git a/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-tl-cook-surface-two-tl.yml b/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-tl-cook-surface-two-tl.yml index 4d73638a7d..b342ebff20 100644 --- a/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-tl-cook-surface-two-tl.yml +++ b/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-tl-cook-surface-two-tl.yml @@ -1,26 +1,26 @@ -name: cook-surface-one-tl-cook-surface-two-tl -components: -- id: main - capabilities: - - id: switch - version: 1 - - id: firmwareUpdate - version: 1 - - id: refresh - version: 1 - categories: - - name: Cooktop -- id: cookSurfaceOne - label: Cook Surface 1 - capabilities: - - id: temperatureLevel - version: 1 - - id: temperatureMeasurement - version: 1 -- id: cookSurfaceTwo - label: Cook Surface 2 - capabilities: - - id: temperatureLevel - version: 1 - - id: temperatureMeasurement - version: 1 +name: cook-surface-one-tl-cook-surface-two-tl +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Cooktop +- id: cookSurfaceOne + label: Cook Surface 1 + capabilities: + - id: temperatureLevel + version: 1 + - id: temperatureMeasurement + version: 1 +- id: cookSurfaceTwo + label: Cook Surface 2 + capabilities: + - id: temperatureLevel + version: 1 + - id: temperatureMeasurement + version: 1 diff --git a/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-tl-cook-surface-two.yml b/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-tl-cook-surface-two.yml index 4a6464030d..8a13ca1121 100644 --- a/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-tl-cook-surface-two.yml +++ b/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-tl-cook-surface-two.yml @@ -1,24 +1,24 @@ -name: cook-surface-one-tl-cook-surface-two -components: -- id: main - capabilities: - - id: switch - version: 1 - - id: firmwareUpdate - version: 1 - - id: refresh - version: 1 - categories: - - name: Cooktop -- id: cookSurfaceOne - label: Cook Surface 1 - capabilities: - - id: temperatureLevel - version: 1 - - id: temperatureMeasurement - version: 1 -- id: cookSurfaceTwo - label: Cook Surface 2 - capabilities: - - id: temperatureMeasurement - version: 1 +name: cook-surface-one-tl-cook-surface-two +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Cooktop +- id: cookSurfaceOne + label: Cook Surface 1 + capabilities: + - id: temperatureLevel + version: 1 + - id: temperatureMeasurement + version: 1 +- id: cookSurfaceTwo + label: Cook Surface 2 + capabilities: + - id: temperatureMeasurement + version: 1 diff --git a/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-tl.yml b/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-tl.yml index bb206ac8d3..4da683c7fc 100644 --- a/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-tl.yml +++ b/drivers/SmartThings/matter-appliance/profiles/cook-surface-one-tl.yml @@ -1,19 +1,19 @@ -name: cook-surface-one-tl -components: -- id: main - capabilities: - - id: switch - version: 1 - - id: firmwareUpdate - version: 1 - - id: refresh - version: 1 - categories: - - name: Cooktop -- id: cookSurfaceOne - label: Cook Surface 1 - capabilities: - - id: temperatureLevel - version: 1 - - id: temperatureMeasurement - version: 1 +name: cook-surface-one-tl +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Cooktop +- id: cookSurfaceOne + label: Cook Surface 1 + capabilities: + - id: temperatureLevel + version: 1 + - id: temperatureMeasurement + version: 1 diff --git a/drivers/SmartThings/matter-appliance/profiles/cook-surface-one.yml b/drivers/SmartThings/matter-appliance/profiles/cook-surface-one.yml index 44fee23b77..142c2bd23d 100644 --- a/drivers/SmartThings/matter-appliance/profiles/cook-surface-one.yml +++ b/drivers/SmartThings/matter-appliance/profiles/cook-surface-one.yml @@ -1,17 +1,17 @@ -name: cook-surface-one -components: -- id: main - capabilities: - - id: switch - version: 1 - - id: firmwareUpdate - version: 1 - - id: refresh - version: 1 - categories: - - name: Cooktop -- id: cookSurfaceOne - label: Cook Surface 1 - capabilities: - - id: temperatureMeasurement - version: 1 +name: cook-surface-one +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Cooktop +- id: cookSurfaceOne + label: Cook Surface 1 + capabilities: + - id: temperatureMeasurement + version: 1 diff --git a/drivers/SmartThings/matter-appliance/profiles/cook-top.yml b/drivers/SmartThings/matter-appliance/profiles/cook-top.yml index 9ecb6d9566..09add6cf7a 100644 --- a/drivers/SmartThings/matter-appliance/profiles/cook-top.yml +++ b/drivers/SmartThings/matter-appliance/profiles/cook-top.yml @@ -1,13 +1,13 @@ -name: cook-top -components: -- id: main - capabilities: - - id: switch - version: 1 - - id: firmwareUpdate - version: 1 - - id: refresh - version: 1 - categories: - - name: Cooktop +name: cook-top +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Cooktop \ No newline at end of file diff --git a/drivers/SmartThings/matter-appliance/src/matter-cook-top/init.lua b/drivers/SmartThings/matter-appliance/src/matter-cook-top/init.lua index 0a8f9f3182..519031ea7a 100644 --- a/drivers/SmartThings/matter-appliance/src/matter-cook-top/init.lua +++ b/drivers/SmartThings/matter-appliance/src/matter-cook-top/init.lua @@ -1,97 +1,97 @@ --- Copyright 2025 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.matter.clusters" -local common_utils = require "common-utils" -local embedded_cluster_utils = require "embedded-cluster-utils" -local version = require "version" - -if version.api < 10 then - clusters.TemperatureControl = require "TemperatureControl" -end - -local COOK_SURFACE_DEVICE_TYPE_ID = 0x0077 -local COOK_TOP_DEVICE_TYPE_ID = 0x0078 -local OVEN_DEVICE_ID = 0x007B - -local function table_contains(tab, val) - for _, tab_val in ipairs(tab) do - if tab_val == val then - return true - end - end - return false -end - -local function device_added(driver, device) - local cook_surface_endpoints = common_utils.get_endpoints_for_dt(device, COOK_SURFACE_DEVICE_TYPE_ID) - local componentToEndpointMap = { - ["cookSurfaceOne"] = cook_surface_endpoints[1], - ["cookSurfaceTwo"] = cook_surface_endpoints[2] - } - device:set_field(common_utils.COMPONENT_TO_ENDPOINT_MAP, componentToEndpointMap, { persist = true }) -end - -local function do_configure(driver, device) - local cook_surface_endpoints = common_utils.get_endpoints_for_dt(device, COOK_SURFACE_DEVICE_TYPE_ID) - - local tl_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureControl.ID, - { feature_bitmap = clusters.TemperatureControl.types.Feature.TEMPERATURE_LEVEL }) - - local profile_name - if #cook_surface_endpoints > 0 then - profile_name = "cook-surface-one" - if table_contains(tl_eps, cook_surface_endpoints[1]) then - profile_name = profile_name .. "-tl" - end - - -- we only support up to two cook surfaces - if #cook_surface_endpoints > 1 then - profile_name = profile_name .. "-cook-surface-two" - if table_contains(tl_eps, cook_surface_endpoints[2]) then - profile_name = profile_name .. "-tl" - end - end - end - - if profile_name then - device.log.info_with({hub_logs=true}, string.format("Updating device profile to %s.", profile_name)) - device:try_update_metadata({ profile = profile_name }) - end -end - -local function is_cook_top_device(opts, driver, device, ...) - local cook_top_eps = common_utils.get_endpoints_for_dt(device, COOK_TOP_DEVICE_TYPE_ID) - local oven_eps = common_utils.get_endpoints_for_dt(device, OVEN_DEVICE_ID) - -- we want to skip lifecycle events in cases where the device is an oven with a composed cook-top device - if (#oven_eps > 0) and opts.dispatcher_class == "DeviceLifecycleDispatcher" then - return false - end - if #cook_top_eps > 0 then - return true - end - return false -end - --- Matter Handlers -- -local matter_cook_top_handler = { - NAME = "matter-cook-top", - lifecycle_handlers = { - added = device_added, - doConfigure = do_configure - }, - can_handle = is_cook_top_device -} - -return matter_cook_top_handler +-- Copyright 2025 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.matter.clusters" +local common_utils = require "common-utils" +local embedded_cluster_utils = require "embedded-cluster-utils" +local version = require "version" + +if version.api < 10 then + clusters.TemperatureControl = require "TemperatureControl" +end + +local COOK_SURFACE_DEVICE_TYPE_ID = 0x0077 +local COOK_TOP_DEVICE_TYPE_ID = 0x0078 +local OVEN_DEVICE_ID = 0x007B + +local function table_contains(tab, val) + for _, tab_val in ipairs(tab) do + if tab_val == val then + return true + end + end + return false +end + +local function device_added(driver, device) + local cook_surface_endpoints = common_utils.get_endpoints_for_dt(device, COOK_SURFACE_DEVICE_TYPE_ID) + local componentToEndpointMap = { + ["cookSurfaceOne"] = cook_surface_endpoints[1], + ["cookSurfaceTwo"] = cook_surface_endpoints[2] + } + device:set_field(common_utils.COMPONENT_TO_ENDPOINT_MAP, componentToEndpointMap, { persist = true }) +end + +local function do_configure(driver, device) + local cook_surface_endpoints = common_utils.get_endpoints_for_dt(device, COOK_SURFACE_DEVICE_TYPE_ID) + + local tl_eps = embedded_cluster_utils.get_endpoints(device, clusters.TemperatureControl.ID, + { feature_bitmap = clusters.TemperatureControl.types.Feature.TEMPERATURE_LEVEL }) + + local profile_name + if #cook_surface_endpoints > 0 then + profile_name = "cook-surface-one" + if table_contains(tl_eps, cook_surface_endpoints[1]) then + profile_name = profile_name .. "-tl" + end + + -- we only support up to two cook surfaces + if #cook_surface_endpoints > 1 then + profile_name = profile_name .. "-cook-surface-two" + if table_contains(tl_eps, cook_surface_endpoints[2]) then + profile_name = profile_name .. "-tl" + end + end + end + + if profile_name then + device.log.info_with({hub_logs=true}, string.format("Updating device profile to %s.", profile_name)) + device:try_update_metadata({ profile = profile_name }) + end +end + +local function is_cook_top_device(opts, driver, device, ...) + local cook_top_eps = common_utils.get_endpoints_for_dt(device, COOK_TOP_DEVICE_TYPE_ID) + local oven_eps = common_utils.get_endpoints_for_dt(device, OVEN_DEVICE_ID) + -- we want to skip lifecycle events in cases where the device is an oven with a composed cook-top device + if (#oven_eps > 0) and opts.dispatcher_class == "DeviceLifecycleDispatcher" then + return false + end + if #cook_top_eps > 0 then + return true + end + return false +end + +-- Matter Handlers -- +local matter_cook_top_handler = { + NAME = "matter-cook-top", + lifecycle_handlers = { + added = device_added, + doConfigure = do_configure + }, + can_handle = is_cook_top_device +} + +return matter_cook_top_handler diff --git a/drivers/SmartThings/matter-energy/profiles/evse-energy-meas-power-meas-energy-mgmt-mode.yml b/drivers/SmartThings/matter-energy/profiles/evse-energy-meas-power-meas-energy-mgmt-mode.yml index 49a6ce9814..f0a28e35c2 100644 --- a/drivers/SmartThings/matter-energy/profiles/evse-energy-meas-power-meas-energy-mgmt-mode.yml +++ b/drivers/SmartThings/matter-energy/profiles/evse-energy-meas-power-meas-energy-mgmt-mode.yml @@ -1,32 +1,32 @@ -#EVSE with Electrical Power Measurement & Electrical Energy Measurement Cluster in Electrical Sensor DT and Device Energy Management mode in Energy Management DT -name: evse-energy-meas-power-meas-energy-mgmt-mode -components: -- id: main - capabilities: - - id: evseState - version: 1 - - id: evseChargingSession - version: 1 - - id: mode - version: 1 - - id: powerConsumptionReport - version: 1 - - id: firmwareUpdate - version: 1 - - id: refresh - version: 1 - categories: - - name: ElectricVehicleCharger -- id: electricalSensor - label: Electrical Sensor - capabilities: - - id: powerSource - version: 1 -- id: deviceEnergyManagement - label: Energy Manager - capabilities: - - id: mode - version: 1 -metadata: - mnmn: SmartThingsEdge - vid: generic-evse +#EVSE with Electrical Power Measurement & Electrical Energy Measurement Cluster in Electrical Sensor DT and Device Energy Management mode in Energy Management DT +name: evse-energy-meas-power-meas-energy-mgmt-mode +components: +- id: main + capabilities: + - id: evseState + version: 1 + - id: evseChargingSession + version: 1 + - id: mode + version: 1 + - id: powerConsumptionReport + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: ElectricVehicleCharger +- id: electricalSensor + label: Electrical Sensor + capabilities: + - id: powerSource + version: 1 +- id: deviceEnergyManagement + label: Energy Manager + capabilities: + - id: mode + version: 1 +metadata: + mnmn: SmartThingsEdge + vid: generic-evse diff --git a/drivers/SmartThings/matter-lock/config.yml b/drivers/SmartThings/matter-lock/config.yml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/matter-lock/fingerprints.yml b/drivers/SmartThings/matter-lock/fingerprints.yml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/matter-lock/profiles/base-lock-batteryLevel.yml b/drivers/SmartThings/matter-lock/profiles/base-lock-batteryLevel.yml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/matter-lock/profiles/base-lock-nobattery.yml b/drivers/SmartThings/matter-lock/profiles/base-lock-nobattery.yml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/matter-lock/profiles/base-lock.yml b/drivers/SmartThings/matter-lock/profiles/base-lock.yml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/matter-lock/profiles/lock-lockalarm-batteryLevel.yml b/drivers/SmartThings/matter-lock/profiles/lock-lockalarm-batteryLevel.yml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/matter-lock/profiles/lock-lockalarm.yml b/drivers/SmartThings/matter-lock/profiles/lock-lockalarm.yml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/matter-lock/profiles/lock-without-codes-batteryLevel.yml b/drivers/SmartThings/matter-lock/profiles/lock-without-codes-batteryLevel.yml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/matter-lock/profiles/lock-without-codes-nobattery.yml b/drivers/SmartThings/matter-lock/profiles/lock-without-codes-nobattery.yml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/matter-lock/profiles/lock-without-codes.yml b/drivers/SmartThings/matter-lock/profiles/lock-without-codes.yml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/matter-lock/src/init.lua b/drivers/SmartThings/matter-lock/src/init.lua old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/matter-pump/config.yml b/drivers/SmartThings/matter-pump/config.yml index 0e14f67579..b2a82ce374 100644 --- a/drivers/SmartThings/matter-pump/config.yml +++ b/drivers/SmartThings/matter-pump/config.yml @@ -1,4 +1,4 @@ -name: 'Matter Pump' -packageKey: 'matter-pump' -permissions: +name: 'Matter Pump' +packageKey: 'matter-pump' +permissions: matter: {} \ No newline at end of file diff --git a/drivers/SmartThings/matter-pump/fingerprints.yml b/drivers/SmartThings/matter-pump/fingerprints.yml index 238353abcc..f7133af22a 100644 --- a/drivers/SmartThings/matter-pump/fingerprints.yml +++ b/drivers/SmartThings/matter-pump/fingerprints.yml @@ -1,6 +1,6 @@ -matterGeneric: - - id: "matter/pump" - deviceLabel: Matter Pump - deviceTypes: - - id: 0x0303 +matterGeneric: + - id: "matter/pump" + deviceLabel: Matter Pump + deviceTypes: + - id: 0x0303 deviceProfileName: pump-level \ No newline at end of file diff --git a/drivers/SmartThings/matter-pump/profiles/pump-level.yml b/drivers/SmartThings/matter-pump/profiles/pump-level.yml index ae47ef6de4..bdd144a1d4 100644 --- a/drivers/SmartThings/matter-pump/profiles/pump-level.yml +++ b/drivers/SmartThings/matter-pump/profiles/pump-level.yml @@ -1,21 +1,21 @@ -name: pump-level -components: -- id: main - capabilities: - - id: switch - version: 1 - - id: switchLevel - version: 1 - - id: pumpOperationMode - version: 1 - - id: pumpControlMode - version: 1 - - id: firmwareUpdate - version: 1 - - id: refresh - version: 1 - categories: - - name: Pump -metadata: - mnmn: SmartThingsEdge +name: pump-level +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: switchLevel + version: 1 + - id: pumpOperationMode + version: 1 + - id: pumpControlMode + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Pump +metadata: + mnmn: SmartThingsEdge vid: generic-pump-level \ No newline at end of file diff --git a/drivers/SmartThings/matter-pump/profiles/pump-only.yml b/drivers/SmartThings/matter-pump/profiles/pump-only.yml index f51fe87943..8c79c42d1e 100644 --- a/drivers/SmartThings/matter-pump/profiles/pump-only.yml +++ b/drivers/SmartThings/matter-pump/profiles/pump-only.yml @@ -1,19 +1,19 @@ -name: pump-only -components: -- id: main - capabilities: - - id: switch - version: 1 - - id: pumpOperationMode - version: 1 - - id: pumpControlMode - version: 1 - - id: firmwareUpdate - version: 1 - - id: refresh - version: 1 - categories: - - name: Pump -metadata: - mnmn: SmartThingsEdge +name: pump-only +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: pumpOperationMode + version: 1 + - id: pumpControlMode + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Pump +metadata: + mnmn: SmartThingsEdge vid: generic-pump-level \ No newline at end of file diff --git a/drivers/SmartThings/matter-pump/src/init.lua b/drivers/SmartThings/matter-pump/src/init.lua index db43d3b1df..123b443cd8 100644 --- a/drivers/SmartThings/matter-pump/src/init.lua +++ b/drivers/SmartThings/matter-pump/src/init.lua @@ -1,328 +1,328 @@ --- Copyright 2024 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 capabilities = require "st.capabilities" -local log = require "log" -local clusters = require "st.matter.clusters" -local embedded_cluster_utils = require "embedded-cluster-utils" -local MatterDriver = require "st.matter.driver" - -local IS_LOCAL_OVERRIDE = "__is_local_override" --- Per matter spec, the pump level is in steps of 0.5% and the --- max level value is 200. Anything above is considered 100% -local MAX_PUMP_ATTR_LEVEL = 200 -local MAX_CAP_SWITCH_LEVEL = 100 - --- Include driver-side definitions when lua libs api version is < 10 -local version = require "version" -if version.api < 10 then - clusters.PumpConfigurationAndControl = require "PumpConfigurationAndControl" -end - -local pumpOperationMode = capabilities.pumpOperationMode -local pumpControlMode = capabilities.pumpControlMode - -local PUMP_OPERATION_MODE_MAP = { - [clusters.PumpConfigurationAndControl.types.OperationModeEnum.NORMAL] = pumpOperationMode.operationMode.normal, - [clusters.PumpConfigurationAndControl.types.OperationModeEnum.MINIMUM] = pumpOperationMode.operationMode.minimum, - [clusters.PumpConfigurationAndControl.types.OperationModeEnum.MAXIMUM] = pumpOperationMode.operationMode.maximum, - [clusters.PumpConfigurationAndControl.types.OperationModeEnum.LOCAL] = pumpOperationMode.operationMode.localSetting, -} - -local PUMP_CONTROL_MODE_MAP = { - [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_SPEED] = pumpControlMode.controlMode.constantSpeed, - [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_PRESSURE] = pumpControlMode.controlMode.constantPressure, - [clusters.PumpConfigurationAndControl.types.ControlModeEnum.PROPORTIONAL_PRESSURE] = pumpControlMode.controlMode.proportionalPressure, - [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_FLOW] = pumpControlMode.controlMode.constantFlow, - [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_TEMPERATURE] = pumpControlMode.controlMode.constantTemperature, - [clusters.PumpConfigurationAndControl.types.ControlModeEnum.AUTOMATIC] = pumpControlMode.controlMode.automatic, -} - -local PUMP_CURRENT_CONTROL_MODE_MAP = { - [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_SPEED] = pumpControlMode.currentControlMode.constantSpeed, - [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_PRESSURE] = pumpControlMode.currentControlMode.constantPressure, - [clusters.PumpConfigurationAndControl.types.ControlModeEnum.PROPORTIONAL_PRESSURE] = pumpControlMode.currentControlMode.proportionalPressure, - [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_FLOW] = pumpControlMode.currentControlMode.constantFlow, - [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_TEMPERATURE] = pumpControlMode.currentControlMode.constantTemperature, - [clusters.PumpConfigurationAndControl.types.ControlModeEnum.AUTOMATIC] = pumpControlMode.currentControlMode.automatic, -} - -local subscribed_attributes = { - [capabilities.switch.ID] = { - clusters.OnOff.attributes.OnOff, - }, - [capabilities.switchLevel.ID] = { - clusters.LevelControl.attributes.CurrentLevel - }, - [capabilities.pumpOperationMode.ID]={ - clusters.PumpConfigurationAndControl.attributes.OperationMode, - clusters.PumpConfigurationAndControl.attributes.EffectiveOperationMode, - clusters.PumpConfigurationAndControl.attributes.PumpStatus, - }, - [capabilities.pumpControlMode.ID]={ - clusters.PumpConfigurationAndControl.attributes.EffectiveControlMode, - }, -} - -local function find_default_endpoint(device, cluster) - local res = device.MATTER_DEFAULT_ENDPOINT - local eps = embedded_cluster_utils.get_endpoints(device, cluster) - table.sort(eps) - for _, v in ipairs(eps) do - if v ~= 0 then --0 is the matter RootNode endpoint - return v - end - end - device.log.warn(string.format("Did not find default endpoint, will use endpoint %d instead", device.MATTER_DEFAULT_ENDPOINT)) - return res -end - -local function component_to_endpoint(device, component_name) - -- Use the find_default_endpoint function to return the first endpoint that - -- supports a given cluster. - return find_default_endpoint(device, clusters.PumpConfigurationAndControl.ID) -end - -local function device_init(driver, device) - device:subscribe() - device:set_component_to_endpoint_fn(component_to_endpoint) -end - -local function info_changed(driver, device, event, args) - --Note this is needed because device:subscribe() does not recalculate - -- the subscribed attributes each time it is run, that only happens at init. - -- This will change in the 0.48.x release of the lua libs. - for cap_id, attributes in pairs(subscribed_attributes) do - if device:supports_capability_by_id(cap_id) then - for _, attr in ipairs(attributes) do - device:add_subscribed_attribute(attr) - end - end - end - device:subscribe() -end - -local function set_supported_op_mode(driver, device) - local spd_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.CONSTANT_SPEED}) - local local_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.LOCAL_OPERATION}) - local supported_op_modes = {pumpOperationMode.operationMode.normal.NAME} - if #spd_eps > 0 then - table.insert(supported_op_modes, pumpOperationMode.operationMode.minimum.NAME) - table.insert(supported_op_modes, pumpOperationMode.operationMode.maximum.NAME) - end - if #local_eps > 0 then - table.insert(supported_op_modes, pumpOperationMode.operationMode.localSetting.NAME) - end - device:emit_event(pumpOperationMode.supportedOperationModes(supported_op_modes)) -end - -local function set_supported_control_mode(driver, device) - local spd_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.CONSTANT_SPEED}) - local prsconst_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.CONSTANT_PRESSURE}) - local prscomp_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.COMPENSATED_PRESSURE}) - local flw_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.CONSTANT_FLOW}) - local temp_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.CONSTANT_TEMPERATURE}) - local auto_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.AUTOMATIC}) - local supported_control_modes = {} - if #spd_eps > 0 then - table.insert(supported_control_modes, pumpControlMode.controlMode.constantSpeed.NAME) - end - if #prsconst_eps > 0 then - table.insert(supported_control_modes, pumpControlMode.controlMode.constantPressure.NAME) - end - if #prscomp_eps > 0 then - table.insert(supported_control_modes, pumpControlMode.controlMode.proportionalPressure.NAME) - end - if #flw_eps > 0 then - table.insert(supported_control_modes, pumpControlMode.controlMode.constantFlow.NAME) - end - if #temp_eps > 0 then - table.insert(supported_control_modes, pumpControlMode.controlMode.constantTemperature.NAME) - end - if #auto_eps > 0 then - table.insert(supported_control_modes, pumpControlMode.controlMode.automatic.NAME) - end - device:emit_event(pumpControlMode.supportedControlModes(supported_control_modes)) -end - -local function do_configure(driver, device) - local pump_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID) - local level_eps = embedded_cluster_utils.get_endpoints(device, clusters.LevelControl.ID) - local profile_name = "pump" - if #pump_eps == 1 then - if #level_eps > 0 then - profile_name = profile_name .. "-level" - else - profile_name = profile_name .. "-only" - end - device.log.info_with({hub_logs=true}, string.format("Updating device profile to %s.", profile_name)) - device:try_update_metadata({profile = profile_name}) - else - device.log.warn_with({hub_logs=true}, "Device does not support pump configuration and control cluster") - end - set_supported_op_mode(driver, device) - set_supported_control_mode(driver, device) -end - --- Matter Handlers -- -local function on_off_attr_handler(driver, device, ib, response) - if ib.data.value then - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.switch.switch.on()) - else - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.switch.switch.off()) - end -end - -local function level_attr_handler(driver, device, ib, response) - if ib.data.value ~= nil then - local level = math.floor((ib.data.value / MAX_PUMP_ATTR_LEVEL * MAX_CAP_SWITCH_LEVEL) + 0.5) - level = math.min(level, MAX_CAP_SWITCH_LEVEL) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.switchLevel.level(level)) - end -end - -local function effective_operation_mode_handler(driver, device, ib, response) - local modeEnum = clusters.PumpConfigurationAndControl.types.OperationModeEnum - local supported_control_modes = {} - local local_override = device:get_field(IS_LOCAL_OVERRIDE) - if not local_override then - set_supported_op_mode(driver, device) - end - if ib.data.value == modeEnum.NORMAL then - device:emit_event_for_endpoint(ib.endpoint_id, pumpOperationMode.currentOperationMode.normal()) - set_supported_control_mode(driver, device) - elseif ib.data.value == modeEnum.MINIMUM then - device:emit_event_for_endpoint(ib.endpoint_id, pumpOperationMode.currentOperationMode.minimum()) - device:emit_event_for_endpoint(ib.endpoint_id, pumpControlMode.supportedControlModes(supported_control_modes)) - elseif ib.data.value == modeEnum.MAXIMUM then - device:emit_event_for_endpoint(ib.endpoint_id, pumpOperationMode.currentOperationMode.maximum()) - device:emit_event_for_endpoint(ib.endpoint_id, pumpControlMode.supportedControlModes(supported_control_modes)) - elseif ib.data.value == modeEnum.LOCAL then - device:emit_event_for_endpoint(ib.endpoint_id, pumpOperationMode.currentOperationMode.localSetting()) - device:emit_event_for_endpoint(ib.endpoint_id, pumpControlMode.supportedControlModes(supported_control_modes)) - end -end - -local function effective_control_mode_handler(driver, device, ib, response) - device:emit_event_for_endpoint(ib.endpoint_id, PUMP_CURRENT_CONTROL_MODE_MAP[ib.data.value]()) -end - -local function pump_status_handler(driver, device, ib, response) - if ib.data.value == clusters.PumpConfigurationAndControl.types.PumpStatusBitmap.LOCAL_OVERRIDE then - device:set_field(IS_LOCAL_OVERRIDE, true, {persist = true}) - device:emit_event_for_endpoint(ib.endpoint_id, pumpOperationMode.currentOperationMode.localSetting()) - local supported_op_modes = {} - local supported_control_modes = {} - device:emit_event(pumpOperationMode.supportedOperationModes(supported_op_modes)) - device:emit_event(pumpControlMode.supportedControlModes(supported_control_modes)) - elseif ib.data.value == clusters.PumpConfigurationAndControl.types.PumpStatusBitmap.RUNNING then - device:set_field(IS_LOCAL_OVERRIDE, false, {persist = true}) - device:send(clusters.PumpConfigurationAndControl.attributes.EffectiveOperationMode:read(device)) - end -end - --- Capability Handlers -- -local function handle_switch_on(driver, device, cmd) - local endpoint_id = device:component_to_endpoint(cmd.component) - local req = clusters.OnOff.server.commands.On(device, endpoint_id) - device:send(req) -end - -local function handle_switch_off(driver, device, cmd) - local endpoint_id = device:component_to_endpoint(cmd.component) - local req = clusters.OnOff.server.commands.Off(device, endpoint_id) - device:send(req) -end - -local function handle_set_level(driver, device, cmd) - local endpoint_id = device:component_to_endpoint(cmd.component) - local level = math.floor(cmd.args.level / MAX_CAP_SWITCH_LEVEL * MAX_PUMP_ATTR_LEVEL) - local req = clusters.LevelControl.server.commands.MoveToLevelWithOnOff(device, endpoint_id, level, cmd.args.rate or 0, 0 ,0) - device:send(req) -end - -local function set_operation_mode(driver, device, cmd) - local mode_id = nil - for id, mode in pairs(PUMP_OPERATION_MODE_MAP) do - if mode.NAME == cmd.args.operationMode then - mode_id = id - break - end - end - if mode_id then - device:send(clusters.PumpConfigurationAndControl.attributes.OperationMode:write(device, device:component_to_endpoint(cmd.component), mode_id)) - end -end - -local function set_control_mode(driver, device, cmd) - local mode_id = nil - for id, mode in pairs(PUMP_CONTROL_MODE_MAP) do - if mode.NAME == cmd.args.controlMode then - mode_id = id - break - end - end - if mode_id then - device:send(clusters.PumpConfigurationAndControl.attributes.ControlMode:write(device, device:component_to_endpoint(cmd.component), mode_id)) - end -end - -local matter_driver_template = { - lifecycle_handlers = { - init = device_init, - doConfigure = do_configure, - infoChanged = info_changed, - }, - matter_handlers = { - attr = { - [clusters.OnOff.ID] = { - [clusters.OnOff.attributes.OnOff.ID] = on_off_attr_handler, - }, - [clusters.LevelControl.ID] = { - [clusters.LevelControl.attributes.CurrentLevel.ID] = level_attr_handler - }, - [clusters.PumpConfigurationAndControl.ID] = { - [clusters.PumpConfigurationAndControl.attributes.EffectiveOperationMode.ID] = effective_operation_mode_handler, - [clusters.PumpConfigurationAndControl.attributes.EffectiveControlMode.ID] = effective_control_mode_handler, - [clusters.PumpConfigurationAndControl.attributes.PumpStatus.ID] = pump_status_handler, - }, - }, - }, - subscribed_attributes = subscribed_attributes, - capability_handlers = { - [capabilities.switch.ID] = { - [capabilities.switch.commands.on.NAME] = handle_switch_on, - [capabilities.switch.commands.off.NAME] = handle_switch_off, - }, - [capabilities.switchLevel.ID] = { - [capabilities.switchLevel.commands.setLevel.NAME] = handle_set_level, - }, - [capabilities.pumpOperationMode.ID] = { - [capabilities.pumpOperationMode.commands.setOperationMode.NAME] = set_operation_mode, - }, - [capabilities.pumpControlMode.ID] = { - [capabilities.pumpControlMode.commands.setControlMode.NAME] = set_control_mode, - }, - }, - supported_capabilities = { - capabilities.switch, - capabilities.switchLevel, - capabilities.pumpOperationMode, - capabilities.pumpControlMode, - }, -} - -local matter_driver = MatterDriver("matter-pump", matter_driver_template) -log.info_with({hub_logs=true}, string.format("Starting %s driver, with dispatcher: %s", matter_driver.NAME, matter_driver.matter_dispatcher)) +-- Copyright 2024 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 capabilities = require "st.capabilities" +local log = require "log" +local clusters = require "st.matter.clusters" +local embedded_cluster_utils = require "embedded-cluster-utils" +local MatterDriver = require "st.matter.driver" + +local IS_LOCAL_OVERRIDE = "__is_local_override" +-- Per matter spec, the pump level is in steps of 0.5% and the +-- max level value is 200. Anything above is considered 100% +local MAX_PUMP_ATTR_LEVEL = 200 +local MAX_CAP_SWITCH_LEVEL = 100 + +-- Include driver-side definitions when lua libs api version is < 10 +local version = require "version" +if version.api < 10 then + clusters.PumpConfigurationAndControl = require "PumpConfigurationAndControl" +end + +local pumpOperationMode = capabilities.pumpOperationMode +local pumpControlMode = capabilities.pumpControlMode + +local PUMP_OPERATION_MODE_MAP = { + [clusters.PumpConfigurationAndControl.types.OperationModeEnum.NORMAL] = pumpOperationMode.operationMode.normal, + [clusters.PumpConfigurationAndControl.types.OperationModeEnum.MINIMUM] = pumpOperationMode.operationMode.minimum, + [clusters.PumpConfigurationAndControl.types.OperationModeEnum.MAXIMUM] = pumpOperationMode.operationMode.maximum, + [clusters.PumpConfigurationAndControl.types.OperationModeEnum.LOCAL] = pumpOperationMode.operationMode.localSetting, +} + +local PUMP_CONTROL_MODE_MAP = { + [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_SPEED] = pumpControlMode.controlMode.constantSpeed, + [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_PRESSURE] = pumpControlMode.controlMode.constantPressure, + [clusters.PumpConfigurationAndControl.types.ControlModeEnum.PROPORTIONAL_PRESSURE] = pumpControlMode.controlMode.proportionalPressure, + [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_FLOW] = pumpControlMode.controlMode.constantFlow, + [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_TEMPERATURE] = pumpControlMode.controlMode.constantTemperature, + [clusters.PumpConfigurationAndControl.types.ControlModeEnum.AUTOMATIC] = pumpControlMode.controlMode.automatic, +} + +local PUMP_CURRENT_CONTROL_MODE_MAP = { + [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_SPEED] = pumpControlMode.currentControlMode.constantSpeed, + [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_PRESSURE] = pumpControlMode.currentControlMode.constantPressure, + [clusters.PumpConfigurationAndControl.types.ControlModeEnum.PROPORTIONAL_PRESSURE] = pumpControlMode.currentControlMode.proportionalPressure, + [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_FLOW] = pumpControlMode.currentControlMode.constantFlow, + [clusters.PumpConfigurationAndControl.types.ControlModeEnum.CONSTANT_TEMPERATURE] = pumpControlMode.currentControlMode.constantTemperature, + [clusters.PumpConfigurationAndControl.types.ControlModeEnum.AUTOMATIC] = pumpControlMode.currentControlMode.automatic, +} + +local subscribed_attributes = { + [capabilities.switch.ID] = { + clusters.OnOff.attributes.OnOff, + }, + [capabilities.switchLevel.ID] = { + clusters.LevelControl.attributes.CurrentLevel + }, + [capabilities.pumpOperationMode.ID]={ + clusters.PumpConfigurationAndControl.attributes.OperationMode, + clusters.PumpConfigurationAndControl.attributes.EffectiveOperationMode, + clusters.PumpConfigurationAndControl.attributes.PumpStatus, + }, + [capabilities.pumpControlMode.ID]={ + clusters.PumpConfigurationAndControl.attributes.EffectiveControlMode, + }, +} + +local function find_default_endpoint(device, cluster) + local res = device.MATTER_DEFAULT_ENDPOINT + local eps = embedded_cluster_utils.get_endpoints(device, cluster) + table.sort(eps) + for _, v in ipairs(eps) do + if v ~= 0 then --0 is the matter RootNode endpoint + return v + end + end + device.log.warn(string.format("Did not find default endpoint, will use endpoint %d instead", device.MATTER_DEFAULT_ENDPOINT)) + return res +end + +local function component_to_endpoint(device, component_name) + -- Use the find_default_endpoint function to return the first endpoint that + -- supports a given cluster. + return find_default_endpoint(device, clusters.PumpConfigurationAndControl.ID) +end + +local function device_init(driver, device) + device:subscribe() + device:set_component_to_endpoint_fn(component_to_endpoint) +end + +local function info_changed(driver, device, event, args) + --Note this is needed because device:subscribe() does not recalculate + -- the subscribed attributes each time it is run, that only happens at init. + -- This will change in the 0.48.x release of the lua libs. + for cap_id, attributes in pairs(subscribed_attributes) do + if device:supports_capability_by_id(cap_id) then + for _, attr in ipairs(attributes) do + device:add_subscribed_attribute(attr) + end + end + end + device:subscribe() +end + +local function set_supported_op_mode(driver, device) + local spd_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.CONSTANT_SPEED}) + local local_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.LOCAL_OPERATION}) + local supported_op_modes = {pumpOperationMode.operationMode.normal.NAME} + if #spd_eps > 0 then + table.insert(supported_op_modes, pumpOperationMode.operationMode.minimum.NAME) + table.insert(supported_op_modes, pumpOperationMode.operationMode.maximum.NAME) + end + if #local_eps > 0 then + table.insert(supported_op_modes, pumpOperationMode.operationMode.localSetting.NAME) + end + device:emit_event(pumpOperationMode.supportedOperationModes(supported_op_modes)) +end + +local function set_supported_control_mode(driver, device) + local spd_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.CONSTANT_SPEED}) + local prsconst_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.CONSTANT_PRESSURE}) + local prscomp_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.COMPENSATED_PRESSURE}) + local flw_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.CONSTANT_FLOW}) + local temp_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.CONSTANT_TEMPERATURE}) + local auto_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID, {feature_bitmap = clusters.PumpConfigurationAndControl.types.Feature.AUTOMATIC}) + local supported_control_modes = {} + if #spd_eps > 0 then + table.insert(supported_control_modes, pumpControlMode.controlMode.constantSpeed.NAME) + end + if #prsconst_eps > 0 then + table.insert(supported_control_modes, pumpControlMode.controlMode.constantPressure.NAME) + end + if #prscomp_eps > 0 then + table.insert(supported_control_modes, pumpControlMode.controlMode.proportionalPressure.NAME) + end + if #flw_eps > 0 then + table.insert(supported_control_modes, pumpControlMode.controlMode.constantFlow.NAME) + end + if #temp_eps > 0 then + table.insert(supported_control_modes, pumpControlMode.controlMode.constantTemperature.NAME) + end + if #auto_eps > 0 then + table.insert(supported_control_modes, pumpControlMode.controlMode.automatic.NAME) + end + device:emit_event(pumpControlMode.supportedControlModes(supported_control_modes)) +end + +local function do_configure(driver, device) + local pump_eps = embedded_cluster_utils.get_endpoints(device, clusters.PumpConfigurationAndControl.ID) + local level_eps = embedded_cluster_utils.get_endpoints(device, clusters.LevelControl.ID) + local profile_name = "pump" + if #pump_eps == 1 then + if #level_eps > 0 then + profile_name = profile_name .. "-level" + else + profile_name = profile_name .. "-only" + end + device.log.info_with({hub_logs=true}, string.format("Updating device profile to %s.", profile_name)) + device:try_update_metadata({profile = profile_name}) + else + device.log.warn_with({hub_logs=true}, "Device does not support pump configuration and control cluster") + end + set_supported_op_mode(driver, device) + set_supported_control_mode(driver, device) +end + +-- Matter Handlers -- +local function on_off_attr_handler(driver, device, ib, response) + if ib.data.value then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.switch.switch.on()) + else + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.switch.switch.off()) + end +end + +local function level_attr_handler(driver, device, ib, response) + if ib.data.value ~= nil then + local level = math.floor((ib.data.value / MAX_PUMP_ATTR_LEVEL * MAX_CAP_SWITCH_LEVEL) + 0.5) + level = math.min(level, MAX_CAP_SWITCH_LEVEL) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.switchLevel.level(level)) + end +end + +local function effective_operation_mode_handler(driver, device, ib, response) + local modeEnum = clusters.PumpConfigurationAndControl.types.OperationModeEnum + local supported_control_modes = {} + local local_override = device:get_field(IS_LOCAL_OVERRIDE) + if not local_override then + set_supported_op_mode(driver, device) + end + if ib.data.value == modeEnum.NORMAL then + device:emit_event_for_endpoint(ib.endpoint_id, pumpOperationMode.currentOperationMode.normal()) + set_supported_control_mode(driver, device) + elseif ib.data.value == modeEnum.MINIMUM then + device:emit_event_for_endpoint(ib.endpoint_id, pumpOperationMode.currentOperationMode.minimum()) + device:emit_event_for_endpoint(ib.endpoint_id, pumpControlMode.supportedControlModes(supported_control_modes)) + elseif ib.data.value == modeEnum.MAXIMUM then + device:emit_event_for_endpoint(ib.endpoint_id, pumpOperationMode.currentOperationMode.maximum()) + device:emit_event_for_endpoint(ib.endpoint_id, pumpControlMode.supportedControlModes(supported_control_modes)) + elseif ib.data.value == modeEnum.LOCAL then + device:emit_event_for_endpoint(ib.endpoint_id, pumpOperationMode.currentOperationMode.localSetting()) + device:emit_event_for_endpoint(ib.endpoint_id, pumpControlMode.supportedControlModes(supported_control_modes)) + end +end + +local function effective_control_mode_handler(driver, device, ib, response) + device:emit_event_for_endpoint(ib.endpoint_id, PUMP_CURRENT_CONTROL_MODE_MAP[ib.data.value]()) +end + +local function pump_status_handler(driver, device, ib, response) + if ib.data.value == clusters.PumpConfigurationAndControl.types.PumpStatusBitmap.LOCAL_OVERRIDE then + device:set_field(IS_LOCAL_OVERRIDE, true, {persist = true}) + device:emit_event_for_endpoint(ib.endpoint_id, pumpOperationMode.currentOperationMode.localSetting()) + local supported_op_modes = {} + local supported_control_modes = {} + device:emit_event(pumpOperationMode.supportedOperationModes(supported_op_modes)) + device:emit_event(pumpControlMode.supportedControlModes(supported_control_modes)) + elseif ib.data.value == clusters.PumpConfigurationAndControl.types.PumpStatusBitmap.RUNNING then + device:set_field(IS_LOCAL_OVERRIDE, false, {persist = true}) + device:send(clusters.PumpConfigurationAndControl.attributes.EffectiveOperationMode:read(device)) + end +end + +-- Capability Handlers -- +local function handle_switch_on(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + local req = clusters.OnOff.server.commands.On(device, endpoint_id) + device:send(req) +end + +local function handle_switch_off(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + local req = clusters.OnOff.server.commands.Off(device, endpoint_id) + device:send(req) +end + +local function handle_set_level(driver, device, cmd) + local endpoint_id = device:component_to_endpoint(cmd.component) + local level = math.floor(cmd.args.level / MAX_CAP_SWITCH_LEVEL * MAX_PUMP_ATTR_LEVEL) + local req = clusters.LevelControl.server.commands.MoveToLevelWithOnOff(device, endpoint_id, level, cmd.args.rate or 0, 0 ,0) + device:send(req) +end + +local function set_operation_mode(driver, device, cmd) + local mode_id = nil + for id, mode in pairs(PUMP_OPERATION_MODE_MAP) do + if mode.NAME == cmd.args.operationMode then + mode_id = id + break + end + end + if mode_id then + device:send(clusters.PumpConfigurationAndControl.attributes.OperationMode:write(device, device:component_to_endpoint(cmd.component), mode_id)) + end +end + +local function set_control_mode(driver, device, cmd) + local mode_id = nil + for id, mode in pairs(PUMP_CONTROL_MODE_MAP) do + if mode.NAME == cmd.args.controlMode then + mode_id = id + break + end + end + if mode_id then + device:send(clusters.PumpConfigurationAndControl.attributes.ControlMode:write(device, device:component_to_endpoint(cmd.component), mode_id)) + end +end + +local matter_driver_template = { + lifecycle_handlers = { + init = device_init, + doConfigure = do_configure, + infoChanged = info_changed, + }, + matter_handlers = { + attr = { + [clusters.OnOff.ID] = { + [clusters.OnOff.attributes.OnOff.ID] = on_off_attr_handler, + }, + [clusters.LevelControl.ID] = { + [clusters.LevelControl.attributes.CurrentLevel.ID] = level_attr_handler + }, + [clusters.PumpConfigurationAndControl.ID] = { + [clusters.PumpConfigurationAndControl.attributes.EffectiveOperationMode.ID] = effective_operation_mode_handler, + [clusters.PumpConfigurationAndControl.attributes.EffectiveControlMode.ID] = effective_control_mode_handler, + [clusters.PumpConfigurationAndControl.attributes.PumpStatus.ID] = pump_status_handler, + }, + }, + }, + subscribed_attributes = subscribed_attributes, + capability_handlers = { + [capabilities.switch.ID] = { + [capabilities.switch.commands.on.NAME] = handle_switch_on, + [capabilities.switch.commands.off.NAME] = handle_switch_off, + }, + [capabilities.switchLevel.ID] = { + [capabilities.switchLevel.commands.setLevel.NAME] = handle_set_level, + }, + [capabilities.pumpOperationMode.ID] = { + [capabilities.pumpOperationMode.commands.setOperationMode.NAME] = set_operation_mode, + }, + [capabilities.pumpControlMode.ID] = { + [capabilities.pumpControlMode.commands.setControlMode.NAME] = set_control_mode, + }, + }, + supported_capabilities = { + capabilities.switch, + capabilities.switchLevel, + capabilities.pumpOperationMode, + capabilities.pumpControlMode, + }, +} + +local matter_driver = MatterDriver("matter-pump", matter_driver_template) +log.info_with({hub_logs=true}, string.format("Starting %s driver, with dispatcher: %s", matter_driver.NAME, matter_driver.matter_dispatcher)) matter_driver:run() \ No newline at end of file diff --git a/drivers/SmartThings/matter-switch/profiles/light-color-level-1800K-6500K.yml b/drivers/SmartThings/matter-switch/profiles/light-color-level-1800K-6500K.yml old mode 100755 new mode 100644 index 1638962b8f..d35ff8aa88 --- a/drivers/SmartThings/matter-switch/profiles/light-color-level-1800K-6500K.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-color-level-1800K-6500K.yml @@ -1,26 +1,26 @@ -name: light-color-level-1800K-6500K -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: [ 1800, 6500 ] - - id: colorControl - version: 1 - - id: firmwareUpdate - version: 1 - - id: refresh - version: 1 - categories: - - name: Light +name: light-color-level-1800K-6500K +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: [ 1800, 6500 ] + - id: colorControl + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Light diff --git a/drivers/SmartThings/matter-switch/profiles/light-level-power-energy-powerConsumption.yml b/drivers/SmartThings/matter-switch/profiles/light-level-power-energy-powerConsumption.yml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/matter-thermostat/profiles/air-purifier-hepa-ac-rock-wind-thermostat-humidity-fan-heating-only-nostate-nobattery-aqs-pm10-pm25-ch2o-meas-pm10-pm25-ch2o-no2-tvoc-level.yml b/drivers/SmartThings/matter-thermostat/profiles/air-purifier-hepa-ac-rock-wind-thermostat-humidity-fan-heating-only-nostate-nobattery-aqs-pm10-pm25-ch2o-meas-pm10-pm25-ch2o-no2-tvoc-level.yml deleted file mode 100644 index f568aaac1d..0000000000 --- a/drivers/SmartThings/matter-thermostat/profiles/air-purifier-hepa-ac-rock-wind-thermostat-humidity-fan-heating-only-nostate-nobattery-aqs-pm10-pm25-ch2o-meas-pm10-pm25-ch2o-no2-tvoc-level.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: air-purifier-hepa-ac-rock-wind-thermostat-humidity-fan-heating-only-nostate-nobattery-aqs-pm10-pm25-ch2o-meas-pm10-pm25-ch2o-no2-tvoc-level -components: -- id: main - label: Main - capabilities: - - id: airPurifierFanMode - version: 1 - - id: fanSpeedPercent - version: 1 - - id: fanOscillationMode - version: 1 - - id: windMode - version: 1 - - id: thermostatHeatingSetpoint - version: 1 - - id: thermostatMode - version: 1 - - id: temperatureMeasurement - version: 1 - - id: dustSensor - version: 1 - - id: formaldehydeMeasurement - version: 1 - - id: relativeHumidityMeasurement - version: 1 - - id: airQualityHealthConcern - version: 1 - - id: dustHealthConcern - version: 1 - - id: fineDustHealthConcern - version: 1 - - id: formaldehydeHealthConcern - version: 1 - - id: nitrogenDioxideHealthConcern - version: 1 - - id: tvocHealthConcern - version: 1 - - id: firmwareUpdate - version: 1 - - id: refresh - version: 1 - categories: - - name: AirPurifier -- id: hepaFilter - label: Hepa Filter - capabilities: - - id: filterState - version: 1 - - id: filterStatus - version: 1 - categories: - - name: AirPurifier -- id: activatedCarbonFilter - label: Activated Carbon Filter - capabilities: - - id: filterState - version: 1 - - id: filterStatus - version: 1 - categories: - - name: AirPurifier diff --git a/drivers/SmartThings/philips-hue/run_specs.sh b/drivers/SmartThings/philips-hue/run_specs.sh old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-air-quality-detector/config.yml b/drivers/SmartThings/zigbee-air-quality-detector/config.yml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-air-quality-detector/fingerprints.yml b/drivers/SmartThings/zigbee-air-quality-detector/fingerprints.yml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-air-quality-detector/profiles/air-quality-detector-MultiIR.yml b/drivers/SmartThings/zigbee-air-quality-detector/profiles/air-quality-detector-MultiIR.yml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-air-quality-detector/src/MultiIR/custom_clusters.lua b/drivers/SmartThings/zigbee-air-quality-detector/src/MultiIR/custom_clusters.lua old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-air-quality-detector/src/MultiIR/init.lua b/drivers/SmartThings/zigbee-air-quality-detector/src/MultiIR/init.lua old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-air-quality-detector/src/init.lua b/drivers/SmartThings/zigbee-air-quality-detector/src/init.lua old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-air-quality-detector/src/test/test_MultiIR_air_quality_detector.lua b/drivers/SmartThings/zigbee-air-quality-detector/src/test/test_MultiIR_air_quality_detector.lua old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-bed/capabilities/aiMode.yaml b/drivers/SmartThings/zigbee-bed/capabilities/aiMode.yaml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-bed/capabilities/autoInflation.yaml b/drivers/SmartThings/zigbee-bed/capabilities/autoInflation.yaml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-bed/capabilities/leftControl.yaml b/drivers/SmartThings/zigbee-bed/capabilities/leftControl.yaml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-bed/capabilities/mattressHardness.yaml b/drivers/SmartThings/zigbee-bed/capabilities/mattressHardness.yaml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-bed/capabilities/rightControl.yaml b/drivers/SmartThings/zigbee-bed/capabilities/rightControl.yaml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-bed/capabilities/strongExpMode.yaml b/drivers/SmartThings/zigbee-bed/capabilities/strongExpMode.yaml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-bed/capabilities/yoga.yaml b/drivers/SmartThings/zigbee-bed/capabilities/yoga.yaml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-bed/config.yml b/drivers/SmartThings/zigbee-bed/config.yml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-bed/fingerprints.yml b/drivers/SmartThings/zigbee-bed/fingerprints.yml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-bed/profiles/shus-smart-mattress.yml b/drivers/SmartThings/zigbee-bed/profiles/shus-smart-mattress.yml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-bed/src/init.lua b/drivers/SmartThings/zigbee-bed/src/init.lua old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-bed/src/shus-mattress/custom_capabilities.lua b/drivers/SmartThings/zigbee-bed/src/shus-mattress/custom_capabilities.lua old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-bed/src/shus-mattress/custom_clusters.lua b/drivers/SmartThings/zigbee-bed/src/shus-mattress/custom_clusters.lua old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-bed/src/shus-mattress/init.lua b/drivers/SmartThings/zigbee-bed/src/shus-mattress/init.lua old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua b/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua old mode 100755 new mode 100644 index 0ba0441719..46bdf5bd3b --- a/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua +++ b/drivers/SmartThings/zigbee-bed/src/test/test_shus_mattress.lua @@ -1,924 +1,924 @@ --- Copyright 2024 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 cluster_base = require "st.zigbee.cluster_base" -local data_types = require "st.zigbee.data_types" -local t_utils = require "integration_test.utils" -local zigbee_test_utils = require "integration_test.zigbee_test_utils" -local custom_capabilities = require "shus-mattress/custom_capabilities" - -local shus_mattress_profile_def = t_utils.get_profile_definition("shus-smart-mattress.yml") -test.add_package_capability("aiMode.yaml") -test.add_package_capability("autoInflation.yaml") -test.add_package_capability("leftControl.yaml") -test.add_package_capability("rightControl.yaml") -test.add_package_capability("strongExpMode.yaml") -test.add_package_capability("yoga.yaml") -test.add_package_capability("mattressHardness.yaml") - -local PRIVATE_CLUSTER_ID = 0xFCC2 -local MFG_CODE = 0x1235 - -local mock_device = test.mock_device.build_test_zigbee_device( -{ - label = "Shus Smart Mattress", - profile = shus_mattress_profile_def, - zigbee_endpoints = { - [1] = { - id = 1, - manufacturer = "SHUS", - model = "SX-1", - server_clusters = { 0x0000,PRIVATE_CLUSTER_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( - "lifecycle - added test", - function() - test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.yoga.supportedYogaState({"stop", "left", "right"}) )) - local read_0x0006_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0006, MFG_CODE) - local read_0x0007_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0007, MFG_CODE) - local read_0x0009_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0009, MFG_CODE) - local read_0x000a_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000a, MFG_CODE) - local read_0x0000_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0000, MFG_CODE) - local read_0x0001_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0001, MFG_CODE) - local read_0x0002_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0002, MFG_CODE) - local read_0x0003_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0003, MFG_CODE) - local read_0x0004_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0004, MFG_CODE) - local read_0x0005_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0005, MFG_CODE) - local read_0x0008_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0008, MFG_CODE) - local read_0x000C_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000C, MFG_CODE) - local read_0x000D_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000D, MFG_CODE) - local read_0x000E_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000E, MFG_CODE) - local read_0x000F_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000F, MFG_CODE) - local read_0x0010_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0010, MFG_CODE) - local read_0x0011_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0011, MFG_CODE) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0006_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0007_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0009_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x000a_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0000_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0001_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0002_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0003_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0004_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0005_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0008_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x000C_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x000D_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x000E_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x000F_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0010_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0011_messge}) - end -) - -test.register_coroutine_test( - "capability - refresh", - function() - test.socket.capability:__queue_receive({ mock_device.id, - { capability = "refresh", component = "main", command = "refresh", args = {} } }) - local read_0x0006_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0006, MFG_CODE) - local read_0x0007_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0007, MFG_CODE) - local read_0x0009_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0009, MFG_CODE) - local read_0x000a_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000a, MFG_CODE) - local read_0x0000_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0000, MFG_CODE) - local read_0x0001_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0001, MFG_CODE) - local read_0x0002_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0002, MFG_CODE) - local read_0x0003_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0003, MFG_CODE) - local read_0x0004_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0004, MFG_CODE) - local read_0x0005_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0005, MFG_CODE) - local read_0x0008_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0008, MFG_CODE) - local read_0x000C_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000C, MFG_CODE) - local read_0x000D_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000D, MFG_CODE) - local read_0x000E_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000E, MFG_CODE) - local read_0x000F_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000F, MFG_CODE) - local read_0x0010_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0010, MFG_CODE) - local read_0x0011_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0011, MFG_CODE) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0006_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0007_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0009_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x000a_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0000_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0001_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0002_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0003_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0004_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0005_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0008_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x000C_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x000D_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x000E_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x000F_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0010_messge}) - test.socket.zigbee:__expect_send({mock_device.id, read_0x0011_messge}) - end -) - -test.register_coroutine_test( - "Device reported leftback 0 and driver emit custom_capabilities.left_control.leftback.idle()", - function() - local attr_report_data = { - { 0x0000, data_types.Uint8.ID, 0 } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.leftback.idle())) - end -) - -test.register_coroutine_test( - "Device reported leftback 1 and driver emit custom_capabilities.left_control.leftback.idle()", - function() - local attr_report_data = { - { 0x0000, data_types.Uint8.ID, 1 } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.leftback.idle())) - end -) - -test.register_coroutine_test( - "Device reported leftwaist 0 and driver emit custom_capabilities.left_control.leftwaist.idle()", - function() - local attr_report_data = { - { 0x0001, data_types.Uint8.ID, 0 } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.leftwaist.idle())) - end -) - -test.register_coroutine_test( - "Device reported leftwaist 1 and driver emit custom_capabilities.left_control.leftwaist.idle()", - function() - local attr_report_data = { - { 0x0001, data_types.Uint8.ID, 1 } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.leftwaist.idle())) - end -) - -test.register_coroutine_test( - "Device reported lefthip 0 and driver emit custom_capabilities.left_control.lefthip.idle()", - function() - local attr_report_data = { - { 0x0002, data_types.Uint8.ID, 0 } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.lefthip.idle())) - end -) - -test.register_coroutine_test( - "Device reported lefthip 1 and driver emit custom_capabilities.left_control.lefthip.idle()", - function() - local attr_report_data = { - { 0x0002, data_types.Uint8.ID, 1 } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.lefthip.idle())) - end -) - -test.register_coroutine_test( - "Device reported rightback 0 and driver emit custom_capabilities.right_control.rightback.idle()", - function() - local attr_report_data = { - { 0x0003, data_types.Uint8.ID, 0 } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.right_control.rightback.idle())) - end -) - -test.register_coroutine_test( - "Device reported rightback 1 and driver emit custom_capabilities.right_control.rightback.idle()", - function() - local attr_report_data = { - { 0x0003, data_types.Uint8.ID, 1 } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.right_control.rightback.idle())) - end -) - -test.register_coroutine_test( - "Device reported rightwaist 0 and driver emit custom_capabilities.right_control.rightwaist.idle()", - function() - local attr_report_data = { - { 0x0004, data_types.Uint8.ID, 0 } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.right_control.rightwaist.idle())) - end -) - -test.register_coroutine_test( - "Device reported rightwaist 1 and driver emit custom_capabilities.right_control.rightwaist.idle()", - function() - local attr_report_data = { - { 0x0004, data_types.Uint8.ID, 1 } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.right_control.rightwaist.idle())) - end -) - -test.register_coroutine_test( - "Device reported righthip 0 and driver emit custom_capabilities.right_control.righthip.idle()", - function() - local attr_report_data = { - { 0x0005, data_types.Uint8.ID, 0 } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.right_control.righthip.idle())) - end -) - -test.register_coroutine_test( - "Device reported righthip 1 and driver emit custom_capabilities.right_control.righthip.idle()", - function() - local attr_report_data = { - { 0x0005, data_types.Uint8.ID, 1 } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.right_control.righthip.idle())) - end -) - -test.register_coroutine_test( - "Device reported leftBackHardness 1 and driver emit custom_capabilities.mattressHardness.leftBackHardness(1)", - function() - local attr_report_data = { - { 0x000C, data_types.Uint8.ID, 1 } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.mattressHardness.leftBackHardness(1))) - end -) - -test.register_coroutine_test( - "Device reported leftWaistHardness 1 and driver emit custom_capabilities.mattressHardness.leftWaistHardness(1)", - function() - local attr_report_data = { - { 0x000D, data_types.Uint8.ID, 1 } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.mattressHardness.leftWaistHardness(1))) - end -) - -test.register_coroutine_test( - "Device reported leftHipHardness 1 and driver emit custom_capabilities.mattressHardness.leftHipHardness(1)", - function() - local attr_report_data = { - { 0x000E, data_types.Uint8.ID, 1 } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.mattressHardness.leftHipHardness(1))) - end -) - -test.register_coroutine_test( - "Device reported rightBackHardness 1 and driver emit custom_capabilities.mattressHardness.rightBackHardness(1)", - function() - local attr_report_data = { - { 0x000F, data_types.Uint8.ID, 1 } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.mattressHardness.rightBackHardness(1))) - end -) - -test.register_coroutine_test( - "Device reported rightWaistHardness 1 and driver emit custom_capabilities.mattressHardness.rightWaistHardness(1)", - function() - local attr_report_data = { - { 0x0010, data_types.Uint8.ID, 1 } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.mattressHardness.rightWaistHardness(1))) - end -) - -test.register_coroutine_test( - "Device reported rightHipHardness 1 and driver emit custom_capabilities.mattressHardness.rightHipHardness(1)", - function() - local attr_report_data = { - { 0x0011, data_types.Uint8.ID, 1 } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.mattressHardness.rightHipHardness(1))) - end -) - -test.register_coroutine_test( - "Device reported yoga 2 and driver emit custom_capabilities.yoga.state.right()", - function() - local attr_report_data = { - { 0x0008, data_types.Uint8.ID, 2 } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.yoga.state.right())) - end -) - -test.register_coroutine_test( - "Device reported yoga 1 and driver emit custom_capabilities.yoga.state.left()", - function() - local attr_report_data = { - { 0x0008, data_types.Uint8.ID, 1 } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.yoga.state.left())) - end -) - -test.register_coroutine_test( - "Device reported yoga 0 and driver emit custom_capabilities.yoga.state.stop()", - function() - local attr_report_data = { - { 0x0008, data_types.Uint8.ID, 0 } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.yoga.state.stop())) - end -) - -test.register_coroutine_test( - "Device reported ai_mode left false and driver emit custom_capabilities.ai_mode.left.off()", - function() - local attr_report_data = { - { 0x0006, data_types.Boolean.ID, false } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.ai_mode.left.off())) - end -) - -test.register_coroutine_test( - "Device reported ai_mode left true and driver emit custom_capabilities.ai_mode.left.on()", - function() - local attr_report_data = { - { 0x0006, data_types.Boolean.ID, true } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.ai_mode.left.on())) - end -) - -test.register_coroutine_test( - "Device reported ai_mode right true and driver emit custom_capabilities.ai_mode.right.on()", - function() - local attr_report_data = { - { 0x0007, data_types.Boolean.ID, true } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.ai_mode.right.on())) - end -) - -test.register_coroutine_test( - "Device reported ai_mode right false and driver emit custom_capabilities.ai_mode.right.off()", - function() - local attr_report_data = { - { 0x0007, data_types.Boolean.ID, false } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.ai_mode.right.off())) - end -) - -test.register_coroutine_test( - "Device reported inflationState false and driver emit custom_capabilities.auto_inflation.inflationState.off()", - function() - local attr_report_data = { - { 0x0009, data_types.Boolean.ID, false } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.auto_inflation.inflationState.off())) - end -) - -test.register_coroutine_test( - "Device reported inflationState true and driver emit custom_capabilities.auto_inflation.inflationState.on()", - function() - local attr_report_data = { - { 0x0009, data_types.Boolean.ID, true } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.auto_inflation.inflationState.on())) - end -) - -test.register_coroutine_test( - "Device reported strong_exp_mode false and driver emit custom_capabilities.strong_exp_mode.expState.off()", - function() - local attr_report_data = { - { 0x000a, data_types.Boolean.ID, false } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.strong_exp_mode.expState.off())) - end -) - -test.register_coroutine_test( - "Device reported strong_exp_mode true and driver emit custom_capabilities.strong_exp_mode.expState.on()", - function() - local attr_report_data = { - { 0x000a, data_types.Boolean.ID, true } - } - test.socket.zigbee:__queue_receive({ - mock_device.id, - zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.strong_exp_mode.expState.on())) - end -) - - -test.register_coroutine_test( - "capability leftControl on and driver send on ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.ai_mode.ID, component = "main", command ="leftControl" , args = {"on"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0006, MFG_CODE, data_types.Boolean, true) - }) - end -) - -test.register_coroutine_test( - "capability leftControl off and driver send off ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.ai_mode.ID, component = "main", command ="leftControl" , args = {"off"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0006, MFG_CODE, data_types.Boolean, false) - }) - end -) - -test.register_coroutine_test( - "capability rightControl on and driver send on ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.ai_mode.ID, component = "main", command ="rightControl" , args = {"on"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0007, MFG_CODE, data_types.Boolean, true) - }) - end -) - -test.register_coroutine_test( - "capability rightControl off and driver send off ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.ai_mode.ID, component = "main", command ="rightControl" , args = {"off"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0007, MFG_CODE, data_types.Boolean, false) - }) - end -) - -test.register_coroutine_test( - "capability auto_inflation stateControl on and driver send on ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.auto_inflation.ID, component = "main", command ="stateControl" , args = {"on"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0009, MFG_CODE, data_types.Boolean, true) - }) - end -) - -test.register_coroutine_test( - "capability auto_inflation stateControl off and driver send off ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.auto_inflation.ID, component = "main", command ="stateControl" , args = {"off"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0009, MFG_CODE, data_types.Boolean, false) - }) - end -) - -test.register_coroutine_test( - "capability strong_exp_mode stateControl on and driver send on ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.strong_exp_mode.ID, component = "main", command ="stateControl" , args = {"on"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x000a, MFG_CODE, data_types.Boolean, true) - }) - end -) - -test.register_coroutine_test( - "capability strong_exp_mode stateControl off and driver send off ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.strong_exp_mode.ID, component = "main", command ="stateControl" , args = {"off"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x000a, MFG_CODE, data_types.Boolean, false) - }) - end -) - -test.register_coroutine_test( - "capability left_control backControl soft and driver send soft ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.left_control.ID, component = "main", command ="backControl" , args = {"soft"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0000, MFG_CODE, data_types.Uint8, 0) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.leftback.soft())) - end -) - -test.register_coroutine_test( - "capability waistControl backControl soft and driver send soft ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.left_control.ID, component = "main", command ="waistControl" , args = {"soft"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0001, MFG_CODE, data_types.Uint8, 0) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.leftwaist.soft())) - end -) - -test.register_coroutine_test( - "capability left_control hipControl soft and driver send soft ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.left_control.ID, component = "main", command ="hipControl" , args = {"soft"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0002, MFG_CODE, data_types.Uint8, 0) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.lefthip.soft())) - end -) - -test.register_coroutine_test( - "capability left_control backControl hard and driver send hard ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.left_control.ID, component = "main", command ="backControl" , args = {"hard"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0000, MFG_CODE, data_types.Uint8, 1) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.leftback.hard())) - end -) - -test.register_coroutine_test( - "capability waistControl backControl hard and driver send hard ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.left_control.ID, component = "main", command ="waistControl" , args = {"hard"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0001, MFG_CODE, data_types.Uint8, 1) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.leftwaist.hard())) - end -) - -test.register_coroutine_test( - "capability left_control hipControl hard and driver send hard ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.left_control.ID, component = "main", command ="hipControl" , args = {"hard"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0002, MFG_CODE, data_types.Uint8, 1) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.left_control.lefthip.hard())) - end -) - -test.register_coroutine_test( - "capability right_control backControl soft and driver send soft ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.right_control.ID, component = "main", command ="backControl" , args = {"soft"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0003, MFG_CODE, data_types.Uint8, 0) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.right_control.rightback.soft())) - end -) - -test.register_coroutine_test( - "capability right_control waistControl soft and driver send soft ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.right_control.ID, component = "main", command ="waistControl" , args = {"soft"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0004, MFG_CODE, data_types.Uint8, 0) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.right_control.rightwaist.soft())) - end -) - -test.register_coroutine_test( - "capability right_control hipControl soft and driver send soft ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.right_control.ID, component = "main", command ="hipControl" , args = {"soft"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0005, MFG_CODE, data_types.Uint8, 0) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.right_control.righthip.soft())) - end -) - -test.register_coroutine_test( - "capability right_control backControl hard and driver send hard ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.right_control.ID, component = "main", command ="backControl" , args = {"hard"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0003, MFG_CODE, data_types.Uint8, 1) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.right_control.rightback.hard())) - end -) - -test.register_coroutine_test( - "capability right_control waistControl hard and driver send hard ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.right_control.ID, component = "main", command ="waistControl" , args = {"hard"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0004, MFG_CODE, data_types.Uint8, 1) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.right_control.rightwaist.hard())) - end -) - -test.register_coroutine_test( - "capability right_control hipControl hard and driver send hard ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.right_control.ID, component = "main", command ="hipControl" , args = {"hard"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0005, MFG_CODE, data_types.Uint8, 1) - }) - test.socket.capability:__expect_send(mock_device:generate_test_message("main", - custom_capabilities.right_control.righthip.hard())) - end -) - -test.register_coroutine_test( - "capability yoga stateControl left and driver send left ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.yoga.ID, component = "main", command ="stateControl" , args = {"left"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0008, MFG_CODE, data_types.Uint8, 1) - }) - end -) - -test.register_coroutine_test( - "capability yoga stateControl right and driver send right ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.yoga.ID, component = "main", command ="stateControl" , args = {"right"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0008, MFG_CODE, data_types.Uint8, 2) - }) - end -) - -test.register_coroutine_test( - "capability yoga stateControl stop and driver send stop ", - function() - test.socket.capability:__queue_receive({ - mock_device.id, - { capability = custom_capabilities.yoga.ID, component = "main", command ="stateControl" , args = {"stop"}} - }) - test.socket.zigbee:__expect_send({ mock_device.id, - cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, - 0x0008, MFG_CODE, data_types.Uint8, 0) - }) - end -) - -test.run_registered_tests() +-- Copyright 2024 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 cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" +local t_utils = require "integration_test.utils" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local custom_capabilities = require "shus-mattress/custom_capabilities" + +local shus_mattress_profile_def = t_utils.get_profile_definition("shus-smart-mattress.yml") +test.add_package_capability("aiMode.yaml") +test.add_package_capability("autoInflation.yaml") +test.add_package_capability("leftControl.yaml") +test.add_package_capability("rightControl.yaml") +test.add_package_capability("strongExpMode.yaml") +test.add_package_capability("yoga.yaml") +test.add_package_capability("mattressHardness.yaml") + +local PRIVATE_CLUSTER_ID = 0xFCC2 +local MFG_CODE = 0x1235 + +local mock_device = test.mock_device.build_test_zigbee_device( +{ + label = "Shus Smart Mattress", + profile = shus_mattress_profile_def, + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "SHUS", + model = "SX-1", + server_clusters = { 0x0000,PRIVATE_CLUSTER_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( + "lifecycle - added test", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.yoga.supportedYogaState({"stop", "left", "right"}) )) + local read_0x0006_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0006, MFG_CODE) + local read_0x0007_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0007, MFG_CODE) + local read_0x0009_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0009, MFG_CODE) + local read_0x000a_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000a, MFG_CODE) + local read_0x0000_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0000, MFG_CODE) + local read_0x0001_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0001, MFG_CODE) + local read_0x0002_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0002, MFG_CODE) + local read_0x0003_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0003, MFG_CODE) + local read_0x0004_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0004, MFG_CODE) + local read_0x0005_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0005, MFG_CODE) + local read_0x0008_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0008, MFG_CODE) + local read_0x000C_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000C, MFG_CODE) + local read_0x000D_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000D, MFG_CODE) + local read_0x000E_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000E, MFG_CODE) + local read_0x000F_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000F, MFG_CODE) + local read_0x0010_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0010, MFG_CODE) + local read_0x0011_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0011, MFG_CODE) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0006_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0007_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0009_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x000a_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0000_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0001_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0002_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0003_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0004_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0005_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0008_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x000C_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x000D_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x000E_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x000F_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0010_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0011_messge}) + end +) + +test.register_coroutine_test( + "capability - refresh", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "refresh", component = "main", command = "refresh", args = {} } }) + local read_0x0006_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0006, MFG_CODE) + local read_0x0007_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0007, MFG_CODE) + local read_0x0009_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0009, MFG_CODE) + local read_0x000a_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000a, MFG_CODE) + local read_0x0000_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0000, MFG_CODE) + local read_0x0001_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0001, MFG_CODE) + local read_0x0002_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0002, MFG_CODE) + local read_0x0003_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0003, MFG_CODE) + local read_0x0004_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0004, MFG_CODE) + local read_0x0005_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0005, MFG_CODE) + local read_0x0008_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0008, MFG_CODE) + local read_0x000C_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000C, MFG_CODE) + local read_0x000D_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000D, MFG_CODE) + local read_0x000E_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000E, MFG_CODE) + local read_0x000F_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x000F, MFG_CODE) + local read_0x0010_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0010, MFG_CODE) + local read_0x0011_messge = cluster_base.read_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, 0x0011, MFG_CODE) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0006_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0007_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0009_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x000a_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0000_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0001_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0002_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0003_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0004_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0005_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0008_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x000C_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x000D_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x000E_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x000F_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0010_messge}) + test.socket.zigbee:__expect_send({mock_device.id, read_0x0011_messge}) + end +) + +test.register_coroutine_test( + "Device reported leftback 0 and driver emit custom_capabilities.left_control.leftback.idle()", + function() + local attr_report_data = { + { 0x0000, data_types.Uint8.ID, 0 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.left_control.leftback.idle())) + end +) + +test.register_coroutine_test( + "Device reported leftback 1 and driver emit custom_capabilities.left_control.leftback.idle()", + function() + local attr_report_data = { + { 0x0000, data_types.Uint8.ID, 1 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.left_control.leftback.idle())) + end +) + +test.register_coroutine_test( + "Device reported leftwaist 0 and driver emit custom_capabilities.left_control.leftwaist.idle()", + function() + local attr_report_data = { + { 0x0001, data_types.Uint8.ID, 0 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.left_control.leftwaist.idle())) + end +) + +test.register_coroutine_test( + "Device reported leftwaist 1 and driver emit custom_capabilities.left_control.leftwaist.idle()", + function() + local attr_report_data = { + { 0x0001, data_types.Uint8.ID, 1 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.left_control.leftwaist.idle())) + end +) + +test.register_coroutine_test( + "Device reported lefthip 0 and driver emit custom_capabilities.left_control.lefthip.idle()", + function() + local attr_report_data = { + { 0x0002, data_types.Uint8.ID, 0 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.left_control.lefthip.idle())) + end +) + +test.register_coroutine_test( + "Device reported lefthip 1 and driver emit custom_capabilities.left_control.lefthip.idle()", + function() + local attr_report_data = { + { 0x0002, data_types.Uint8.ID, 1 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.left_control.lefthip.idle())) + end +) + +test.register_coroutine_test( + "Device reported rightback 0 and driver emit custom_capabilities.right_control.rightback.idle()", + function() + local attr_report_data = { + { 0x0003, data_types.Uint8.ID, 0 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.right_control.rightback.idle())) + end +) + +test.register_coroutine_test( + "Device reported rightback 1 and driver emit custom_capabilities.right_control.rightback.idle()", + function() + local attr_report_data = { + { 0x0003, data_types.Uint8.ID, 1 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.right_control.rightback.idle())) + end +) + +test.register_coroutine_test( + "Device reported rightwaist 0 and driver emit custom_capabilities.right_control.rightwaist.idle()", + function() + local attr_report_data = { + { 0x0004, data_types.Uint8.ID, 0 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.right_control.rightwaist.idle())) + end +) + +test.register_coroutine_test( + "Device reported rightwaist 1 and driver emit custom_capabilities.right_control.rightwaist.idle()", + function() + local attr_report_data = { + { 0x0004, data_types.Uint8.ID, 1 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.right_control.rightwaist.idle())) + end +) + +test.register_coroutine_test( + "Device reported righthip 0 and driver emit custom_capabilities.right_control.righthip.idle()", + function() + local attr_report_data = { + { 0x0005, data_types.Uint8.ID, 0 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.right_control.righthip.idle())) + end +) + +test.register_coroutine_test( + "Device reported righthip 1 and driver emit custom_capabilities.right_control.righthip.idle()", + function() + local attr_report_data = { + { 0x0005, data_types.Uint8.ID, 1 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.right_control.righthip.idle())) + end +) + +test.register_coroutine_test( + "Device reported leftBackHardness 1 and driver emit custom_capabilities.mattressHardness.leftBackHardness(1)", + function() + local attr_report_data = { + { 0x000C, data_types.Uint8.ID, 1 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.mattressHardness.leftBackHardness(1))) + end +) + +test.register_coroutine_test( + "Device reported leftWaistHardness 1 and driver emit custom_capabilities.mattressHardness.leftWaistHardness(1)", + function() + local attr_report_data = { + { 0x000D, data_types.Uint8.ID, 1 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.mattressHardness.leftWaistHardness(1))) + end +) + +test.register_coroutine_test( + "Device reported leftHipHardness 1 and driver emit custom_capabilities.mattressHardness.leftHipHardness(1)", + function() + local attr_report_data = { + { 0x000E, data_types.Uint8.ID, 1 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.mattressHardness.leftHipHardness(1))) + end +) + +test.register_coroutine_test( + "Device reported rightBackHardness 1 and driver emit custom_capabilities.mattressHardness.rightBackHardness(1)", + function() + local attr_report_data = { + { 0x000F, data_types.Uint8.ID, 1 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.mattressHardness.rightBackHardness(1))) + end +) + +test.register_coroutine_test( + "Device reported rightWaistHardness 1 and driver emit custom_capabilities.mattressHardness.rightWaistHardness(1)", + function() + local attr_report_data = { + { 0x0010, data_types.Uint8.ID, 1 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.mattressHardness.rightWaistHardness(1))) + end +) + +test.register_coroutine_test( + "Device reported rightHipHardness 1 and driver emit custom_capabilities.mattressHardness.rightHipHardness(1)", + function() + local attr_report_data = { + { 0x0011, data_types.Uint8.ID, 1 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.mattressHardness.rightHipHardness(1))) + end +) + +test.register_coroutine_test( + "Device reported yoga 2 and driver emit custom_capabilities.yoga.state.right()", + function() + local attr_report_data = { + { 0x0008, data_types.Uint8.ID, 2 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.yoga.state.right())) + end +) + +test.register_coroutine_test( + "Device reported yoga 1 and driver emit custom_capabilities.yoga.state.left()", + function() + local attr_report_data = { + { 0x0008, data_types.Uint8.ID, 1 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.yoga.state.left())) + end +) + +test.register_coroutine_test( + "Device reported yoga 0 and driver emit custom_capabilities.yoga.state.stop()", + function() + local attr_report_data = { + { 0x0008, data_types.Uint8.ID, 0 } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.yoga.state.stop())) + end +) + +test.register_coroutine_test( + "Device reported ai_mode left false and driver emit custom_capabilities.ai_mode.left.off()", + function() + local attr_report_data = { + { 0x0006, data_types.Boolean.ID, false } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.ai_mode.left.off())) + end +) + +test.register_coroutine_test( + "Device reported ai_mode left true and driver emit custom_capabilities.ai_mode.left.on()", + function() + local attr_report_data = { + { 0x0006, data_types.Boolean.ID, true } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.ai_mode.left.on())) + end +) + +test.register_coroutine_test( + "Device reported ai_mode right true and driver emit custom_capabilities.ai_mode.right.on()", + function() + local attr_report_data = { + { 0x0007, data_types.Boolean.ID, true } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.ai_mode.right.on())) + end +) + +test.register_coroutine_test( + "Device reported ai_mode right false and driver emit custom_capabilities.ai_mode.right.off()", + function() + local attr_report_data = { + { 0x0007, data_types.Boolean.ID, false } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.ai_mode.right.off())) + end +) + +test.register_coroutine_test( + "Device reported inflationState false and driver emit custom_capabilities.auto_inflation.inflationState.off()", + function() + local attr_report_data = { + { 0x0009, data_types.Boolean.ID, false } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.auto_inflation.inflationState.off())) + end +) + +test.register_coroutine_test( + "Device reported inflationState true and driver emit custom_capabilities.auto_inflation.inflationState.on()", + function() + local attr_report_data = { + { 0x0009, data_types.Boolean.ID, true } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.auto_inflation.inflationState.on())) + end +) + +test.register_coroutine_test( + "Device reported strong_exp_mode false and driver emit custom_capabilities.strong_exp_mode.expState.off()", + function() + local attr_report_data = { + { 0x000a, data_types.Boolean.ID, false } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.strong_exp_mode.expState.off())) + end +) + +test.register_coroutine_test( + "Device reported strong_exp_mode true and driver emit custom_capabilities.strong_exp_mode.expState.on()", + function() + local attr_report_data = { + { 0x000a, data_types.Boolean.ID, true } + } + test.socket.zigbee:__queue_receive({ + mock_device.id, + zigbee_test_utils.build_attribute_report(mock_device, PRIVATE_CLUSTER_ID, attr_report_data, MFG_CODE) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.strong_exp_mode.expState.on())) + end +) + + +test.register_coroutine_test( + "capability leftControl on and driver send on ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.ai_mode.ID, component = "main", command ="leftControl" , args = {"on"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0006, MFG_CODE, data_types.Boolean, true) + }) + end +) + +test.register_coroutine_test( + "capability leftControl off and driver send off ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.ai_mode.ID, component = "main", command ="leftControl" , args = {"off"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0006, MFG_CODE, data_types.Boolean, false) + }) + end +) + +test.register_coroutine_test( + "capability rightControl on and driver send on ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.ai_mode.ID, component = "main", command ="rightControl" , args = {"on"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0007, MFG_CODE, data_types.Boolean, true) + }) + end +) + +test.register_coroutine_test( + "capability rightControl off and driver send off ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.ai_mode.ID, component = "main", command ="rightControl" , args = {"off"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0007, MFG_CODE, data_types.Boolean, false) + }) + end +) + +test.register_coroutine_test( + "capability auto_inflation stateControl on and driver send on ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.auto_inflation.ID, component = "main", command ="stateControl" , args = {"on"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0009, MFG_CODE, data_types.Boolean, true) + }) + end +) + +test.register_coroutine_test( + "capability auto_inflation stateControl off and driver send off ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.auto_inflation.ID, component = "main", command ="stateControl" , args = {"off"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0009, MFG_CODE, data_types.Boolean, false) + }) + end +) + +test.register_coroutine_test( + "capability strong_exp_mode stateControl on and driver send on ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.strong_exp_mode.ID, component = "main", command ="stateControl" , args = {"on"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x000a, MFG_CODE, data_types.Boolean, true) + }) + end +) + +test.register_coroutine_test( + "capability strong_exp_mode stateControl off and driver send off ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.strong_exp_mode.ID, component = "main", command ="stateControl" , args = {"off"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x000a, MFG_CODE, data_types.Boolean, false) + }) + end +) + +test.register_coroutine_test( + "capability left_control backControl soft and driver send soft ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.left_control.ID, component = "main", command ="backControl" , args = {"soft"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0000, MFG_CODE, data_types.Uint8, 0) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.left_control.leftback.soft())) + end +) + +test.register_coroutine_test( + "capability waistControl backControl soft and driver send soft ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.left_control.ID, component = "main", command ="waistControl" , args = {"soft"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0001, MFG_CODE, data_types.Uint8, 0) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.left_control.leftwaist.soft())) + end +) + +test.register_coroutine_test( + "capability left_control hipControl soft and driver send soft ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.left_control.ID, component = "main", command ="hipControl" , args = {"soft"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0002, MFG_CODE, data_types.Uint8, 0) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.left_control.lefthip.soft())) + end +) + +test.register_coroutine_test( + "capability left_control backControl hard and driver send hard ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.left_control.ID, component = "main", command ="backControl" , args = {"hard"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0000, MFG_CODE, data_types.Uint8, 1) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.left_control.leftback.hard())) + end +) + +test.register_coroutine_test( + "capability waistControl backControl hard and driver send hard ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.left_control.ID, component = "main", command ="waistControl" , args = {"hard"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0001, MFG_CODE, data_types.Uint8, 1) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.left_control.leftwaist.hard())) + end +) + +test.register_coroutine_test( + "capability left_control hipControl hard and driver send hard ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.left_control.ID, component = "main", command ="hipControl" , args = {"hard"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0002, MFG_CODE, data_types.Uint8, 1) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.left_control.lefthip.hard())) + end +) + +test.register_coroutine_test( + "capability right_control backControl soft and driver send soft ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.right_control.ID, component = "main", command ="backControl" , args = {"soft"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0003, MFG_CODE, data_types.Uint8, 0) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.right_control.rightback.soft())) + end +) + +test.register_coroutine_test( + "capability right_control waistControl soft and driver send soft ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.right_control.ID, component = "main", command ="waistControl" , args = {"soft"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0004, MFG_CODE, data_types.Uint8, 0) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.right_control.rightwaist.soft())) + end +) + +test.register_coroutine_test( + "capability right_control hipControl soft and driver send soft ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.right_control.ID, component = "main", command ="hipControl" , args = {"soft"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0005, MFG_CODE, data_types.Uint8, 0) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.right_control.righthip.soft())) + end +) + +test.register_coroutine_test( + "capability right_control backControl hard and driver send hard ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.right_control.ID, component = "main", command ="backControl" , args = {"hard"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0003, MFG_CODE, data_types.Uint8, 1) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.right_control.rightback.hard())) + end +) + +test.register_coroutine_test( + "capability right_control waistControl hard and driver send hard ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.right_control.ID, component = "main", command ="waistControl" , args = {"hard"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0004, MFG_CODE, data_types.Uint8, 1) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.right_control.rightwaist.hard())) + end +) + +test.register_coroutine_test( + "capability right_control hipControl hard and driver send hard ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.right_control.ID, component = "main", command ="hipControl" , args = {"hard"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0005, MFG_CODE, data_types.Uint8, 1) + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + custom_capabilities.right_control.righthip.hard())) + end +) + +test.register_coroutine_test( + "capability yoga stateControl left and driver send left ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.yoga.ID, component = "main", command ="stateControl" , args = {"left"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0008, MFG_CODE, data_types.Uint8, 1) + }) + end +) + +test.register_coroutine_test( + "capability yoga stateControl right and driver send right ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.yoga.ID, component = "main", command ="stateControl" , args = {"right"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0008, MFG_CODE, data_types.Uint8, 2) + }) + end +) + +test.register_coroutine_test( + "capability yoga stateControl stop and driver send stop ", + function() + test.socket.capability:__queue_receive({ + mock_device.id, + { capability = custom_capabilities.yoga.ID, component = "main", command ="stateControl" , args = {"stop"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + 0x0008, MFG_CODE, data_types.Uint8, 0) + }) + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-sensor/profiles/generic-motion-illuminance.yml b/drivers/SmartThings/zigbee-sensor/profiles/generic-motion-illuminance.yml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-sensor/src/contact/init.lua b/drivers/SmartThings/zigbee-sensor/src/contact/init.lua old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-sensor/src/motion-illuminance/init.lua b/drivers/SmartThings/zigbee-sensor/src/motion-illuminance/init.lua old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-sensor/src/motion/init.lua b/drivers/SmartThings/zigbee-sensor/src/motion/init.lua old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-sensor/src/waterleak/init.lua b/drivers/SmartThings/zigbee-sensor/src/waterleak/init.lua old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-switch/profiles/switch-smart-bath-heater-laisiao.yml b/drivers/SmartThings/zigbee-switch/profiles/switch-smart-bath-heater-laisiao.yml old mode 100755 new mode 100644 index 214784e70d..682b61e607 --- a/drivers/SmartThings/zigbee-switch/profiles/switch-smart-bath-heater-laisiao.yml +++ b/drivers/SmartThings/zigbee-switch/profiles/switch-smart-bath-heater-laisiao.yml @@ -1,62 +1,62 @@ -name: switch-smart-bath-heater-laisiao -components: - - id: main - label: "待机" - capabilities: - - id: switch - version: 1 - - id: firmwareUpdate - version: 1 - - id: refresh - version: 1 - categories: - - name: BathroomHeater - - id: switch2 - label: "照明" - capabilities: - - id: switch - version: 1 - categories: - - name: BathroomHeater - - id: switch3 - label: "强暖" - capabilities: - - id: switch - version: 1 - categories: - - name: BathroomHeater - - id: switch4 - label: "弱暖" - capabilities: - - id: switch - version: 1 - categories: - - name: BathroomHeater - - id: switch5 - label: "换气" - capabilities: - - id: switch - version: 1 - categories: - - name: BathroomHeater - - id: switch6 - label: "自然风" - capabilities: - - id: switch - version: 1 - categories: - - name: BathroomHeater - - id: switch7 - label: "干燥" - capabilities: - - id: switch - version: 1 - categories: - - name: BathroomHeater - - id: switch8 - label: "摆页" - capabilities: - - id: switch - version: 1 - categories: - - name: BathroomHeater +name: switch-smart-bath-heater-laisiao +components: + - id: main + label: "待机" + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: BathroomHeater + - id: switch2 + label: "照明" + capabilities: + - id: switch + version: 1 + categories: + - name: BathroomHeater + - id: switch3 + label: "强暖" + capabilities: + - id: switch + version: 1 + categories: + - name: BathroomHeater + - id: switch4 + label: "弱暖" + capabilities: + - id: switch + version: 1 + categories: + - name: BathroomHeater + - id: switch5 + label: "换气" + capabilities: + - id: switch + version: 1 + categories: + - name: BathroomHeater + - id: switch6 + label: "自然风" + capabilities: + - id: switch + version: 1 + categories: + - name: BathroomHeater + - id: switch7 + label: "干燥" + capabilities: + - id: switch + version: 1 + categories: + - name: BathroomHeater + - id: switch8 + label: "摆页" + capabilities: + - id: switch + version: 1 + categories: + - name: BathroomHeater diff --git a/drivers/SmartThings/zigbee-switch/src/laisiao/init.lua b/drivers/SmartThings/zigbee-switch/src/laisiao/init.lua old mode 100755 new mode 100644 index edb829bf1f..dda3803fc4 --- a/drivers/SmartThings/zigbee-switch/src/laisiao/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/laisiao/init.lua @@ -1,84 +1,84 @@ --- Copyright 2024 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 capabilities = require "st.capabilities" -local zcl_clusters = require "st.zigbee.zcl.clusters" -local configurations = require "configurations" - -local FINGERPRINTS = { - { mfr = "LAISIAO", model = "yuba" }, -} - -local function can_handle_laisiao(opts, driver, device, ...) - for _, fingerprint in ipairs(FINGERPRINTS) do - if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then - local subdriver = require("laisiao") - return true, subdriver - end - end - return false -end - -local function component_to_endpoint(device, component_id) - if component_id == "main" then - return device.fingerprinted_endpoint_id - else - local ep_num = component_id:match("switch(%d)") - return ep_num and tonumber(ep_num) or device.fingerprinted_endpoint_id - end -end - -local function endpoint_to_component(device, ep) - if ep == device.fingerprinted_endpoint_id then - return "main" - else - return string.format("switch%d", ep) - end -end - -local device_init = function(self, device) - device:set_component_to_endpoint_fn(component_to_endpoint) - device:set_endpoint_to_component_fn(endpoint_to_component) -end - -local function on_handler(driver, device, command) - local attr = capabilities.switch.switch - if command.component == "main" then - -- The main component is set to on by the device and cannot be set to on itself. It can only trigger off - device:emit_event_for_endpoint(device.fingerprinted_endpoint_id, attr.on()) - device.thread:call_with_delay(1, function(d) - device:emit_event_for_endpoint(device.fingerprinted_endpoint_id, attr.off()) - end) - else - device:send_to_component(command.component, zcl_clusters.OnOff.server.commands.On(device)) - end -end - -local laisiao_bath_heater = { - NAME = "Zigbee Laisiao Bathroom Heater", - supported_capabilities = { - capabilities.switch, - }, - lifecycle_handlers = { - init = configurations.power_reconfig_wrapper(device_init), - }, - capability_handlers = { - [capabilities.switch.ID] = { - [capabilities.switch.commands.on.NAME] = on_handler - } - }, - can_handle = can_handle_laisiao -} - -return laisiao_bath_heater +-- Copyright 2024 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 capabilities = require "st.capabilities" +local zcl_clusters = require "st.zigbee.zcl.clusters" +local configurations = require "configurations" + +local FINGERPRINTS = { + { mfr = "LAISIAO", model = "yuba" }, +} + +local function can_handle_laisiao(opts, driver, device, ...) + for _, fingerprint in ipairs(FINGERPRINTS) do + if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then + local subdriver = require("laisiao") + return true, subdriver + end + end + return false +end + +local function component_to_endpoint(device, component_id) + if component_id == "main" then + return device.fingerprinted_endpoint_id + else + local ep_num = component_id:match("switch(%d)") + return ep_num and tonumber(ep_num) or device.fingerprinted_endpoint_id + end +end + +local function endpoint_to_component(device, ep) + if ep == device.fingerprinted_endpoint_id then + return "main" + else + return string.format("switch%d", ep) + end +end + +local device_init = function(self, device) + device:set_component_to_endpoint_fn(component_to_endpoint) + device:set_endpoint_to_component_fn(endpoint_to_component) +end + +local function on_handler(driver, device, command) + local attr = capabilities.switch.switch + if command.component == "main" then + -- The main component is set to on by the device and cannot be set to on itself. It can only trigger off + device:emit_event_for_endpoint(device.fingerprinted_endpoint_id, attr.on()) + device.thread:call_with_delay(1, function(d) + device:emit_event_for_endpoint(device.fingerprinted_endpoint_id, attr.off()) + end) + else + device:send_to_component(command.component, zcl_clusters.OnOff.server.commands.On(device)) + end +end + +local laisiao_bath_heater = { + NAME = "Zigbee Laisiao Bathroom Heater", + supported_capabilities = { + capabilities.switch, + }, + lifecycle_handlers = { + init = configurations.power_reconfig_wrapper(device_init), + }, + capability_handlers = { + [capabilities.switch.ID] = { + [capabilities.switch.commands.on.NAME] = on_handler + } + }, + can_handle = can_handle_laisiao +} + +return laisiao_bath_heater diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_inovelli-vzm31-sn.lua b/drivers/SmartThings/zigbee-switch/src/test/test_inovelli-vzm31-sn.lua old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_laisiao_bath_heather.lua b/drivers/SmartThings/zigbee-switch/src/test/test_laisiao_bath_heather.lua old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_sinope_switch.lua b/drivers/SmartThings/zigbee-switch/src/test/test_sinope_switch.lua old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_tuya_multi.lua b/drivers/SmartThings/zigbee-switch/src/test/test_tuya_multi.lua old mode 100755 new mode 100644 index 45f3d4e2b3..aae68eb057 --- a/drivers/SmartThings/zigbee-switch/src/test/test_tuya_multi.lua +++ b/drivers/SmartThings/zigbee-switch/src/test/test_tuya_multi.lua @@ -1,93 +1,93 @@ --- Copyright 2025 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 clusters = require "st.zigbee.zcl.clusters" -local BasicCluster = clusters.Basic -local OnOffCluster = clusters.OnOff -local t_utils = require "integration_test.utils" -local zigbee_test_utils = require "integration_test.zigbee_test_utils" - -local profile = t_utils.get_profile_definition("basic-switch.yml") - -local mock_device = test.mock_device.build_test_zigbee_device({ - label = "Zigbee Switch", - profile = profile, - zigbee_endpoints = { - [1] = { - id = 1, - manufacturer = "_TZ123fas", - server_clusters = { 0x0006 }, - }, - [2] = { - id = 2, - manufacturer = "_TZ123fas", - server_clusters = { 0x0006 }, - }, - }, - fingerprinted_endpoint_id = 0x01 -}) - -zigbee_test_utils.prepare_zigbee_env_info() - -local function test_init() - mock_device:set_field("_configuration_version", 1, {persist = true}) - test.mock_device.add_test_device(mock_device) -end - -test.set_test_init_function(test_init) - -test.register_coroutine_test( - "lifecycle configure event should configure device", - function () - test.socket.zigbee:__set_channel_ordering("relaxed") - test.socket.device_lifecycle:__queue_receive({mock_device.id, "doConfigure"}) - test.socket.zigbee:__expect_send({ - mock_device.id, - OnOffCluster.attributes.OnOff:read(mock_device) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - OnOffCluster.attributes.OnOff:read(mock_device):to_endpoint(0x02) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - OnOffCluster.attributes.OnOff:configure_reporting(mock_device, 0, 300):to_endpoint(1) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - OnOffCluster.attributes.OnOff:configure_reporting(mock_device, 0, 300):to_endpoint(2) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - zigbee_test_utils.build_bind_request(mock_device, - zigbee_test_utils.mock_hub_eui, - OnOffCluster.ID, 1):to_endpoint(1) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - zigbee_test_utils.build_bind_request(mock_device, - zigbee_test_utils.mock_hub_eui, - OnOffCluster.ID, 2):to_endpoint(2) - }) - test.socket.zigbee:__expect_send({ - mock_device.id, - zigbee_test_utils.build_attribute_read(mock_device, BasicCluster.ID, {0x0004, 0x0000, 0x0001, 0x0005, 0x0007, 0xfffe}) - }) - - mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) - end -) - -test.run_registered_tests() +-- Copyright 2025 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 clusters = require "st.zigbee.zcl.clusters" +local BasicCluster = clusters.Basic +local OnOffCluster = clusters.OnOff +local t_utils = require "integration_test.utils" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" + +local profile = t_utils.get_profile_definition("basic-switch.yml") + +local mock_device = test.mock_device.build_test_zigbee_device({ + label = "Zigbee Switch", + profile = profile, + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "_TZ123fas", + server_clusters = { 0x0006 }, + }, + [2] = { + id = 2, + manufacturer = "_TZ123fas", + server_clusters = { 0x0006 }, + }, + }, + fingerprinted_endpoint_id = 0x01 +}) + +zigbee_test_utils.prepare_zigbee_env_info() + +local function test_init() + mock_device:set_field("_configuration_version", 1, {persist = true}) + test.mock_device.add_test_device(mock_device) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "lifecycle configure event should configure device", + function () + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({mock_device.id, "doConfigure"}) + test.socket.zigbee:__expect_send({ + mock_device.id, + OnOffCluster.attributes.OnOff:read(mock_device) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + OnOffCluster.attributes.OnOff:read(mock_device):to_endpoint(0x02) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + OnOffCluster.attributes.OnOff:configure_reporting(mock_device, 0, 300):to_endpoint(1) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + OnOffCluster.attributes.OnOff:configure_reporting(mock_device, 0, 300):to_endpoint(2) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, + zigbee_test_utils.mock_hub_eui, + OnOffCluster.ID, 1):to_endpoint(1) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, + zigbee_test_utils.mock_hub_eui, + OnOffCluster.ID, 2):to_endpoint(2) + }) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_attribute_read(mock_device, BasicCluster.ID, {0x0004, 0x0000, 0x0001, 0x0005, 0x0007, 0xfffe}) + }) + + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.run_registered_tests() diff --git a/drivers/SmartThings/zigbee-window-treatment/capabilities/deviceInitialization.yaml b/drivers/SmartThings/zigbee-window-treatment/capabilities/deviceInitialization.yaml index 93b7e857e9..f590c1776e 100644 --- a/drivers/SmartThings/zigbee-window-treatment/capabilities/deviceInitialization.yaml +++ b/drivers/SmartThings/zigbee-window-treatment/capabilities/deviceInitialization.yaml @@ -1,45 +1,45 @@ -id: stse.deviceInitialization -version: 1 -status: proposed -name: device initialization -ephemeral: false -attributes: - initializedState: - schema: - type: object - properties: - value: - type: string - enum: - - initialized - - notInitialized - - initializing - additionalProperties: false - required: - - value - setter: setInitializedState - supportedInitializedState: - schema: - type: object - additionalProperties: false - properties: - value: - type: array - items: - type: string - enum: - - initialized - - notInitialized - - initializing -commands: - setInitializedState: - name: setInitializedState - arguments: - - name: state - optional: false - schema: - type: string - enum: - - initialized - - notInitialized - - initializing +id: stse.deviceInitialization +version: 1 +status: proposed +name: device initialization +ephemeral: false +attributes: + initializedState: + schema: + type: object + properties: + value: + type: string + enum: + - initialized + - notInitialized + - initializing + additionalProperties: false + required: + - value + setter: setInitializedState + supportedInitializedState: + schema: + type: object + additionalProperties: false + properties: + value: + type: array + items: + type: string + enum: + - initialized + - notInitialized + - initializing +commands: + setInitializedState: + name: setInitializedState + arguments: + - name: state + optional: false + schema: + type: string + enum: + - initialized + - notInitialized + - initializing diff --git a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-aqara-curtain-driver-e1.yml b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-aqara-curtain-driver-e1.yml index dbd219e523..46f27cb73e 100644 --- a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-aqara-curtain-driver-e1.yml +++ b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-aqara-curtain-driver-e1.yml @@ -1,30 +1,30 @@ -name: window-treatment-aqara-curtain-driver-e1 -components: - - id: main - capabilities: - - id: windowShade - version: 1 - - id: windowShadeLevel - version: 1 - - id: stse.initializedStateWithGuide - version: 1 - - id: stse.hookLockState - version: 1 - - id: battery - version: 1 - - id: stse.chargingState - version: 1 - - id: firmwareUpdate - version: 1 - - id: refresh - version: 1 - categories: - - name: Blind -preferences: - - preferenceId: stse.reverseCurtainDirection - explicit: true - - preferenceId: stse.softTouch - explicit: true -metadata: - mnmn: SolutionsEngineering +name: window-treatment-aqara-curtain-driver-e1 +components: + - id: main + capabilities: + - id: windowShade + version: 1 + - id: windowShadeLevel + version: 1 + - id: stse.initializedStateWithGuide + version: 1 + - id: stse.hookLockState + version: 1 + - id: battery + version: 1 + - id: stse.chargingState + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Blind +preferences: + - preferenceId: stse.reverseCurtainDirection + explicit: true + - preferenceId: stse.softTouch + explicit: true +metadata: + mnmn: SolutionsEngineering vid: SmartThings-smartthings-Aqara_Curtain_Driver_E1 \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-aqara-roller-shade-rotate.yml b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-aqara-roller-shade-rotate.yml index c454c4d590..5ca8e65f0f 100644 --- a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-aqara-roller-shade-rotate.yml +++ b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-aqara-roller-shade-rotate.yml @@ -1,21 +1,21 @@ -name: window-treatment-aqara-roller-shade-rotate -components: - - id: main - capabilities: - - id: windowShade - version: 1 - - id: windowShadeLevel - version: 1 - - id: stse.initializedStateWithGuide - version: 1 - - id: stse.shadeRotateState - version: 1 - - id: firmwareUpdate - version: 1 - - id: refresh - version: 1 - categories: - - name: Blind -preferences: - - preferenceId: stse.reverseRollerShadeDir - explicit: true +name: window-treatment-aqara-roller-shade-rotate +components: + - id: main + capabilities: + - id: windowShade + version: 1 + - id: windowShadeLevel + version: 1 + - id: stse.initializedStateWithGuide + version: 1 + - id: stse.shadeRotateState + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Blind +preferences: + - preferenceId: stse.reverseRollerShadeDir + explicit: true diff --git a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-aqara.yml b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-aqara.yml index 6c2cc967e7..5e2812ccd8 100644 --- a/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-aqara.yml +++ b/drivers/SmartThings/zigbee-window-treatment/profiles/window-treatment-aqara.yml @@ -1,21 +1,21 @@ -name: window-treatment-aqara -components: - - id: main - capabilities: - - id: windowShade - version: 1 - - id: windowShadeLevel - version: 1 - - id: stse.deviceInitialization - version: 1 - - id: firmwareUpdate - version: 1 - - id: refresh - version: 1 - categories: - - name: Blind -preferences: - - preferenceId: stse.reverseCurtainDirection - explicit: true - - preferenceId: stse.softTouch - explicit: true +name: window-treatment-aqara +components: + - id: main + capabilities: + - id: windowShade + version: 1 + - id: windowShadeLevel + version: 1 + - id: stse.deviceInitialization + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Blind +preferences: + - preferenceId: stse.reverseCurtainDirection + explicit: true + - preferenceId: stse.softTouch + explicit: true diff --git a/drivers/SmartThings/zigbee-window-treatment/src/aqara/curtain-driver-e1/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/aqara/curtain-driver-e1/init.lua index 5d4f4d3a37..556a88bb2c 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/aqara/curtain-driver-e1/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/aqara/curtain-driver-e1/init.lua @@ -1,232 +1,232 @@ --- Copyright 2024 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 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 aqara_utils = require "aqara/aqara_utils" -local window_treatment_utils = require "window_treatment_utils" - -local Groups = clusters.Groups -local Basic = clusters.Basic -local WindowCovering = clusters.WindowCovering -local PowerConfiguration = clusters.PowerConfiguration -local PRIVATE_CURTAIN_MANUAL_ATTRIBUTE_ID = 0x0401 -local PRIVATE_CURTAIN_RANGE_FLAG_ATTRIBUTE_ID = 0x0402 -local PRIVATE_CURTAIN_STATUS_ATTRIBUTE_ID = 0x0421 -local PRIVATE_CURTAIN_LOCKING_SETTING_ATTRIBUTE_ID = 0x0427 -local PRIVATE_CURTAIN_LOCKING_STATUS_ATTRIBUTE_ID = 0x0428 - -local initializedStateWithGuide = capabilities["stse.initializedStateWithGuide"] -local reverseCurtainDirection = capabilities["stse.reverseCurtainDirection"] -local hookLockState = capabilities["stse.hookLockState"] -local chargingState = capabilities["stse.chargingState"] -local softTouch = capabilities["stse.softTouch"] -local hookUnlockCommandName = "hookUnlock" -local hookLockCommandName = "hookLock" - -local SHADE_STATE_CLOSE = 0 -local SHADE_STATE_OPEN = 1 -local SHADE_STATE_STOP = 2 - -local function device_added(driver, device) - device:emit_event(capabilities.windowShade.supportedWindowShadeCommands({ "open", "close", "pause" }, {visibility = {displayed = false}})) - window_treatment_utils.emit_event_if_latest_state_missing(device, "main", capabilities.windowShadeLevel, capabilities.windowShadeLevel.shadeLevel.NAME, capabilities.windowShadeLevel.shadeLevel(0)) - window_treatment_utils.emit_event_if_latest_state_missing(device, "main", capabilities.windowShade, capabilities.windowShade.windowShade.NAME, capabilities.windowShade.windowShade.closed()) - device:emit_event(initializedStateWithGuide.initializedStateWithGuide.notInitialized()) - device:emit_event(hookLockState.hookLockState.unlocked()) - device:emit_event(chargingState.chargingState.stopped()) - device:emit_event(capabilities.battery.battery(100)) -end - -local function do_refresh(self, device) - device:send(cluster_base.read_manufacturer_specific_attribute(device, aqara_utils.PRIVATE_CLUSTER_ID, PRIVATE_CURTAIN_RANGE_FLAG_ATTRIBUTE_ID, aqara_utils.MFG_CODE)) - device:send(cluster_base.read_manufacturer_specific_attribute(device, aqara_utils.PRIVATE_CLUSTER_ID, PRIVATE_CURTAIN_LOCKING_STATUS_ATTRIBUTE_ID, aqara_utils.MFG_CODE)) - device:send(WindowCovering.attributes.CurrentPositionLiftPercentage:read(device)) - device:send(PowerConfiguration.attributes.BatteryPercentageRemaining:read(device)) -end - -local function do_configure(self, device) - device:configure() - device:send(Groups.server.commands.RemoveAllGroups(device)) -- required - do_refresh(self, device) -end - -local CONFIGURATIONS = { - { - cluster = PowerConfiguration.ID, - attribute = PowerConfiguration.attributes.BatteryPercentageRemaining.ID, - minimum_interval = 30, - maximum_interval = 3600, - data_type = PowerConfiguration.attributes.BatteryPercentageRemaining.base_type, - reportable_change = 1 - } -} - -local function device_init(driver, device) - for _, attribute in ipairs(CONFIGURATIONS) do - device:add_configured_attribute(attribute) - end -end - -local function device_info_changed(driver, device, event, args) - if device.preferences ~= nil then - local reverseCurtainDirectionPrefValue = device.preferences[reverseCurtainDirection.ID] - local softTouchPrefValue = device.preferences[softTouch.ID] - - -- reverse direction - if reverseCurtainDirectionPrefValue ~= nil and - reverseCurtainDirectionPrefValue ~= args.old_st_store.preferences[reverseCurtainDirection.ID] then - local raw_value = reverseCurtainDirectionPrefValue and 0x01 or 0x00 - device:send(aqara_utils.custom_write_attribute(device, WindowCovering.ID, WindowCovering.attributes.Mode.ID, - data_types.Bitmap8, raw_value, nil)) - end - - -- soft touch - if softTouchPrefValue ~= nil and - softTouchPrefValue ~= args.old_st_store.preferences[softTouch.ID] then - device:send(cluster_base.write_manufacturer_specific_attribute(device, - aqara_utils.PRIVATE_CLUSTER_ID, PRIVATE_CURTAIN_MANUAL_ATTRIBUTE_ID, aqara_utils.MFG_CODE, data_types.Boolean, (not softTouchPrefValue))) - end - end -end - -local function shade_state_report_handler(driver, device, value, zb_rx) - local state = value.value - -- update state ui - if state == SHADE_STATE_STOP then - -- read shade position to update the UI - device:send(WindowCovering.attributes.CurrentPositionLiftPercentage:read(device)) - elseif state == SHADE_STATE_OPEN then - device:emit_event(capabilities.windowShade.windowShade.opening()) - elseif state == SHADE_STATE_CLOSE then - device:emit_event(capabilities.windowShade.windowShade.closing()) - end -end - -local function curtain_range_report_handler(driver, device, value, zb_rx) - -- initializedState - if value.value == true then - device:emit_event(initializedStateWithGuide.initializedStateWithGuide.initialized()) - elseif value.value == false then - device:emit_event(initializedStateWithGuide.initializedStateWithGuide.notInitialized()) - end -end - -local function curtain_state_of_charge_report_handler(driver, device, value, zb_rx) - if value.value == 3 then - device:emit_event(chargingState.chargingState.stopped()) - elseif value.value == 4 then - device:emit_event(chargingState.chargingState.charging()) - elseif value.value == 7 then - device:emit_event(chargingState.chargingState.fullyCharged()) - end -end - -local function battery_energy_status_handler(driver, device, value, zb_rx) - device:emit_event(capabilities.battery.battery(math.floor(value.value / 2.0 + 0.5))) -end - -local function window_locking_status_handler(driver, device, value, zb_rx) - if value.value == 0 then - device:emit_event(hookLockState.hookLockState.unlocked()) - elseif value.value == 1 then - device:emit_event(hookLockState.hookLockState.locked()) - elseif value.value == 2 then - device:emit_event(hookLockState.hookLockState.locking()) - elseif value.value == 3 then - device:emit_event(hookLockState.hookLockState.unlocking()) - end -end - -local function window_shade_open_cmd(driver, device, command) - -- Cannot be controlled if not initialized - local initialized = device:get_latest_state("main", initializedStateWithGuide.ID, - initializedStateWithGuide.initializedStateWithGuide.NAME) or 0 - if initialized == initializedStateWithGuide.initializedStateWithGuide.initialized.NAME then - device:send_to_component(command.component, WindowCovering.server.commands.UpOrOpen(device)) - end -end - -local function window_shade_pause_cmd(driver, device, command) - -- Cannot be controlled if not initialized - local initialized = device:get_latest_state("main", initializedStateWithGuide.ID, - initializedStateWithGuide.initializedStateWithGuide.NAME) or 0 - if initialized == initializedStateWithGuide.initializedStateWithGuide.initialized.NAME then - device:send_to_component(command.component, WindowCovering.server.commands.Stop(device)) - end -end - -local function window_shade_close_cmd(driver, device, command) - -- Cannot be controlled if not initialized - local initialized = device:get_latest_state("main", initializedStateWithGuide.ID, - initializedStateWithGuide.initializedStateWithGuide.NAME) or 0 - if initialized == initializedStateWithGuide.initializedStateWithGuide.initialized.NAME then - device:send_to_component(command.component, WindowCovering.server.commands.DownOrClose(device)) - end -end - -local function hook_lock_cmd(driver, device, command) - device:send(cluster_base.write_manufacturer_specific_attribute(device, - aqara_utils.PRIVATE_CLUSTER_ID, PRIVATE_CURTAIN_LOCKING_SETTING_ATTRIBUTE_ID, aqara_utils.MFG_CODE, data_types.Uint8, 0x01)) -end - -local function hook_unlock_cmd(driver, device, command) - device:send(cluster_base.write_manufacturer_specific_attribute(device, - aqara_utils.PRIVATE_CLUSTER_ID, PRIVATE_CURTAIN_LOCKING_SETTING_ATTRIBUTE_ID, aqara_utils.MFG_CODE, data_types.Uint8, 0x00)) -end - -local aqara_curtain_driver_e1_handler = { - NAME = "Aqara Curtain Driver E1 Handler", - lifecycle_handlers = { - init = device_init, - added = device_added, - doConfigure = do_configure, - infoChanged = device_info_changed - }, - capability_handlers = { - [capabilities.windowShade.ID] = { - [capabilities.windowShade.commands.open.NAME] = window_shade_open_cmd, - [capabilities.windowShade.commands.pause.NAME] = window_shade_pause_cmd, - [capabilities.windowShade.commands.close.NAME] = window_shade_close_cmd, - }, - [hookLockState.ID] = { - [hookLockCommandName] = hook_lock_cmd, - [hookUnlockCommandName] = hook_unlock_cmd, - }, - [capabilities.refresh.ID] = { - [capabilities.refresh.commands.refresh.NAME] = do_refresh - } - }, - zigbee_handlers = { - attr = { - [Basic.ID] = { - [Basic.attributes.PowerSource.ID] = curtain_state_of_charge_report_handler - }, - [PowerConfiguration.ID] = { - [PowerConfiguration.attributes.BatteryPercentageRemaining.ID] = battery_energy_status_handler - }, - [aqara_utils.PRIVATE_CLUSTER_ID] = { - [PRIVATE_CURTAIN_RANGE_FLAG_ATTRIBUTE_ID] = curtain_range_report_handler, - [PRIVATE_CURTAIN_STATUS_ATTRIBUTE_ID] = shade_state_report_handler, - [PRIVATE_CURTAIN_LOCKING_STATUS_ATTRIBUTE_ID] = window_locking_status_handler - } - } - }, - can_handle = function(opts, driver, device, ...) - return device:get_model() == "lumi.curtain.agl001" - end -} - -return aqara_curtain_driver_e1_handler +-- Copyright 2024 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 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 aqara_utils = require "aqara/aqara_utils" +local window_treatment_utils = require "window_treatment_utils" + +local Groups = clusters.Groups +local Basic = clusters.Basic +local WindowCovering = clusters.WindowCovering +local PowerConfiguration = clusters.PowerConfiguration +local PRIVATE_CURTAIN_MANUAL_ATTRIBUTE_ID = 0x0401 +local PRIVATE_CURTAIN_RANGE_FLAG_ATTRIBUTE_ID = 0x0402 +local PRIVATE_CURTAIN_STATUS_ATTRIBUTE_ID = 0x0421 +local PRIVATE_CURTAIN_LOCKING_SETTING_ATTRIBUTE_ID = 0x0427 +local PRIVATE_CURTAIN_LOCKING_STATUS_ATTRIBUTE_ID = 0x0428 + +local initializedStateWithGuide = capabilities["stse.initializedStateWithGuide"] +local reverseCurtainDirection = capabilities["stse.reverseCurtainDirection"] +local hookLockState = capabilities["stse.hookLockState"] +local chargingState = capabilities["stse.chargingState"] +local softTouch = capabilities["stse.softTouch"] +local hookUnlockCommandName = "hookUnlock" +local hookLockCommandName = "hookLock" + +local SHADE_STATE_CLOSE = 0 +local SHADE_STATE_OPEN = 1 +local SHADE_STATE_STOP = 2 + +local function device_added(driver, device) + device:emit_event(capabilities.windowShade.supportedWindowShadeCommands({ "open", "close", "pause" }, {visibility = {displayed = false}})) + window_treatment_utils.emit_event_if_latest_state_missing(device, "main", capabilities.windowShadeLevel, capabilities.windowShadeLevel.shadeLevel.NAME, capabilities.windowShadeLevel.shadeLevel(0)) + window_treatment_utils.emit_event_if_latest_state_missing(device, "main", capabilities.windowShade, capabilities.windowShade.windowShade.NAME, capabilities.windowShade.windowShade.closed()) + device:emit_event(initializedStateWithGuide.initializedStateWithGuide.notInitialized()) + device:emit_event(hookLockState.hookLockState.unlocked()) + device:emit_event(chargingState.chargingState.stopped()) + device:emit_event(capabilities.battery.battery(100)) +end + +local function do_refresh(self, device) + device:send(cluster_base.read_manufacturer_specific_attribute(device, aqara_utils.PRIVATE_CLUSTER_ID, PRIVATE_CURTAIN_RANGE_FLAG_ATTRIBUTE_ID, aqara_utils.MFG_CODE)) + device:send(cluster_base.read_manufacturer_specific_attribute(device, aqara_utils.PRIVATE_CLUSTER_ID, PRIVATE_CURTAIN_LOCKING_STATUS_ATTRIBUTE_ID, aqara_utils.MFG_CODE)) + device:send(WindowCovering.attributes.CurrentPositionLiftPercentage:read(device)) + device:send(PowerConfiguration.attributes.BatteryPercentageRemaining:read(device)) +end + +local function do_configure(self, device) + device:configure() + device:send(Groups.server.commands.RemoveAllGroups(device)) -- required + do_refresh(self, device) +end + +local CONFIGURATIONS = { + { + cluster = PowerConfiguration.ID, + attribute = PowerConfiguration.attributes.BatteryPercentageRemaining.ID, + minimum_interval = 30, + maximum_interval = 3600, + data_type = PowerConfiguration.attributes.BatteryPercentageRemaining.base_type, + reportable_change = 1 + } +} + +local function device_init(driver, device) + for _, attribute in ipairs(CONFIGURATIONS) do + device:add_configured_attribute(attribute) + end +end + +local function device_info_changed(driver, device, event, args) + if device.preferences ~= nil then + local reverseCurtainDirectionPrefValue = device.preferences[reverseCurtainDirection.ID] + local softTouchPrefValue = device.preferences[softTouch.ID] + + -- reverse direction + if reverseCurtainDirectionPrefValue ~= nil and + reverseCurtainDirectionPrefValue ~= args.old_st_store.preferences[reverseCurtainDirection.ID] then + local raw_value = reverseCurtainDirectionPrefValue and 0x01 or 0x00 + device:send(aqara_utils.custom_write_attribute(device, WindowCovering.ID, WindowCovering.attributes.Mode.ID, + data_types.Bitmap8, raw_value, nil)) + end + + -- soft touch + if softTouchPrefValue ~= nil and + softTouchPrefValue ~= args.old_st_store.preferences[softTouch.ID] then + device:send(cluster_base.write_manufacturer_specific_attribute(device, + aqara_utils.PRIVATE_CLUSTER_ID, PRIVATE_CURTAIN_MANUAL_ATTRIBUTE_ID, aqara_utils.MFG_CODE, data_types.Boolean, (not softTouchPrefValue))) + end + end +end + +local function shade_state_report_handler(driver, device, value, zb_rx) + local state = value.value + -- update state ui + if state == SHADE_STATE_STOP then + -- read shade position to update the UI + device:send(WindowCovering.attributes.CurrentPositionLiftPercentage:read(device)) + elseif state == SHADE_STATE_OPEN then + device:emit_event(capabilities.windowShade.windowShade.opening()) + elseif state == SHADE_STATE_CLOSE then + device:emit_event(capabilities.windowShade.windowShade.closing()) + end +end + +local function curtain_range_report_handler(driver, device, value, zb_rx) + -- initializedState + if value.value == true then + device:emit_event(initializedStateWithGuide.initializedStateWithGuide.initialized()) + elseif value.value == false then + device:emit_event(initializedStateWithGuide.initializedStateWithGuide.notInitialized()) + end +end + +local function curtain_state_of_charge_report_handler(driver, device, value, zb_rx) + if value.value == 3 then + device:emit_event(chargingState.chargingState.stopped()) + elseif value.value == 4 then + device:emit_event(chargingState.chargingState.charging()) + elseif value.value == 7 then + device:emit_event(chargingState.chargingState.fullyCharged()) + end +end + +local function battery_energy_status_handler(driver, device, value, zb_rx) + device:emit_event(capabilities.battery.battery(math.floor(value.value / 2.0 + 0.5))) +end + +local function window_locking_status_handler(driver, device, value, zb_rx) + if value.value == 0 then + device:emit_event(hookLockState.hookLockState.unlocked()) + elseif value.value == 1 then + device:emit_event(hookLockState.hookLockState.locked()) + elseif value.value == 2 then + device:emit_event(hookLockState.hookLockState.locking()) + elseif value.value == 3 then + device:emit_event(hookLockState.hookLockState.unlocking()) + end +end + +local function window_shade_open_cmd(driver, device, command) + -- Cannot be controlled if not initialized + local initialized = device:get_latest_state("main", initializedStateWithGuide.ID, + initializedStateWithGuide.initializedStateWithGuide.NAME) or 0 + if initialized == initializedStateWithGuide.initializedStateWithGuide.initialized.NAME then + device:send_to_component(command.component, WindowCovering.server.commands.UpOrOpen(device)) + end +end + +local function window_shade_pause_cmd(driver, device, command) + -- Cannot be controlled if not initialized + local initialized = device:get_latest_state("main", initializedStateWithGuide.ID, + initializedStateWithGuide.initializedStateWithGuide.NAME) or 0 + if initialized == initializedStateWithGuide.initializedStateWithGuide.initialized.NAME then + device:send_to_component(command.component, WindowCovering.server.commands.Stop(device)) + end +end + +local function window_shade_close_cmd(driver, device, command) + -- Cannot be controlled if not initialized + local initialized = device:get_latest_state("main", initializedStateWithGuide.ID, + initializedStateWithGuide.initializedStateWithGuide.NAME) or 0 + if initialized == initializedStateWithGuide.initializedStateWithGuide.initialized.NAME then + device:send_to_component(command.component, WindowCovering.server.commands.DownOrClose(device)) + end +end + +local function hook_lock_cmd(driver, device, command) + device:send(cluster_base.write_manufacturer_specific_attribute(device, + aqara_utils.PRIVATE_CLUSTER_ID, PRIVATE_CURTAIN_LOCKING_SETTING_ATTRIBUTE_ID, aqara_utils.MFG_CODE, data_types.Uint8, 0x01)) +end + +local function hook_unlock_cmd(driver, device, command) + device:send(cluster_base.write_manufacturer_specific_attribute(device, + aqara_utils.PRIVATE_CLUSTER_ID, PRIVATE_CURTAIN_LOCKING_SETTING_ATTRIBUTE_ID, aqara_utils.MFG_CODE, data_types.Uint8, 0x00)) +end + +local aqara_curtain_driver_e1_handler = { + NAME = "Aqara Curtain Driver E1 Handler", + lifecycle_handlers = { + init = device_init, + added = device_added, + doConfigure = do_configure, + infoChanged = device_info_changed + }, + capability_handlers = { + [capabilities.windowShade.ID] = { + [capabilities.windowShade.commands.open.NAME] = window_shade_open_cmd, + [capabilities.windowShade.commands.pause.NAME] = window_shade_pause_cmd, + [capabilities.windowShade.commands.close.NAME] = window_shade_close_cmd, + }, + [hookLockState.ID] = { + [hookLockCommandName] = hook_lock_cmd, + [hookUnlockCommandName] = hook_unlock_cmd, + }, + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = do_refresh + } + }, + zigbee_handlers = { + attr = { + [Basic.ID] = { + [Basic.attributes.PowerSource.ID] = curtain_state_of_charge_report_handler + }, + [PowerConfiguration.ID] = { + [PowerConfiguration.attributes.BatteryPercentageRemaining.ID] = battery_energy_status_handler + }, + [aqara_utils.PRIVATE_CLUSTER_ID] = { + [PRIVATE_CURTAIN_RANGE_FLAG_ATTRIBUTE_ID] = curtain_range_report_handler, + [PRIVATE_CURTAIN_STATUS_ATTRIBUTE_ID] = shade_state_report_handler, + [PRIVATE_CURTAIN_LOCKING_STATUS_ATTRIBUTE_ID] = window_locking_status_handler + } + } + }, + can_handle = function(opts, driver, device, ...) + return device:get_model() == "lumi.curtain.agl001" + end +} + +return aqara_curtain_driver_e1_handler diff --git a/drivers/SmartThings/zigbee-window-treatment/src/aqara/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/aqara/init.lua index ae9513734c..f6274e3113 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/aqara/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/aqara/init.lua @@ -1,228 +1,228 @@ -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 aqara_utils = require "aqara/aqara_utils" -local window_treatment_utils = require "window_treatment_utils" - -local Basic = clusters.Basic -local WindowCovering = clusters.WindowCovering -local AnalogOutput = clusters.AnalogOutput -local Groups = clusters.Groups - -local deviceInitialization = capabilities["stse.deviceInitialization"] -local reverseCurtainDirection = capabilities["stse.reverseCurtainDirection"] -local softTouch = capabilities["stse.softTouch"] -local setInitializedStateCommandName = "setInitializedState" - -local INIT_STATE = "initState" -local INIT_STATE_INIT = "init" -local INIT_STATE_OPEN = "open" -local INIT_STATE_CLOSE = "close" -local INIT_STATE_REVERSE = "reverse" - -local PREF_INITIALIZE = "\x00\x01\x00\x00\x00\x00\x00" -local PREF_SOFT_TOUCH_OFF = "\x00\x08\x00\x00\x00\x01\x00" -local PREF_SOFT_TOUCH_ON = "\x00\x08\x00\x00\x00\x00\x00" - -local APPLICATION_VERSION = "application_version" - -local FINGERPRINTS = { - { mfr = "LUMI", model = "lumi.curtain" }, - { mfr = "LUMI", model = "lumi.curtain.v1" }, - { mfr = "LUMI", model = "lumi.curtain.aq2" }, - { mfr = "LUMI", model = "lumi.curtain.agl001" } -} - -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 window_shade_level_cmd(driver, device, command) - aqara_utils.shade_level_cmd(driver, device, command) -end - -local function set_initialized_state_handler(driver, device, command) - -- update ui - device:emit_event(deviceInitialization.initializedState.initializing()) - - -- initialize - device:set_field(INIT_STATE, INIT_STATE_INIT) - device:send(cluster_base.write_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, - aqara_utils.MFG_CODE, data_types.CharString, PREF_INITIALIZE)) - - -- open/close command - device.thread:call_with_delay(3, function(d) - local lastLevel = device:get_latest_state("main", capabilities.windowShadeLevel.ID, - capabilities.windowShadeLevel.shadeLevel.NAME) or 0 - if lastLevel > 0 then - device:set_field(INIT_STATE, INIT_STATE_CLOSE) - device:send_to_component(command.component, WindowCovering.server.commands.GoToLiftPercentage(device, 0)) - else - device:set_field(INIT_STATE, INIT_STATE_OPEN) - device:send_to_component(command.component, WindowCovering.server.commands.GoToLiftPercentage(device, 100)) - end - end) -end - -local function shade_level_report_legacy_handler(driver, device, value, zb_rx) - -- for version 34 - aqara_utils.emit_shade_level_event(device, value) - aqara_utils.emit_shade_event(device, value) -end - -local function shade_level_report_handler(driver, device, value, zb_rx) - aqara_utils.emit_shade_level_event(device, value) - aqara_utils.emit_shade_event(device, value) -end - -local function shade_state_report_handler(driver, device, value, zb_rx) - aqara_utils.emit_shade_event_by_state(device, value) - - -- initializedState - local state = value.value - if state == aqara_utils.SHADE_STATE_STOP or state == 0x04 then - local init_state_value = device:get_field(INIT_STATE) or "" - if init_state_value == INIT_STATE_OPEN then - device:set_field(INIT_STATE, INIT_STATE_REVERSE) - device.thread:call_with_delay(2, function(d) - device:send_to_component("main", WindowCovering.server.commands.GoToLiftPercentage(device, 0)) - end) - elseif init_state_value == INIT_STATE_CLOSE then - device:set_field(INIT_STATE, INIT_STATE_REVERSE) - device.thread:call_with_delay(2, function(d) - device:send_to_component("main", WindowCovering.server.commands.GoToLiftPercentage(device, 100)) - end) - elseif init_state_value == INIT_STATE_REVERSE then - device:set_field(INIT_STATE, "") - device.thread:call_with_delay(2, function(d) - device:send(cluster_base.read_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, - aqara_utils.MFG_CODE)) - end) - end - end -end - -local function pref_report_handler(driver, device, value, zb_rx) - -- initializedState - local initialized = string.byte(value.value, 3) & 0xFF - - -- Do not update if in progress. - local init_state_value = device:get_field(INIT_STATE) or "" - if init_state_value == "" then - device:emit_event(initialized == 1 and deviceInitialization.initializedState.initialized() or - deviceInitialization.initializedState.notInitialized()) - end -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 do_refresh(self, device) - device:send(AnalogOutput.attributes.PresentValue:read(device)) - device:send(cluster_base.read_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, - aqara_utils.MFG_CODE)) -end - -local function device_info_changed(driver, device, event, args) - if device.preferences ~= nil then - local reverseCurtainDirectionPrefValue = device.preferences[reverseCurtainDirection.ID] - local softTouchPrefValue = device.preferences[softTouch.ID] - - -- reverse direction - if reverseCurtainDirectionPrefValue ~= nil and - reverseCurtainDirectionPrefValue ~= args.old_st_store.preferences[reverseCurtainDirection.ID] then - local raw_value = reverseCurtainDirectionPrefValue and aqara_utils.PREF_REVERSE_ON or aqara_utils.PREF_REVERSE_OFF - device:send(cluster_base.write_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, - aqara_utils.MFG_CODE, data_types.CharString, raw_value)) - - -- read updated value - device.thread:call_with_delay(2, function(d) - device:send(cluster_base.read_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, - aqara_utils.MFG_CODE)) - end) - end - - -- soft touch - if softTouchPrefValue ~= nil and - softTouchPrefValue ~= args.old_st_store.preferences[softTouch.ID] then - local raw_value = softTouchPrefValue and PREF_SOFT_TOUCH_ON or PREF_SOFT_TOUCH_OFF - device:send(cluster_base.write_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, - aqara_utils.MFG_CODE, data_types.CharString, raw_value)) - end - end -end - -local function do_configure(self, device) - device:configure() - device:send(Basic.attributes.ApplicationVersion:read(device)) - device:send(Groups.server.commands.RemoveAllGroups(device)) - do_refresh(self, device) -end - -local function device_added(driver, device) - device:emit_event(capabilities.windowShade.supportedWindowShadeCommands({ "open", "close", "pause" }, {visibility = {displayed = false}})) - device:emit_event(deviceInitialization.supportedInitializedState({ "notInitialized", "initializing", "initialized" }, {visibility = {displayed = false}})) - window_treatment_utils.emit_event_if_latest_state_missing(device, "main", capabilities.windowShadeLevel, capabilities.windowShadeLevel.shadeLevel.NAME, capabilities.windowShadeLevel.shadeLevel(0)) - window_treatment_utils.emit_event_if_latest_state_missing(device, "main", capabilities.windowShade, capabilities.windowShade.windowShade.NAME, capabilities.windowShade.windowShade.closed()) - device:emit_event(deviceInitialization.initializedState.notInitialized()) - - device:send(cluster_base.write_manufacturer_specific_attribute(device, aqara_utils.PRIVATE_CLUSTER_ID, - aqara_utils.PRIVATE_ATTRIBUTE_ID, aqara_utils.MFG_CODE, data_types.Uint8, 1)) - - -- Initial default settings - device:send(cluster_base.write_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, - aqara_utils.MFG_CODE, data_types.CharString, aqara_utils.PREF_REVERSE_OFF)) - device:send(cluster_base.write_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, - aqara_utils.MFG_CODE, data_types.CharString, PREF_SOFT_TOUCH_ON)) -end - -local aqara_window_treatment_handler = { - NAME = "Aqara Window Treatment Handler", - lifecycle_handlers = { - added = device_added, - doConfigure = do_configure, - infoChanged = device_info_changed - }, - capability_handlers = { - [capabilities.windowShadeLevel.ID] = { - [capabilities.windowShadeLevel.commands.setShadeLevel.NAME] = window_shade_level_cmd - }, - [deviceInitialization.ID] = { - [setInitializedStateCommandName] = set_initialized_state_handler - }, - [capabilities.refresh.ID] = { - [capabilities.refresh.commands.refresh.NAME] = do_refresh - } - }, - zigbee_handlers = { - attr = { - [WindowCovering.ID] = { - [WindowCovering.attributes.CurrentPositionLiftPercentage.ID] = shade_level_report_legacy_handler - }, - [AnalogOutput.ID] = { - [AnalogOutput.attributes.PresentValue.ID] = shade_level_report_handler - }, - [Basic.ID] = { - [aqara_utils.SHADE_STATE_ATTRIBUTE_ID] = shade_state_report_handler, - [aqara_utils.PREF_ATTRIBUTE_ID] = pref_report_handler, - [Basic.attributes.ApplicationVersion.ID] = application_version_handler - } - } - }, - sub_drivers = { - require("aqara.roller-shade"), - require("aqara.curtain-driver-e1"), - require("aqara.version") - }, - can_handle = is_aqara_products -} - -return aqara_window_treatment_handler +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 aqara_utils = require "aqara/aqara_utils" +local window_treatment_utils = require "window_treatment_utils" + +local Basic = clusters.Basic +local WindowCovering = clusters.WindowCovering +local AnalogOutput = clusters.AnalogOutput +local Groups = clusters.Groups + +local deviceInitialization = capabilities["stse.deviceInitialization"] +local reverseCurtainDirection = capabilities["stse.reverseCurtainDirection"] +local softTouch = capabilities["stse.softTouch"] +local setInitializedStateCommandName = "setInitializedState" + +local INIT_STATE = "initState" +local INIT_STATE_INIT = "init" +local INIT_STATE_OPEN = "open" +local INIT_STATE_CLOSE = "close" +local INIT_STATE_REVERSE = "reverse" + +local PREF_INITIALIZE = "\x00\x01\x00\x00\x00\x00\x00" +local PREF_SOFT_TOUCH_OFF = "\x00\x08\x00\x00\x00\x01\x00" +local PREF_SOFT_TOUCH_ON = "\x00\x08\x00\x00\x00\x00\x00" + +local APPLICATION_VERSION = "application_version" + +local FINGERPRINTS = { + { mfr = "LUMI", model = "lumi.curtain" }, + { mfr = "LUMI", model = "lumi.curtain.v1" }, + { mfr = "LUMI", model = "lumi.curtain.aq2" }, + { mfr = "LUMI", model = "lumi.curtain.agl001" } +} + +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 window_shade_level_cmd(driver, device, command) + aqara_utils.shade_level_cmd(driver, device, command) +end + +local function set_initialized_state_handler(driver, device, command) + -- update ui + device:emit_event(deviceInitialization.initializedState.initializing()) + + -- initialize + device:set_field(INIT_STATE, INIT_STATE_INIT) + device:send(cluster_base.write_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, + aqara_utils.MFG_CODE, data_types.CharString, PREF_INITIALIZE)) + + -- open/close command + device.thread:call_with_delay(3, function(d) + local lastLevel = device:get_latest_state("main", capabilities.windowShadeLevel.ID, + capabilities.windowShadeLevel.shadeLevel.NAME) or 0 + if lastLevel > 0 then + device:set_field(INIT_STATE, INIT_STATE_CLOSE) + device:send_to_component(command.component, WindowCovering.server.commands.GoToLiftPercentage(device, 0)) + else + device:set_field(INIT_STATE, INIT_STATE_OPEN) + device:send_to_component(command.component, WindowCovering.server.commands.GoToLiftPercentage(device, 100)) + end + end) +end + +local function shade_level_report_legacy_handler(driver, device, value, zb_rx) + -- for version 34 + aqara_utils.emit_shade_level_event(device, value) + aqara_utils.emit_shade_event(device, value) +end + +local function shade_level_report_handler(driver, device, value, zb_rx) + aqara_utils.emit_shade_level_event(device, value) + aqara_utils.emit_shade_event(device, value) +end + +local function shade_state_report_handler(driver, device, value, zb_rx) + aqara_utils.emit_shade_event_by_state(device, value) + + -- initializedState + local state = value.value + if state == aqara_utils.SHADE_STATE_STOP or state == 0x04 then + local init_state_value = device:get_field(INIT_STATE) or "" + if init_state_value == INIT_STATE_OPEN then + device:set_field(INIT_STATE, INIT_STATE_REVERSE) + device.thread:call_with_delay(2, function(d) + device:send_to_component("main", WindowCovering.server.commands.GoToLiftPercentage(device, 0)) + end) + elseif init_state_value == INIT_STATE_CLOSE then + device:set_field(INIT_STATE, INIT_STATE_REVERSE) + device.thread:call_with_delay(2, function(d) + device:send_to_component("main", WindowCovering.server.commands.GoToLiftPercentage(device, 100)) + end) + elseif init_state_value == INIT_STATE_REVERSE then + device:set_field(INIT_STATE, "") + device.thread:call_with_delay(2, function(d) + device:send(cluster_base.read_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, + aqara_utils.MFG_CODE)) + end) + end + end +end + +local function pref_report_handler(driver, device, value, zb_rx) + -- initializedState + local initialized = string.byte(value.value, 3) & 0xFF + + -- Do not update if in progress. + local init_state_value = device:get_field(INIT_STATE) or "" + if init_state_value == "" then + device:emit_event(initialized == 1 and deviceInitialization.initializedState.initialized() or + deviceInitialization.initializedState.notInitialized()) + end +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 do_refresh(self, device) + device:send(AnalogOutput.attributes.PresentValue:read(device)) + device:send(cluster_base.read_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, + aqara_utils.MFG_CODE)) +end + +local function device_info_changed(driver, device, event, args) + if device.preferences ~= nil then + local reverseCurtainDirectionPrefValue = device.preferences[reverseCurtainDirection.ID] + local softTouchPrefValue = device.preferences[softTouch.ID] + + -- reverse direction + if reverseCurtainDirectionPrefValue ~= nil and + reverseCurtainDirectionPrefValue ~= args.old_st_store.preferences[reverseCurtainDirection.ID] then + local raw_value = reverseCurtainDirectionPrefValue and aqara_utils.PREF_REVERSE_ON or aqara_utils.PREF_REVERSE_OFF + device:send(cluster_base.write_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, + aqara_utils.MFG_CODE, data_types.CharString, raw_value)) + + -- read updated value + device.thread:call_with_delay(2, function(d) + device:send(cluster_base.read_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, + aqara_utils.MFG_CODE)) + end) + end + + -- soft touch + if softTouchPrefValue ~= nil and + softTouchPrefValue ~= args.old_st_store.preferences[softTouch.ID] then + local raw_value = softTouchPrefValue and PREF_SOFT_TOUCH_ON or PREF_SOFT_TOUCH_OFF + device:send(cluster_base.write_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, + aqara_utils.MFG_CODE, data_types.CharString, raw_value)) + end + end +end + +local function do_configure(self, device) + device:configure() + device:send(Basic.attributes.ApplicationVersion:read(device)) + device:send(Groups.server.commands.RemoveAllGroups(device)) + do_refresh(self, device) +end + +local function device_added(driver, device) + device:emit_event(capabilities.windowShade.supportedWindowShadeCommands({ "open", "close", "pause" }, {visibility = {displayed = false}})) + device:emit_event(deviceInitialization.supportedInitializedState({ "notInitialized", "initializing", "initialized" }, {visibility = {displayed = false}})) + window_treatment_utils.emit_event_if_latest_state_missing(device, "main", capabilities.windowShadeLevel, capabilities.windowShadeLevel.shadeLevel.NAME, capabilities.windowShadeLevel.shadeLevel(0)) + window_treatment_utils.emit_event_if_latest_state_missing(device, "main", capabilities.windowShade, capabilities.windowShade.windowShade.NAME, capabilities.windowShade.windowShade.closed()) + device:emit_event(deviceInitialization.initializedState.notInitialized()) + + device:send(cluster_base.write_manufacturer_specific_attribute(device, aqara_utils.PRIVATE_CLUSTER_ID, + aqara_utils.PRIVATE_ATTRIBUTE_ID, aqara_utils.MFG_CODE, data_types.Uint8, 1)) + + -- Initial default settings + device:send(cluster_base.write_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, + aqara_utils.MFG_CODE, data_types.CharString, aqara_utils.PREF_REVERSE_OFF)) + device:send(cluster_base.write_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, + aqara_utils.MFG_CODE, data_types.CharString, PREF_SOFT_TOUCH_ON)) +end + +local aqara_window_treatment_handler = { + NAME = "Aqara Window Treatment Handler", + lifecycle_handlers = { + added = device_added, + doConfigure = do_configure, + infoChanged = device_info_changed + }, + capability_handlers = { + [capabilities.windowShadeLevel.ID] = { + [capabilities.windowShadeLevel.commands.setShadeLevel.NAME] = window_shade_level_cmd + }, + [deviceInitialization.ID] = { + [setInitializedStateCommandName] = set_initialized_state_handler + }, + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = do_refresh + } + }, + zigbee_handlers = { + attr = { + [WindowCovering.ID] = { + [WindowCovering.attributes.CurrentPositionLiftPercentage.ID] = shade_level_report_legacy_handler + }, + [AnalogOutput.ID] = { + [AnalogOutput.attributes.PresentValue.ID] = shade_level_report_handler + }, + [Basic.ID] = { + [aqara_utils.SHADE_STATE_ATTRIBUTE_ID] = shade_state_report_handler, + [aqara_utils.PREF_ATTRIBUTE_ID] = pref_report_handler, + [Basic.attributes.ApplicationVersion.ID] = application_version_handler + } + } + }, + sub_drivers = { + require("aqara.roller-shade"), + require("aqara.curtain-driver-e1"), + require("aqara.version") + }, + can_handle = is_aqara_products +} + +return aqara_window_treatment_handler diff --git a/drivers/SmartThings/zigbee-window-treatment/src/aqara/roller-shade/init.lua b/drivers/SmartThings/zigbee-window-treatment/src/aqara/roller-shade/init.lua index c7b6b9a50a..6c05e83de7 100644 --- a/drivers/SmartThings/zigbee-window-treatment/src/aqara/roller-shade/init.lua +++ b/drivers/SmartThings/zigbee-window-treatment/src/aqara/roller-shade/init.lua @@ -1,141 +1,141 @@ -local capabilities = require "st.capabilities" -local clusters = require "st.zigbee.zcl.clusters" -local cluster_base = require "st.zigbee.cluster_base" -local FrameCtrl = require "st.zigbee.zcl.frame_ctrl" -local data_types = require "st.zigbee.data_types" -local aqara_utils = require "aqara/aqara_utils" -local window_treatment_utils = require "window_treatment_utils" - -local Basic = clusters.Basic -local WindowCovering = clusters.WindowCovering - -local initializedStateWithGuide = capabilities["stse.initializedStateWithGuide"] -local reverseRollerShadeDir = capabilities["stse.reverseRollerShadeDir"] -local shadeRotateState = capabilities["stse.shadeRotateState"] -local setRotateStateCommandName = "setRotateState" - -local MULTISTATE_CLUSTER_ID = 0x0013 -local MULTISTATE_ATTRIBUTE_ID = 0x0055 -local ROTATE_UP_VALUE = 0x0004 -local ROTATE_DOWN_VALUE = 0x0005 - - -local function window_shade_level_cmd(driver, device, command) - -- Cannot be controlled if not initialized - local initialized = device:get_latest_state("main", initializedStateWithGuide.ID, - initializedStateWithGuide.initializedStateWithGuide.NAME) or 0 - if initialized == initializedStateWithGuide.initializedStateWithGuide.initialized.NAME then - aqara_utils.shade_level_cmd(driver, device, command) - end -end - -local function window_shade_open_cmd(driver, device, command) - -- Cannot be controlled if not initialized - local initialized = device:get_latest_state("main", initializedStateWithGuide.ID, - initializedStateWithGuide.initializedStateWithGuide.NAME) or 0 - if initialized == initializedStateWithGuide.initializedStateWithGuide.initialized.NAME then - device:send_to_component(command.component, WindowCovering.server.commands.GoToLiftPercentage(device, 100)) - end -end - -local function window_shade_close_cmd(driver, device, command) - -- Cannot be controlled if not initialized - local initialized = device:get_latest_state("main", initializedStateWithGuide.ID, - initializedStateWithGuide.initializedStateWithGuide.NAME) or 0 - if initialized == initializedStateWithGuide.initializedStateWithGuide.initialized.NAME then - device:send_to_component(command.component, WindowCovering.server.commands.GoToLiftPercentage(device, 0)) - end -end - -local function set_rotate_command_handler(driver, device, command) - device:emit_event(shadeRotateState.rotateState.idle({state_change = true, visibility = { displayed = false }})) -- update UI - - -- Cannot be controlled if not initialized - local initialized = device:get_latest_state("main", initializedStateWithGuide.ID, - initializedStateWithGuide.initializedStateWithGuide.NAME) or 0 - if initialized == initializedStateWithGuide.initializedStateWithGuide.initialized.NAME then - local state = command.args.state - if state == "rotateUp" then - local message = cluster_base.write_manufacturer_specific_attribute(device, MULTISTATE_CLUSTER_ID, - MULTISTATE_ATTRIBUTE_ID, aqara_utils.MFG_CODE, data_types.Uint16, ROTATE_UP_VALUE) - message.body.zcl_header.frame_ctrl = FrameCtrl(0x10) - device:send(message) - elseif state == "rotateDown" then - local message = cluster_base.write_manufacturer_specific_attribute(device, MULTISTATE_CLUSTER_ID, - MULTISTATE_ATTRIBUTE_ID, aqara_utils.MFG_CODE, data_types.Uint16, ROTATE_DOWN_VALUE) - message.body.zcl_header.frame_ctrl = FrameCtrl(0x10) - device:send(message) - end - end -end - -local function shade_state_report_handler(driver, device, value, zb_rx) - aqara_utils.emit_shade_event_by_state(device, value) -end - -local function pref_report_handler(driver, device, value, zb_rx) - -- initializedState - local initialized = string.byte(value.value, 3) & 0xFF - device:emit_event(initialized == 1 and initializedStateWithGuide.initializedStateWithGuide.initialized() or - initializedStateWithGuide.initializedStateWithGuide.notInitialized()) -end - -local function device_info_changed(driver, device, event, args) - if device.preferences ~= nil then - local reverseRollerShadeDirPrefValue = device.preferences[reverseRollerShadeDir.ID] - if reverseRollerShadeDirPrefValue ~= nil and - reverseRollerShadeDirPrefValue ~= args.old_st_store.preferences[reverseRollerShadeDir.ID] then - local raw_value = reverseRollerShadeDirPrefValue and aqara_utils.PREF_REVERSE_ON or aqara_utils.PREF_REVERSE_OFF - device:send(cluster_base.write_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, - aqara_utils.MFG_CODE, data_types.CharString, raw_value)) - end - end -end - -local function device_added(driver, device) - device:emit_event(capabilities.windowShade.supportedWindowShadeCommands({ "open", "close", "pause" }, {visibility = {displayed = false}})) - window_treatment_utils.emit_event_if_latest_state_missing(device, "main", capabilities.windowShadeLevel, capabilities.windowShadeLevel.shadeLevel.NAME, capabilities.windowShadeLevel.shadeLevel(0)) - window_treatment_utils.emit_event_if_latest_state_missing(device, "main", capabilities.windowShade, capabilities.windowShade.windowShade.NAME, capabilities.windowShade.windowShade.closed()) - device:emit_event(initializedStateWithGuide.initializedStateWithGuide.notInitialized()) - device:emit_event(shadeRotateState.rotateState.idle({ visibility = { displayed = false }})) - - device:send(cluster_base.write_manufacturer_specific_attribute(device, aqara_utils.PRIVATE_CLUSTER_ID, - aqara_utils.PRIVATE_ATTRIBUTE_ID, aqara_utils.MFG_CODE, data_types.Uint8, 1)) - - -- Initial default settings - device:send(cluster_base.write_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, - aqara_utils.MFG_CODE, data_types.CharString, aqara_utils.PREF_REVERSE_OFF)) -end - -local aqara_roller_shade_handler = { - NAME = "Aqara Roller Shade Handler", - lifecycle_handlers = { - added = device_added, - infoChanged = device_info_changed - }, - capability_handlers = { - [capabilities.windowShadeLevel.ID] = { - [capabilities.windowShadeLevel.commands.setShadeLevel.NAME] = window_shade_level_cmd - }, - [capabilities.windowShade.ID] = { - [capabilities.windowShade.commands.open.NAME] = window_shade_open_cmd, - [capabilities.windowShade.commands.close.NAME] = window_shade_close_cmd, - }, - [shadeRotateState.ID] = { - [setRotateStateCommandName] = set_rotate_command_handler - } - }, - zigbee_handlers = { - attr = { - [Basic.ID] = { - [aqara_utils.SHADE_STATE_ATTRIBUTE_ID] = shade_state_report_handler, - [aqara_utils.PREF_ATTRIBUTE_ID] = pref_report_handler - } - } - }, - can_handle = function(opts, driver, device, ...) - return device:get_model() == "lumi.curtain.aq2" - end -} - -return aqara_roller_shade_handler +local capabilities = require "st.capabilities" +local clusters = require "st.zigbee.zcl.clusters" +local cluster_base = require "st.zigbee.cluster_base" +local FrameCtrl = require "st.zigbee.zcl.frame_ctrl" +local data_types = require "st.zigbee.data_types" +local aqara_utils = require "aqara/aqara_utils" +local window_treatment_utils = require "window_treatment_utils" + +local Basic = clusters.Basic +local WindowCovering = clusters.WindowCovering + +local initializedStateWithGuide = capabilities["stse.initializedStateWithGuide"] +local reverseRollerShadeDir = capabilities["stse.reverseRollerShadeDir"] +local shadeRotateState = capabilities["stse.shadeRotateState"] +local setRotateStateCommandName = "setRotateState" + +local MULTISTATE_CLUSTER_ID = 0x0013 +local MULTISTATE_ATTRIBUTE_ID = 0x0055 +local ROTATE_UP_VALUE = 0x0004 +local ROTATE_DOWN_VALUE = 0x0005 + + +local function window_shade_level_cmd(driver, device, command) + -- Cannot be controlled if not initialized + local initialized = device:get_latest_state("main", initializedStateWithGuide.ID, + initializedStateWithGuide.initializedStateWithGuide.NAME) or 0 + if initialized == initializedStateWithGuide.initializedStateWithGuide.initialized.NAME then + aqara_utils.shade_level_cmd(driver, device, command) + end +end + +local function window_shade_open_cmd(driver, device, command) + -- Cannot be controlled if not initialized + local initialized = device:get_latest_state("main", initializedStateWithGuide.ID, + initializedStateWithGuide.initializedStateWithGuide.NAME) or 0 + if initialized == initializedStateWithGuide.initializedStateWithGuide.initialized.NAME then + device:send_to_component(command.component, WindowCovering.server.commands.GoToLiftPercentage(device, 100)) + end +end + +local function window_shade_close_cmd(driver, device, command) + -- Cannot be controlled if not initialized + local initialized = device:get_latest_state("main", initializedStateWithGuide.ID, + initializedStateWithGuide.initializedStateWithGuide.NAME) or 0 + if initialized == initializedStateWithGuide.initializedStateWithGuide.initialized.NAME then + device:send_to_component(command.component, WindowCovering.server.commands.GoToLiftPercentage(device, 0)) + end +end + +local function set_rotate_command_handler(driver, device, command) + device:emit_event(shadeRotateState.rotateState.idle({state_change = true, visibility = { displayed = false }})) -- update UI + + -- Cannot be controlled if not initialized + local initialized = device:get_latest_state("main", initializedStateWithGuide.ID, + initializedStateWithGuide.initializedStateWithGuide.NAME) or 0 + if initialized == initializedStateWithGuide.initializedStateWithGuide.initialized.NAME then + local state = command.args.state + if state == "rotateUp" then + local message = cluster_base.write_manufacturer_specific_attribute(device, MULTISTATE_CLUSTER_ID, + MULTISTATE_ATTRIBUTE_ID, aqara_utils.MFG_CODE, data_types.Uint16, ROTATE_UP_VALUE) + message.body.zcl_header.frame_ctrl = FrameCtrl(0x10) + device:send(message) + elseif state == "rotateDown" then + local message = cluster_base.write_manufacturer_specific_attribute(device, MULTISTATE_CLUSTER_ID, + MULTISTATE_ATTRIBUTE_ID, aqara_utils.MFG_CODE, data_types.Uint16, ROTATE_DOWN_VALUE) + message.body.zcl_header.frame_ctrl = FrameCtrl(0x10) + device:send(message) + end + end +end + +local function shade_state_report_handler(driver, device, value, zb_rx) + aqara_utils.emit_shade_event_by_state(device, value) +end + +local function pref_report_handler(driver, device, value, zb_rx) + -- initializedState + local initialized = string.byte(value.value, 3) & 0xFF + device:emit_event(initialized == 1 and initializedStateWithGuide.initializedStateWithGuide.initialized() or + initializedStateWithGuide.initializedStateWithGuide.notInitialized()) +end + +local function device_info_changed(driver, device, event, args) + if device.preferences ~= nil then + local reverseRollerShadeDirPrefValue = device.preferences[reverseRollerShadeDir.ID] + if reverseRollerShadeDirPrefValue ~= nil and + reverseRollerShadeDirPrefValue ~= args.old_st_store.preferences[reverseRollerShadeDir.ID] then + local raw_value = reverseRollerShadeDirPrefValue and aqara_utils.PREF_REVERSE_ON or aqara_utils.PREF_REVERSE_OFF + device:send(cluster_base.write_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, + aqara_utils.MFG_CODE, data_types.CharString, raw_value)) + end + end +end + +local function device_added(driver, device) + device:emit_event(capabilities.windowShade.supportedWindowShadeCommands({ "open", "close", "pause" }, {visibility = {displayed = false}})) + window_treatment_utils.emit_event_if_latest_state_missing(device, "main", capabilities.windowShadeLevel, capabilities.windowShadeLevel.shadeLevel.NAME, capabilities.windowShadeLevel.shadeLevel(0)) + window_treatment_utils.emit_event_if_latest_state_missing(device, "main", capabilities.windowShade, capabilities.windowShade.windowShade.NAME, capabilities.windowShade.windowShade.closed()) + device:emit_event(initializedStateWithGuide.initializedStateWithGuide.notInitialized()) + device:emit_event(shadeRotateState.rotateState.idle({ visibility = { displayed = false }})) + + device:send(cluster_base.write_manufacturer_specific_attribute(device, aqara_utils.PRIVATE_CLUSTER_ID, + aqara_utils.PRIVATE_ATTRIBUTE_ID, aqara_utils.MFG_CODE, data_types.Uint8, 1)) + + -- Initial default settings + device:send(cluster_base.write_manufacturer_specific_attribute(device, Basic.ID, aqara_utils.PREF_ATTRIBUTE_ID, + aqara_utils.MFG_CODE, data_types.CharString, aqara_utils.PREF_REVERSE_OFF)) +end + +local aqara_roller_shade_handler = { + NAME = "Aqara Roller Shade Handler", + lifecycle_handlers = { + added = device_added, + infoChanged = device_info_changed + }, + capability_handlers = { + [capabilities.windowShadeLevel.ID] = { + [capabilities.windowShadeLevel.commands.setShadeLevel.NAME] = window_shade_level_cmd + }, + [capabilities.windowShade.ID] = { + [capabilities.windowShade.commands.open.NAME] = window_shade_open_cmd, + [capabilities.windowShade.commands.close.NAME] = window_shade_close_cmd, + }, + [shadeRotateState.ID] = { + [setRotateStateCommandName] = set_rotate_command_handler + } + }, + zigbee_handlers = { + attr = { + [Basic.ID] = { + [aqara_utils.SHADE_STATE_ATTRIBUTE_ID] = shade_state_report_handler, + [aqara_utils.PREF_ATTRIBUTE_ID] = pref_report_handler + } + } + }, + can_handle = function(opts, driver, device, ...) + return device:get_model() == "lumi.curtain.aq2" + end +} + +return aqara_roller_shade_handler diff --git a/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_axis.lua b/drivers/SmartThings/zigbee-window-treatment/src/test/test_zigbee_window_treatment_axis.lua old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zwave-sensor/profiles/shelly-wave-motion.yml b/drivers/SmartThings/zwave-sensor/profiles/shelly-wave-motion.yml index 5bbeab8ee5..a5be21b68e 100644 --- a/drivers/SmartThings/zwave-sensor/profiles/shelly-wave-motion.yml +++ b/drivers/SmartThings/zwave-sensor/profiles/shelly-wave-motion.yml @@ -1,59 +1,59 @@ -name: shelly-wave-motion -components: -- id: main - capabilities: - - id: motionSensor - version: 1 - - id: illuminanceMeasurement - version: 1 - config: - values: - - key: "illuminance.value" - range: [0, 10000] - - id: battery - version: 1 - - id: refresh - version: 1 - categories: - - name: ContactSensor -preferences: - - name: "ledOpnClsChangeStat" - title: "P157: open/close change status" - description: "This parameter enables open/close status change by LED indicator." - required: false - preferenceType: enumeration - definition: - options: - 0 : "LED ind.disabled" - 1 : "LED ind.enabled" - default: 0 - - name: "sensitivity" - title: "P158: sensitivity" - description: "Sensitivity" - - required: false - preferenceType: enumeration - definition: - options: - 0 : "low sensitivity" - 1 : "moderate sensitivity" - 2 : "high sensitivity" - default: 0 - - name: "blindTime" - title: "P159: Motion Blind time" - description: "Blind time in seconds after last detected motion" - required: false - preferenceType: integer - definition: - minimum: 2 - maximum : 8 - default: 5 - - name: "motionNotdetRepT" - title: "P160:Motion not detect.rep.time" - description: "Time after last detected motion for device to send motion not detected" - required: false - preferenceType: integer - definition: - minimum: 1 - maximum : 32767 +name: shelly-wave-motion +components: +- id: main + capabilities: + - id: motionSensor + version: 1 + - id: illuminanceMeasurement + version: 1 + config: + values: + - key: "illuminance.value" + range: [0, 10000] + - id: battery + version: 1 + - id: refresh + version: 1 + categories: + - name: ContactSensor +preferences: + - name: "ledOpnClsChangeStat" + title: "P157: open/close change status" + description: "This parameter enables open/close status change by LED indicator." + required: false + preferenceType: enumeration + definition: + options: + 0 : "LED ind.disabled" + 1 : "LED ind.enabled" + default: 0 + - name: "sensitivity" + title: "P158: sensitivity" + description: "Sensitivity" + + required: false + preferenceType: enumeration + definition: + options: + 0 : "low sensitivity" + 1 : "moderate sensitivity" + 2 : "high sensitivity" + default: 0 + - name: "blindTime" + title: "P159: Motion Blind time" + description: "Blind time in seconds after last detected motion" + required: false + preferenceType: integer + definition: + minimum: 2 + maximum : 8 + default: 5 + - name: "motionNotdetRepT" + title: "P160:Motion not detect.rep.time" + description: "Time after last detected motion for device to send motion not detected" + required: false + preferenceType: integer + definition: + minimum: 1 + maximum : 32767 default: 30 \ No newline at end of file diff --git a/drivers/SmartThings/zwave-thermostat/profiles/base-radiator-thermostat-10to30C.yml b/drivers/SmartThings/zwave-thermostat/profiles/base-radiator-thermostat-10to30C.yml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zwave-thermostat/profiles/base-radiator-thermostat.yml b/drivers/SmartThings/zwave-thermostat/profiles/base-radiator-thermostat.yml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zwave-thermostat/profiles/popp-radiator-thermostat.yml b/drivers/SmartThings/zwave-thermostat/profiles/popp-radiator-thermostat.yml old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zwave-thermostat/src/aeotec-radiator-thermostat/init.lua b/drivers/SmartThings/zwave-thermostat/src/aeotec-radiator-thermostat/init.lua old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zwave-thermostat/src/init.lua b/drivers/SmartThings/zwave-thermostat/src/init.lua old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zwave-thermostat/src/popp-radiator-thermostat/init.lua b/drivers/SmartThings/zwave-thermostat/src/popp-radiator-thermostat/init.lua old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zwave-thermostat/src/test/test_aeotec_radiator_thermostat.lua b/drivers/SmartThings/zwave-thermostat/src/test/test_aeotec_radiator_thermostat.lua old mode 100755 new mode 100644 diff --git a/drivers/SmartThings/zwave-thermostat/src/test/test_popp_radiator_thermostat.lua b/drivers/SmartThings/zwave-thermostat/src/test/test_popp_radiator_thermostat.lua old mode 100755 new mode 100644 diff --git a/tools/run_driver_tests.py b/tools/run_driver_tests.py old mode 100755 new mode 100644