From 6a782ec522ca51314c391d30b2cf5e5622a93842 Mon Sep 17 00:00:00 2001 From: Liu Yiding Date: Mon, 4 Mar 2024 11:16:31 +0800 Subject: [PATCH 01/21] Migrate HS6ESK-W-EF-3.0-switch from DTH to EdgeDriver Signed-off-by: Liu Yiding --- drivers/SmartThings/zigbee-switch/fingerprints.yml | 5 +++++ .../zigbee-switch/profiles/basic-plug.yml | 12 ++++++++++++ tools/localizations/cn.csv | 1 + 3 files changed, 18 insertions(+) create mode 100644 drivers/SmartThings/zigbee-switch/profiles/basic-plug.yml diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index e1d212be01..54f3b9f665 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -222,6 +222,11 @@ zigbeeManufacturer: manufacturer: HEIMAN model: HS6SW3A-W-EF-3.0 deviceProfileName: basic-switch + - id: "HEIMAN/HS6ESK-W-EF-3.0" + deviceLabel: "HEIMAN Outlet" + manufacturer: HEIMAN + model: HS6ESK-W-EF-3.0 + deviceProfileName: basic-plug - id: "HEIMAN/E_Socket" deviceLabel: "HEIMAN Outlet" manufacturer: HEIMAN diff --git a/drivers/SmartThings/zigbee-switch/profiles/basic-plug.yml b/drivers/SmartThings/zigbee-switch/profiles/basic-plug.yml new file mode 100644 index 0000000000..cb85d825da --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/profiles/basic-plug.yml @@ -0,0 +1,12 @@ +name: basic-plug +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: SmartPlug \ No newline at end of file diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index 934f8ffe14..676dca0af4 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -8,6 +8,7 @@ HEIMAN Carbon Monoxide Sensor,海曼一氧化碳报警器 HEIMAN Gas Detector,海曼燃气报警器 HEIMAN Motion Sensor,海曼人体红外传感器 HEIMAN Multipurpose Sensor,海曼温湿度传感器 +HEIMAN Outlet,海曼智能插座 HEIMAN Open/Closed Sensor,海曼门窗传感器 HEIMAN Remote Control,海曼情景开关 HEIMAN Scene Panel,海曼情景开关 From c3ad6690261bfac323a24933d13b5a4d12a798c0 Mon Sep 17 00:00:00 2001 From: Carter Swedal Date: Tue, 5 Mar 2024 09:20:41 -0700 Subject: [PATCH 02/21] Update JBL lunchbox, to utilize new luncheon library (#1252) --- drivers/SmartThings/jbl/src/lunchbox/rest.lua | 114 ++++++++---------- .../jbl/src/lunchbox/sse/eventsource.lua | 39 +++--- 2 files changed, 68 insertions(+), 85 deletions(-) diff --git a/drivers/SmartThings/jbl/src/lunchbox/rest.lua b/drivers/SmartThings/jbl/src/lunchbox/rest.lua index 92df5441fb..340b73e114 100644 --- a/drivers/SmartThings/jbl/src/lunchbox/rest.lua +++ b/drivers/SmartThings/jbl/src/lunchbox/rest.lua @@ -1,9 +1,18 @@ +---@class ChunkedResponse : Response +---@field package _received_body boolean +---@field package _parsed_headers boolean +---@field public new fun(status_code: number, socket: table?): ChunkedResponse +---@field public fill_body fun(self: ChunkedResponse): string? +---@field public append_body fun(self: ChunkedResponse, next_chunk_body: string): ChunkedResponse + local socket = require "cosock.socket" -local utils = require "utils" +local utils = require "utils" local lb_utils = require "lunchbox.util" local Request = require "luncheon.request" -local Response = require "luncheon.response" +local Response = require "luncheon.response" --[[@as ChunkedResponse]] + +local api_version = require("version").api local RestCallStates = { SEND = "Send", @@ -14,16 +23,14 @@ local RestCallStates = { } local function connect(client) - local default_port = 80 + local port = 80 local use_ssl = false if client.base_url.scheme == "https" then - default_port = 443 + port = 443 use_ssl = true end - local port = client.base_url.port or default_port - local sock, err = client.socket_builder(client.base_url.host, port, use_ssl) if sock == nil then @@ -43,16 +50,22 @@ local function reconnect(client) return connect(client) end +---comment +---@param client RestClient +---@param request Request +---@return integer? bytes_sent +---@return string? err_msg +---@return integer idx local function send_request(client, request) if client.socket == nil then - return nil, "no socket available" + return nil, "no socket available", 0 end local payload = request:serialize() local bytes, err, idx = nil, nil, 0 repeat bytes, err, idx = client.socket:send(payload, idx + 1, #payload) until (bytes == #payload) - or (err ~= nil) + or (err ~= nil) return bytes, err, idx end @@ -63,7 +76,7 @@ local function parse_chunked_response(original_response, sock) EXPECTING_BODY_CHUNK = "ExpectingBodyChunk", } - local full_response = Response.new(original_response.status, nil) + local full_response = Response.new(original_response.status, nil) --[[@as ChunkedResponse]] for header in original_response.headers:iter() do full_response.headers:append_chunk(header) end @@ -130,39 +143,12 @@ local function parse_chunked_response(original_response, sock) return full_response end -local function recv_additional_response(original_response, sock) - local full_response = Response.new(original_response.status, nil) - local headers = original_response:get_headers() - local content_length_str = headers:get_one("Content-Length") - local content_length = nil - local bytes_read = 0 - if content_length_str then - content_length = math.tointeger(content_length_str) - end - - local next_recv, next_err, partial - - repeat - next_recv, next_err, partial = sock:receive(content_length - bytes_read) - - if next_recv ~= nil and #next_recv >= 1 then - full_response:append_body(next_recv) - bytes_read = bytes_read + #next_recv - end - - if partial ~= nil and #partial >= 1 then - full_response:append_body(partial) - bytes_read = bytes_read + #partial - end - until next_err == "closed" or bytes_read >= content_length - - full_response._received_body = true - full_response._parsed_headers = true - - return full_response -end - local function handle_response(sock) + if api_version >= 9 then + local response, err = Response.tcp_source(sock) + if err or (not response) then return response, (err or "unknown error") end + return response, response:fill_body() + end -- called select right before passing in so we receive immediately local initial_recv, initial_err, partial = Response.source(function() return sock:receive('*l') end) @@ -171,9 +157,7 @@ local function handle_response(sock) if initial_recv ~= nil then local headers = initial_recv:get_headers() - if headers:get_one("Content-Length") then - full_response = recv_additional_response(initial_recv, sock) - elseif headers:get_one("Transfer-Encoding") == "chunked" then + if headers and headers:get_one("Transfer-Encoding") == "chunked" then local response, err = parse_chunked_response(initial_recv, sock) if err ~= nil then return nil, err @@ -191,12 +175,12 @@ end local function execute_request(client, request, retry_fn) if not client._active then - return nil, "Called `execute request` on a terminated REST Client" + return nil, "Called `execute request` on a terminated REST Client", nil end if client.socket == nil then local success, err = connect(client) - if not success then return nil, err end + if not success then return nil, err, nil end end local should_retry = retry_fn @@ -208,7 +192,7 @@ local function execute_request(client, request, retry_fn) -- send output local _bytes_sent, send_err, _idx = nil, nil, 0 -- recv output - local response, recv_err, _partial = nil, nil, nil + local response, recv_err, partial = nil, nil, nil -- return values local ret, err = nil, nil @@ -235,7 +219,7 @@ local function execute_request(client, request, retry_fn) current_state = RestCallStates.COMPLETE end elseif current_state == RestCallStates.RECEIVE then - response, recv_err, _partial = handle_response(client.socket) + response, recv_err, partial = handle_response(client.socket) if not recv_err then ret = response @@ -271,7 +255,7 @@ local function execute_request(client, request, retry_fn) end until current_state == RestCallStates.COMPLETE - return ret, err + return ret, err, partial end ---@class RestClient @@ -283,19 +267,17 @@ RestClient.__index = RestClient function RestClient.one_shot_get(full_url, additional_headers, socket_builder) local url_table = lb_utils.force_url_table(full_url) - local client = RestClient.new(url_table.scheme .. "://" .. url_table.authority, socket_builder) + local client = RestClient.new(url_table.scheme .. "://" .. url_table.host, socket_builder) local ret, err = client:get(url_table.path, additional_headers) client:shutdown() - client = nil return ret, err end function RestClient.one_shot_post(full_url, body, additional_headers, socket_builder) local url_table = lb_utils.force_url_table(full_url) - local client = RestClient.new(url_table.scheme .. "://" .. url_table.authority, socket_builder) + local client = RestClient.new(url_table.scheme .. "://" .. url_table.host, socket_builder) local ret, err = client:post(url_table.path, body, additional_headers) client:shutdown() - client = nil return ret, err end @@ -322,10 +304,10 @@ end function RestClient:get(path, additional_headers, retry_fn) local request = Request.new("GET", path, nil):add_header( - "user-agent", "smartthings-lua-edge-driver" - ):add_header("host", string.format("%s", self.base_url.host)):add_header( - "connection", "keep-alive" - ) + "user-agent", "smartthings-lua-edge-driver" + ):add_header("host", string.format("%s", self.base_url.host)):add_header( + "connection", "keep-alive" + ) if additional_headers ~= nil and type(additional_headers) == "table" then for k, v in pairs(additional_headers) do request = request:add_header(k, v) end @@ -336,10 +318,10 @@ end function RestClient:post(path, body_string, additional_headers, retry_fn) local request = Request.new("POST", path, nil):add_header( - "user-agent", "smartthings-lua-edge-driver" - ):add_header("host", string.format("%s", self.base_url.host)):add_header( - "connection", "keep-alive" - ) + "user-agent", "smartthings-lua-edge-driver" + ):add_header("host", string.format("%s", self.base_url.host)):add_header( + "connection", "keep-alive" + ) if additional_headers ~= nil and type(additional_headers) == "table" then for k, v in pairs(additional_headers) do request = request:add_header(k, v) end @@ -352,10 +334,10 @@ end function RestClient:put(path, body_string, additional_headers, retry_fn) local request = Request.new("PUT", path, nil):add_header( - "user-agent", "smartthings-lua-edge-driver" - ):add_header("host", string.format("%s", self.base_url.host)):add_header( - "connection", "keep-alive" - ) + "user-agent", "smartthings-lua-edge-driver" + ):add_header("host", string.format("%s", self.base_url.host)):add_header( + "connection", "keep-alive" + ) if additional_headers ~= nil and type(additional_headers) == "table" then for k, v in pairs(additional_headers) do request = request:add_header(k, v) end @@ -372,7 +354,7 @@ function RestClient.new(base_url, sock_builder) if type(sock_builder) ~= "function" then sock_builder = utils.labeled_socket_builder() end return - setmetatable({base_url = base_url, socket_builder = sock_builder, socket = nil, _active = true}, RestClient) + setmetatable({base_url = base_url, socket_builder = sock_builder, socket = nil, _active = true}, RestClient) end return RestClient diff --git a/drivers/SmartThings/jbl/src/lunchbox/sse/eventsource.lua b/drivers/SmartThings/jbl/src/lunchbox/sse/eventsource.lua index d016ff4908..67e4a33d20 100644 --- a/drivers/SmartThings/jbl/src/lunchbox/sse/eventsource.lua +++ b/drivers/SmartThings/jbl/src/lunchbox/sse/eventsource.lua @@ -21,15 +21,15 @@ local Response = require "luncheon.response" --- @field public onopen function in-line callback for on-open events --- @field public onmessage function in-line callback for on-message events --- @field public onerror function in-line callback for on-error events; error callbacks will fire ---- @field private _reconnect boolean flag that says whether or not the client should attempt to reconnect on close. ---- @field private _reconnect_time_millis number The amount of time to wait between reconnects, in millis. Can be sent by the server. ---- @field private _sock_builder function|nil optional. If this function exists, it will be called to create a new TCP socket on connection. ---- @field private _sock table the TCP socket for the connection ---- @field private _needs_more boolean flag to track whether or not we're still expecting mroe on this source before we dispatch ---- @field private _last_field string the last field the parsing path saw, in case it needs to append more to its value ---- @field private _extra_headers table a table of string:string key-value pairs that will be inserted in to the initial requests's headers. ---- @field private _parse_buffers table inner state, keeps track of the various event stream buffers in between dispatches. ---- @field private _listeners table event listeners attached using the add_event_listener API instead of the inline callbacks. +--- @field package _reconnect boolean flag that says whether or not the client should attempt to reconnect on close. +--- @field package _reconnect_time_millis number The amount of time to wait between reconnects, in millis. Can be sent by the server. +--- @field package _sock_builder function|nil optional. If this function exists, it will be called to create a new TCP socket on connection. +--- @field package _sock table? the TCP socket for the connection +--- @field package _needs_more boolean flag to track whether or not we're still expecting mroe on this source before we dispatch +--- @field package _last_field string the last field the parsing path saw, in case it needs to append more to its value +--- @field package _extra_headers table a table of string:string key-value pairs that will be inserted in to the initial requests's headers. +--- @field package _parse_buffers table inner state, keeps track of the various event stream buffers in between dispatches. +--- @field package _listeners table event listeners attached using the add_event_listener API instead of the inline callbacks. local EventSource = {} EventSource.__index = EventSource @@ -81,7 +81,7 @@ local function send_stream_start_request(payload, sock) until (bytes == #payload) or (err ~= nil) if err then - log.error("send error: " .. err) + log.error_with({ hub_logs = true }, "send error: " .. err) end return bytes, err, idx @@ -156,8 +156,8 @@ local valid_fields = util.read_only { -- h/t to github.com/FreeMasen for the suggestions on the efficient implementation of this local function find_line_endings(chunk) local r_idx, n_idx = string.find(chunk, "[\r\n]+") - if r_idx == n_idx then - -- 1 character + if r_idx == nil or r_idx == n_idx then + -- 1 character or no match return r_idx, n_idx end local slice = string.sub(chunk, r_idx, n_idx) @@ -289,8 +289,8 @@ local function connecting_action(source) local response response, err = Response.tcp_source(source._sock) - if err ~= nil then - return nil, err + if not response or err ~= nil then + return nil, err or "nil response from Response.tcp_source" end if response.status ~= 200 then @@ -301,7 +301,7 @@ local function connecting_action(source) if err ~= nil then return nil, err end - local content_type = string.lower((headers:get_one('content-type') or "none")) + local content_type = string.lower((headers and headers:get_one('content-type') or "none")) if not content_type:find("text/event-stream", 1, true) then local err_msg = "Expected content type of text/event-stream in response headers, received: " .. content_type return nil, err_msg @@ -375,7 +375,8 @@ local function open_action(source) local recv_dbg = recv or "" if #recv_dbg == 0 then recv_dbg = "" end recv_dbg = recv_dbg:gsub("\r\n", ""):gsub("\n", ""):gsub("\r", "") - log.error(string.format("Received %s while expecting a chunked encoding payload length (hex number)\n", recv_dbg)) + log.error_with({ hub_logs = true }, + string.format("Received %s while expecting a chunked encoding payload length (hex number)\n", recv_dbg)) end end @@ -466,16 +467,16 @@ function EventSource.new(url, extra_headers, sock_builder) local st_utils = require "st.utils" while true do if source.ready_state == EventSource.ReadyStates.CLOSED and - not source._reconnect + not source._reconnect then return end local _, action_err, partial = state_actions[source.ready_state](source) if action_err ~= nil then if action_err ~= "timeout" or action_err ~= "wantread" then - log.error("Event Source Coroutine State Machine error: " .. action_err) + log.error_with({ hub_logs = true }, "Event Source Coroutine State Machine error: " .. action_err) if partial ~= nil and #partial > 0 then - log.error(st_utils.stringify_table(partial, "\tReceived Partial", true)) + log.error_with({ hub_logs = true }, st_utils.stringify_table(partial, "\tReceived Partial", true)) end source.ready_state = EventSource.ReadyStates.CLOSED end From d5521c6131da09cb536c7f921e7ae9200f485d19 Mon Sep 17 00:00:00 2001 From: Hongming6 <150672162+Hongming6@users.noreply.github.com> Date: Wed, 6 Mar 2024 05:13:46 +0800 Subject: [PATCH 03/21] Aqara Smart Smoke Detector (#1162) * Aqara Smart Smoke Detector * Fix formatting issues * Example Delete the configurations that support self-check * Update the self-check code of the device * Update the self-check code of the device * Remove duplicate Aqara * Delete debug information * Update self-check * Update PR * Add metadata * Adding the correct CN translation for Aqara Product * Delete .yml --- .../capabilities/selfCheck.yaml | 23 +++ .../zigbee-smoke-detector/fingerprints.yml | 5 + .../profiles/smoke-battery-aqara.yml | 21 +++ .../zigbee-smoke-detector/src/aqara/init.lua | 157 ++++++++++++++++ .../zigbee-smoke-detector/src/init.lua | 5 +- .../src/test/test_aqara_smoke_detector.lua | 175 ++++++++++++++++++ tools/localizations/cn.csv | 4 +- 7 files changed, 387 insertions(+), 3 deletions(-) create mode 100644 drivers/SmartThings/zigbee-smoke-detector/capabilities/selfCheck.yaml create mode 100644 drivers/SmartThings/zigbee-smoke-detector/profiles/smoke-battery-aqara.yml create mode 100644 drivers/SmartThings/zigbee-smoke-detector/src/aqara/init.lua create mode 100644 drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_smoke_detector.lua diff --git a/drivers/SmartThings/zigbee-smoke-detector/capabilities/selfCheck.yaml b/drivers/SmartThings/zigbee-smoke-detector/capabilities/selfCheck.yaml new file mode 100644 index 0000000000..b74e48a05e --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/capabilities/selfCheck.yaml @@ -0,0 +1,23 @@ +id: stse.selfCheck +version: 1 +status: proposed +name: Self Check +ephemeral: false +attributes: + selfCheckState: + schema: + type: object + properties: + value: + type: string + enum: + - idle + - selfChecking + - selfCheckCompleted + additionalProperties: false + required: + - value +commands: + startSelfCheck: + name: startSelfCheck + arguments: [] diff --git a/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml b/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml index 2dacf00653..fe6ac2f884 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml +++ b/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml @@ -1,4 +1,9 @@ zigbeeManufacturer: + - id: "LUMI/lumi.sensor_smoke.acn03" + deviceLabel: Aqara Smart Smoke Detector + manufacturer: LUMI + model: lumi.sensor_smoke.acn03 + deviceProfileName: smoke-battery-aqara - id: "Heiman/Orvibo/Gas1" deviceLabel: Orvibo Smoke Detector manufacturer: Heiman diff --git a/drivers/SmartThings/zigbee-smoke-detector/profiles/smoke-battery-aqara.yml b/drivers/SmartThings/zigbee-smoke-detector/profiles/smoke-battery-aqara.yml new file mode 100644 index 0000000000..b6d30b2ecc --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/profiles/smoke-battery-aqara.yml @@ -0,0 +1,21 @@ +name: smoke-battery-aqara +components: +- id: main + capabilities: + - id: smokeDetector + version: 1 + - id: audioMute + version: 1 + - id: stse.selfCheck + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: SmokeDetector +metadata: + mnmn: SmartThingsCommunity + vid: b61c7b6b-10ab-34d7-9bd5-3c0912ce129f \ No newline at end of file diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/aqara/init.lua b/drivers/SmartThings/zigbee-smoke-detector/src/aqara/init.lua new file mode 100644 index 0000000000..ddf9e791a0 --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/src/aqara/init.lua @@ -0,0 +1,157 @@ +-- 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 data_types = require "st.zigbee.data_types" +local clusters = require "st.zigbee.zcl.clusters" +local cluster_base = require "st.zigbee.cluster_base" +local battery_defaults = require "st.zigbee.defaults.battery_defaults" +local capabilities = require "st.capabilities" + + +local selfCheck = capabilities["stse.selfCheck"] +local startSelfCheckCommandName = "startSelfCheck" + +local PRIVATE_CLUSTER_ID = 0xFCC0 +local PRIVATE_ATTRIBUTE_ID = 0x0009 +local MFG_CODE = 0x115F +local PRIVATE_MUTE_ATTRIBUTE_ID = 0x0126 +local PRIVATE_SELF_CHECK_ATTRIBUTE_ID = 0x0127 +local PRIVATE_SMOKE_ZONE_STATUS_ATTRIBUTE_ID = 0x013A +local PowerConfiguration = clusters.PowerConfiguration + + +local FINGERPRINTS = { + { mfr = "LUMI", model = "lumi.sensor_smoke.acn03" } +} + + +local CONFIGURATIONS = { + { + cluster = PRIVATE_CLUSTER_ID, + attribute = PRIVATE_SMOKE_ZONE_STATUS_ATTRIBUTE_ID, + minimum_interval = 1, + maximum_interval = 3600, + data_type = data_types.Uint16, + reportable_change = 1 + }, + { + cluster = PowerConfiguration.ID, + attribute = PowerConfiguration.attributes.BatteryVoltage.ID, + minimum_interval = 30, + maximum_interval = 3600, + data_type = PowerConfiguration.attributes.BatteryVoltage.base_type, + reportable_change = 1 + } +} + +local function smoke_zone_status_handler(driver, device, value, zb_rx) + if value.value == 1 then + device:emit_event(capabilities.smokeDetector.smoke.detected()) + elseif value.value == 0 then + device:emit_event(capabilities.smokeDetector.smoke.clear()) + end +end + +local function buzzer_status_handler(driver, device, value, zb_rx) + if value.value == 1 then + device:emit_event(capabilities.audioMute.mute.muted()) + elseif value.value == 0 then + device:emit_event(capabilities.audioMute.mute.unmuted()) + end +end + + +local function selfcheck_status_handler(driver, device, value, zb_rx) + if value.value == 0 then + device:emit_event(selfCheck.selfCheckState.idle()) + elseif value.value == 1 then + device:emit_event(selfCheck.selfCheckState.selfCheckCompleted()) + end +end + + + +local function mute_handler(driver, device, cmd) + device:send(cluster_base.write_manufacturer_specific_attribute(device, + PRIVATE_CLUSTER_ID, PRIVATE_MUTE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 1)) +end + +local function unmute_handler(driver, device, cmd) + device:send(cluster_base.write_manufacturer_specific_attribute(device, + PRIVATE_CLUSTER_ID, PRIVATE_MUTE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0)) +end + + +local function self_check_attr_handler(self, device, zone_status, zb_rx) + device:emit_event(selfCheck.selfCheckState.selfChecking()) + device:send(cluster_base.write_manufacturer_specific_attribute(device, + PRIVATE_CLUSTER_ID, PRIVATE_SELF_CHECK_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true)) +end + +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 device_init(driver, device) + battery_defaults.build_linear_voltage_init(2.6, 3.0)(driver, device) + if CONFIGURATIONS ~= nil then + for _, attribute in ipairs(CONFIGURATIONS) do + device:add_configured_attribute(attribute) + device:add_monitored_attribute(attribute) + end + end +end + +local function device_added(driver, device) + device:send(cluster_base.write_manufacturer_specific_attribute(device, + PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0x01)) + device:emit_event(capabilities.smokeDetector.smoke.clear()) + device:emit_event(capabilities.audioMute.mute.unmuted()) + device:emit_event(selfCheck.selfCheckState.idle()) + device:emit_event(capabilities.battery.battery(100)) +end + +local aqara_gas_detector_handler = { + NAME = "Aqara Smoke Detector Handler", + lifecycle_handlers = { + init = device_init, + added = device_added + }, + zigbee_handlers = { + attr = { + [PRIVATE_CLUSTER_ID] = { + [PRIVATE_SMOKE_ZONE_STATUS_ATTRIBUTE_ID] = smoke_zone_status_handler, + [PRIVATE_MUTE_ATTRIBUTE_ID] = buzzer_status_handler, + [PRIVATE_SELF_CHECK_ATTRIBUTE_ID] = selfcheck_status_handler + }, + } + }, + capability_handlers = { + [capabilities.audioMute.ID] = { + [capabilities.audioMute.commands.mute.NAME] = mute_handler, + [capabilities.audioMute.commands.unmute.NAME] = unmute_handler + }, + [selfCheck.ID] = { + [startSelfCheckCommandName] = self_check_attr_handler + }, + }, + can_handle = is_aqara_products +} + +return aqara_gas_detector_handler + diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/init.lua b/drivers/SmartThings/zigbee-smoke-detector/src/init.lua index b2736d6d13..1a704222f1 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/init.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/init.lua @@ -22,7 +22,10 @@ local zigbee_smoke_driver_template = { capabilities.smokeDetector, capabilities.battery }, - sub_drivers = { require("frient") }, + sub_drivers = { + require("aqara"), + require("frient") + }, ias_zone_configuration_method = constants.IAS_ZONE_CONFIGURE_TYPE.AUTO_ENROLL_RESPONSE, } diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_smoke_detector.lua b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_smoke_detector.lua new file mode 100644 index 0000000000..439e590feb --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_smoke_detector.lua @@ -0,0 +1,175 @@ +-- 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 test = require "integration_test" +local t_utils = require "integration_test.utils" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +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 PowerConfiguration = clusters.PowerConfiguration +local selfCheck = capabilities["stse.selfCheck"] +local selfCheckId = "stse.selfCheck" + + +test.add_package_capability("selfCheck.yaml") + +local PRIVATE_CLUSTER_ID = 0xFCC0 +local PRIVATE_ATTRIBUTE_ID = 0x0009 +local MFG_CODE = 0x115F +local PRIVATE_MUTE_ATTRIBUTE_ID = 0x0126 +local PRIVATE_SELF_CHECK_ATTRIBUTE_ID = 0x0127 +local PRIVATE_SMOKE_ZONE_STATUS_ATTRIBUTE_ID = 0x013A + + + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("smoke-battery-aqara.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "LUMI", + model = "lumi.sensor_smoke.acn03", + server_clusters = { 0x0001, 0xFCC0 } + } + } + } +) + + + +zigbee_test_utils.prepare_zigbee_env_info() +local function test_init() + test.mock_device.add_test_device(mock_device) +end + + + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Handle added lifecycle", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE, + data_types.Uint8, 0x01) }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.smokeDetector.smoke.clear())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.audioMute.mute.unmuted())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", selfCheck.selfCheckState.idle())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(100))) + end +) + + + +test.register_coroutine_test( + "smokeDetector report should be handled", + function() + local attr_report_data = { + { PRIVATE_SMOKE_ZONE_STATUS_ATTRIBUTE_ID, data_types.Uint16.ID, 0x0001 } + } + 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", + capabilities.smokeDetector.smoke.detected())) + end +) + + + +test.register_coroutine_test( + "audioMute report should be handled", + function() + local attr_report_data = { + { PRIVATE_MUTE_ATTRIBUTE_ID, data_types.Uint8.ID, 0x01 } + } + 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", + capabilities.audioMute.mute.muted())) + end +) + + + +test.register_coroutine_test( + "Capability on command should be handled : device mute", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "audioMute", component = "main", command = "mute", args = {} } }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + PRIVATE_MUTE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 1) }) + end +) + + + +test.register_coroutine_test( + "selfCheck report should be handled", + function() + local attr_report_data = { + { PRIVATE_SELF_CHECK_ATTRIBUTE_ID, data_types.Uint8.ID, 0x01 } + } + 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", + selfCheck.selfCheckState.selfCheckCompleted())) + end +) + + + +test.register_coroutine_test( + "Capability on command should be handled : device selfCheck", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = selfCheckId, component = "main", command = "startSelfCheck", args = {state = "selfCheckCompleted"} } }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main",selfCheck.selfCheckState.selfChecking())) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + PRIVATE_SELF_CHECK_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) + end +) + + + +test.register_message_test( + "Battery voltage report should be handled", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_device.id, PowerConfiguration.attributes.BatteryVoltage:build_test_attr_report(mock_device, 30) } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.battery.battery(100)) + } + } +) + + +test.run_registered_tests() diff --git a/tools/localizations/cn.csv b/tools/localizations/cn.csv index 934f8ffe14..340a50f8ed 100644 --- a/tools/localizations/cn.csv +++ b/tools/localizations/cn.csv @@ -50,9 +50,9 @@ Aqara Smart Pet Feeder C1,Aqara智能宠物喂食器 C1 "Aqara Smart Wall Switch H1 (With Neutral,Single Rocker)",Aqara智能墙壁开关H1 (零火线单键版) "Aqara Smart Wall Switch H1 (With Neutral,Double Rocker) 1",Aqara智能墙壁开关H1 (零火线双键版) 1 "Aqara Smart Wall Switch H1 (With Neutral,Triple Rocker) 1",Aqara智能墙壁开关H1 (零火线三键版) 1 -Aqara Smart Gas Detector,Aqara天然气报警器 +Aqara Smart Natural Gas Detector,Aqara天然气报警器 Aqara Smart Smoke Detector,Aqara烟雾报警器 Aqara Smart Plug T1,Aqara 智能插座 T1 (国标) Aqara Wireless Mini Switch T1,Aqara 无线开关 T1 "Aqara Wireless Remote Switch E1 (Single Rocker)",Aqara 无线开关 E1 (贴墙式单键版) -"Aqara LED Light Bulb T1 (Tunable White)",Aqara LED灯泡 T1 (可调色温) +"Aqara LED Light Bulb T1 (Tunable White)",Aqara LED灯泡 T1 (可调色温) \ No newline at end of file From 0be527df996d7bd9ecc54084c8e956c40ef7878e Mon Sep 17 00:00:00 2001 From: Tom Manley Date: Fri, 1 Mar 2024 15:16:39 -0600 Subject: [PATCH 04/21] matter-sensor: Add support for Pressure Sensor This includes support for a standalone pressure sensor and also a temp, humidity and pressure sensor, both w/ or w/o battery. --- .../matter-sensor/fingerprints.yml | 12 ++ .../profiles/pressure-battery.yml | 14 ++ .../matter-sensor/profiles/pressure.yml | 12 ++ .../temperature-humidity-pressure-battery.yml | 23 +++ .../temperature-humidity-pressure.yml | 21 +++ .../src/PressureMeasurement/init.lua | 144 ++++++++++++++++ .../server/attributes/AcceptedCommandList.lua | 126 ++++++++++++++ .../server/attributes/AttributeList.lua | 126 ++++++++++++++ .../server/attributes/EventList.lua | 126 ++++++++++++++ .../server/attributes/MaxMeasuredValue.lua | 116 +++++++++++++ .../server/attributes/MaxScaledValue.lua | 116 +++++++++++++ .../server/attributes/MeasuredValue.lua | 116 +++++++++++++ .../server/attributes/MinMeasuredValue.lua | 116 +++++++++++++ .../server/attributes/MinScaledValue.lua | 116 +++++++++++++ .../server/attributes/Scale.lua | 116 +++++++++++++ .../server/attributes/ScaledTolerance.lua | 116 +++++++++++++ .../server/attributes/ScaledValue.lua | 116 +++++++++++++ .../server/attributes/Tolerance.lua | 116 +++++++++++++ .../server/attributes/init.lua | 59 +++++++ .../server/commands/init.lua | 41 +++++ .../server/events/init.lua | 42 +++++ .../src/PressureMeasurement/types/Feature.lua | 81 +++++++++ .../src/PressureMeasurement/types/init.lua | 35 ++++ .../SmartThings/matter-sensor/src/init.lua | 45 ++++- .../src/test/test_matter_pressure_sensor.lua | 156 ++++++++++++++++++ 25 files changed, 2100 insertions(+), 7 deletions(-) create mode 100644 drivers/SmartThings/matter-sensor/profiles/pressure-battery.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/pressure.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/temperature-humidity-pressure-battery.yml create mode 100644 drivers/SmartThings/matter-sensor/profiles/temperature-humidity-pressure.yml create mode 100644 drivers/SmartThings/matter-sensor/src/PressureMeasurement/init.lua create mode 100644 drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/AcceptedCommandList.lua create mode 100644 drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/AttributeList.lua create mode 100644 drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/EventList.lua create mode 100644 drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MaxMeasuredValue.lua create mode 100644 drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MaxScaledValue.lua create mode 100644 drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MeasuredValue.lua create mode 100644 drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MinMeasuredValue.lua create mode 100644 drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MinScaledValue.lua create mode 100644 drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/Scale.lua create mode 100644 drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/ScaledTolerance.lua create mode 100644 drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/ScaledValue.lua create mode 100644 drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/Tolerance.lua create mode 100644 drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/init.lua create mode 100644 drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/commands/init.lua create mode 100644 drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/events/init.lua create mode 100644 drivers/SmartThings/matter-sensor/src/PressureMeasurement/types/Feature.lua create mode 100644 drivers/SmartThings/matter-sensor/src/PressureMeasurement/types/init.lua create mode 100644 drivers/SmartThings/matter-sensor/src/test/test_matter_pressure_sensor.lua diff --git a/drivers/SmartThings/matter-sensor/fingerprints.yml b/drivers/SmartThings/matter-sensor/fingerprints.yml index d095c8a0ca..adb3b06f10 100644 --- a/drivers/SmartThings/matter-sensor/fingerprints.yml +++ b/drivers/SmartThings/matter-sensor/fingerprints.yml @@ -70,12 +70,24 @@ matterGeneric: deviceTypes: - id: 0x0307 # Humidity Sensor deviceProfileName: humidity-battery + - id: "matter/pressure/sensor" + deviceLabel: Matter Pressure Sensor + deviceTypes: + - id: 0x0305 # Pressure Sensor + deviceProfileName: pressure-battery - id: "matter/temperature-humidity/sensor" deviceLabel: Matter Temp/Humidity Sensor deviceTypes: - id: 0x0307 # Humidity Sensor - id: 0x0302 # Temperature Sensor deviceProfileName: temperature-humidity-battery + - id: "matter/temp-humidity-pressure/sensor" + deviceLabel: Matter Temp/Humidity/Pressure Sensor + deviceTypes: + - id: 0x0307 # Humidity Sensor + - id: 0x0302 # Temperature Sensor + - id: 0x0305 # Pressure Sensor + deviceProfileName: temperature-humidity-pressure-battery - id: "matter/motion-illuminance" deviceLabel: Matter Motion/Illuminance Sensor deviceTypes: diff --git a/drivers/SmartThings/matter-sensor/profiles/pressure-battery.yml b/drivers/SmartThings/matter-sensor/profiles/pressure-battery.yml new file mode 100644 index 0000000000..3fd13eabd4 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/pressure-battery.yml @@ -0,0 +1,14 @@ +name: pressure-battery +components: +- id: main + capabilities: + - id: atmosphericPressureMeasurement + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: GenericSensor diff --git a/drivers/SmartThings/matter-sensor/profiles/pressure.yml b/drivers/SmartThings/matter-sensor/profiles/pressure.yml new file mode 100644 index 0000000000..91d85da43a --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/pressure.yml @@ -0,0 +1,12 @@ +name: pressure +components: +- id: main + capabilities: + - id: atmosphericPressureMeasurement + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: GenericSensor diff --git a/drivers/SmartThings/matter-sensor/profiles/temperature-humidity-pressure-battery.yml b/drivers/SmartThings/matter-sensor/profiles/temperature-humidity-pressure-battery.yml new file mode 100644 index 0000000000..ecb432fb88 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/temperature-humidity-pressure-battery.yml @@ -0,0 +1,23 @@ +name: temperature-humidity-pressure-battery +components: +- id: main + capabilities: + - id: temperatureMeasurement + version: 1 + - id: relativeHumidityMeasurement + version: 1 + - id: atmosphericPressureMeasurement + version: 1 + - id: battery + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: HumiditySensor +preferences: + - preferenceId: tempOffset + explicit: true + - preferenceId: humidityOffset + explicit: true diff --git a/drivers/SmartThings/matter-sensor/profiles/temperature-humidity-pressure.yml b/drivers/SmartThings/matter-sensor/profiles/temperature-humidity-pressure.yml new file mode 100644 index 0000000000..424ded6795 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/profiles/temperature-humidity-pressure.yml @@ -0,0 +1,21 @@ +name: temperature-humidity-pressure +components: +- id: main + capabilities: + - id: temperatureMeasurement + version: 1 + - id: relativeHumidityMeasurement + version: 1 + - id: atmosphericPressureMeasurement + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: HumiditySensor +preferences: + - preferenceId: tempOffset + explicit: true + - preferenceId: humidityOffset + explicit: true diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/init.lua b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/init.lua new file mode 100644 index 0000000000..d47c239242 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/init.lua @@ -0,0 +1,144 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. + +local cluster_base = require "st.matter.cluster_base" +local PressureMeasurementServerAttributes = require "PressureMeasurement.server.attributes" +local PressureMeasurementServerCommands = require "PressureMeasurement.server.commands" +local PressureMeasurementTypes = require "PressureMeasurement.types" + +--- @class PressureMeasurement +--- @alias PressureMeasurement +--- +--- @field public ID number 0x0403 the ID of this cluster +--- @field public NAME string "PressureMeasurement" the name of this cluster +--- @field public attributes PressureMeasurementServerAttributes | PressureMeasurementClientAttributes +--- @field public commands PressureMeasurementServerCommands | PressureMeasurementClientCommands +--- @field public types PressureMeasurementTypes + +local PressureMeasurement = {} + +PressureMeasurement.ID = 0x0403 +PressureMeasurement.NAME = "PressureMeasurement" +PressureMeasurement.server = {} +PressureMeasurement.client = {} +PressureMeasurement.server.attributes = PressureMeasurementServerAttributes:set_parent_cluster(PressureMeasurement) +PressureMeasurement.server.commands = PressureMeasurementServerCommands:set_parent_cluster(PressureMeasurement) +PressureMeasurement.types = PressureMeasurementTypes + + +--- Find an attribute by id +--- +--- @param attr_id number +function PressureMeasurement:get_attribute_by_id(attr_id) + local attr_id_map = { + [0x0000] = "MeasuredValue", + [0x0001] = "MinMeasuredValue", + [0x0002] = "MaxMeasuredValue", + [0x0003] = "Tolerance", + [0x0010] = "ScaledValue", + [0x0011] = "MinScaledValue", + [0x0012] = "MaxScaledValue", + [0x0013] = "ScaledTolerance", + [0x0014] = "Scale", + [0xFFF9] = "AcceptedCommandList", + [0xFFFA] = "EventList", + [0xFFFB] = "AttributeList", + } + local attr_name = attr_id_map[attr_id] + if attr_name ~= nil then + return self.attributes[attr_name] + end + return nil +end + +--- Find a server command by id +--- +--- @param command_id number +function PressureMeasurement:get_server_command_by_id(command_id) + local server_id_map = { + } + if server_id_map[command_id] ~= nil then + return self.server.commands[server_id_map[command_id]] + end + return nil +end + + +-- Attribute Mapping +PressureMeasurement.attribute_direction_map = { + ["MeasuredValue"] = "server", + ["MinMeasuredValue"] = "server", + ["MaxMeasuredValue"] = "server", + ["Tolerance"] = "server", + ["ScaledValue"] = "server", + ["MinScaledValue"] = "server", + ["MaxScaledValue"] = "server", + ["ScaledTolerance"] = "server", + ["Scale"] = "server", + ["AcceptedCommandList"] = "server", + ["EventList"] = "server", + ["AttributeList"] = "server", +} + + +-- Command Mapping +PressureMeasurement.command_direction_map = { +} + + +PressureMeasurement.FeatureMap = PressureMeasurement.types.Feature + +function PressureMeasurement.are_features_supported(feature, feature_map) + if (PressureMeasurement.FeatureMap.bits_are_valid(feature)) then + return (feature & feature_map) == feature + end + return false +end + +-- Cluster Completion +local attribute_helper_mt = {} +attribute_helper_mt.__index = function(self, key) + local direction = PressureMeasurement.attribute_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown attribute %s on cluster %s", key, PressureMeasurement.NAME)) + end + return PressureMeasurement[direction].attributes[key] +end +PressureMeasurement.attributes = {} +setmetatable(PressureMeasurement.attributes, attribute_helper_mt) + +local command_helper_mt = {} +command_helper_mt.__index = function(self, key) + local direction = PressureMeasurement.command_direction_map[key] + if direction == nil then + error(string.format("Referenced unknown command %s on cluster %s", key, PressureMeasurement.NAME)) + end + return PressureMeasurement[direction].commands[key] +end +PressureMeasurement.commands = {} +setmetatable(PressureMeasurement.commands, command_helper_mt) + +local event_helper_mt = {} +event_helper_mt.__index = function(self, key) + return PressureMeasurement.server.events[key] +end +PressureMeasurement.events = {} +setmetatable(PressureMeasurement.events, event_helper_mt) + +setmetatable(PressureMeasurement, {__index = cluster_base}) + +return PressureMeasurement + diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/AcceptedCommandList.lua b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/AcceptedCommandList.lua new file mode 100644 index 0000000000..ee9fad2526 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/AcceptedCommandList.lua @@ -0,0 +1,126 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +--- @class st.matter.clusters.PressureMeasurement.AcceptedCommandList +--- @alias AcceptedCommandList +--- +--- @field public ID number 0xFFF9 the ID of this attribute +--- @field public NAME string "AcceptedCommandList" the name of this attribute +--- @field public data_type st.matter.data_types.Array the data type of this attribute + +local AcceptedCommandList = { + ID = 0xFFF9, + NAME = "AcceptedCommandList", + base_type = require "st.matter.data_types.Array", + element_type = require "st.matter.data_types.Uint32", +} + +--- Add additional functionality to the base type object +--- +--- @param base_type_obj st.matter.data_types.Array the base data type object to add functionality to +function AcceptedCommandList:augment_type(data_type_obj) + for i, v in ipairs(data_type_obj.elements) do + data_type_obj.elements[i] = data_types.validate_or_build_type(v, AcceptedCommandList.element_type) + end +end + +--- Create a Array object of this attribute with any additional features provided for the attribute +--- This is also usable with the AcceptedCommandList(...) syntax +--- +--- @vararg vararg the values needed to construct a Array +--- @return st.matter.data_types.Array +function AcceptedCommandList:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +--- Constructs an st.matter.interaction_model.InteractionRequest to read +--- this attribute from a device +--- @param device st.matter.Device +--- @param endpoint_id number|nil +--- @return st.matter.interaction_model.InteractionRequest containing an Interaction Request +function AcceptedCommandList:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + + +--- Reporting policy: AcceptedCommandList => true => mandatory + +--- Sets up a Subscribe Interaction +--- +--- @param device any +--- @param endpoint_id number|nil +--- @return any +function AcceptedCommandList:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AcceptedCommandList:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +--- Builds an AcceptedCommandList test attribute reponse for the driver integration testing framework +--- +--- @param device st.matter.Device the device to build this message for +--- @param endpoint_id number|nil +--- @param value any +--- @param status string Interaction status associated with the path +--- @return st.matter.interaction_model.InteractionResponse of type REPORT_DATA +function AcceptedCommandList:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function AcceptedCommandList:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(AcceptedCommandList, {__call = AcceptedCommandList.new_value, __index = AcceptedCommandList.base_type}) +return AcceptedCommandList + diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/AttributeList.lua b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/AttributeList.lua new file mode 100644 index 0000000000..d4d85b00a0 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/AttributeList.lua @@ -0,0 +1,126 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +--- @class st.matter.clusters.PressureMeasurement.AttributeList +--- @alias AttributeList +--- +--- @field public ID number 0xFFFB the ID of this attribute +--- @field public NAME string "AttributeList" the name of this attribute +--- @field public data_type st.matter.data_types.Array the data type of this attribute + +local AttributeList = { + ID = 0xFFFB, + NAME = "AttributeList", + base_type = require "st.matter.data_types.Array", + element_type = require "st.matter.data_types.Uint32", +} + +--- Add additional functionality to the base type object +--- +--- @param base_type_obj st.matter.data_types.Array the base data type object to add functionality to +function AttributeList:augment_type(data_type_obj) + for i, v in ipairs(data_type_obj.elements) do + data_type_obj.elements[i] = data_types.validate_or_build_type(v, AttributeList.element_type) + end +end + +--- Create a Array object of this attribute with any additional features provided for the attribute +--- This is also usable with the AttributeList(...) syntax +--- +--- @vararg vararg the values needed to construct a Array +--- @return st.matter.data_types.Array +function AttributeList:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +--- Constructs an st.matter.interaction_model.InteractionRequest to read +--- this attribute from a device +--- @param device st.matter.Device +--- @param endpoint_id number|nil +--- @return st.matter.interaction_model.InteractionRequest containing an Interaction Request +function AttributeList:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + + +--- Reporting policy: AttributeList => true => mandatory + +--- Sets up a Subscribe Interaction +--- +--- @param device any +--- @param endpoint_id number|nil +--- @return any +function AttributeList:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function AttributeList:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +--- Builds an AttributeList test attribute reponse for the driver integration testing framework +--- +--- @param device st.matter.Device the device to build this message for +--- @param endpoint_id number|nil +--- @param value any +--- @param status string Interaction status associated with the path +--- @return st.matter.interaction_model.InteractionResponse of type REPORT_DATA +function AttributeList:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function AttributeList:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(AttributeList, {__call = AttributeList.new_value, __index = AttributeList.base_type}) +return AttributeList + diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/EventList.lua b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/EventList.lua new file mode 100644 index 0000000000..b3dc7bf94c --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/EventList.lua @@ -0,0 +1,126 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +--- @class st.matter.clusters.PressureMeasurement.EventList +--- @alias EventList +--- +--- @field public ID number 0xFFFA the ID of this attribute +--- @field public NAME string "EventList" the name of this attribute +--- @field public data_type st.matter.data_types.Array the data type of this attribute + +local EventList = { + ID = 0xFFFA, + NAME = "EventList", + base_type = require "st.matter.data_types.Array", + element_type = require "st.matter.data_types.Uint32", +} + +--- Add additional functionality to the base type object +--- +--- @param base_type_obj st.matter.data_types.Array the base data type object to add functionality to +function EventList:augment_type(data_type_obj) + for i, v in ipairs(data_type_obj.elements) do + data_type_obj.elements[i] = data_types.validate_or_build_type(v, EventList.element_type) + end +end + +--- Create a Array object of this attribute with any additional features provided for the attribute +--- This is also usable with the EventList(...) syntax +--- +--- @vararg vararg the values needed to construct a Array +--- @return st.matter.data_types.Array +function EventList:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +--- Constructs an st.matter.interaction_model.InteractionRequest to read +--- this attribute from a device +--- @param device st.matter.Device +--- @param endpoint_id number|nil +--- @return st.matter.interaction_model.InteractionRequest containing an Interaction Request +function EventList:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + + +--- Reporting policy: EventList => true => mandatory + +--- Sets up a Subscribe Interaction +--- +--- @param device any +--- @param endpoint_id number|nil +--- @return any +function EventList:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function EventList:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +--- Builds an EventList test attribute reponse for the driver integration testing framework +--- +--- @param device st.matter.Device the device to build this message for +--- @param endpoint_id number|nil +--- @param value any +--- @param status string Interaction status associated with the path +--- @return st.matter.interaction_model.InteractionResponse of type REPORT_DATA +function EventList:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function EventList:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(EventList, {__call = EventList.new_value, __index = EventList.base_type}) +return EventList + diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MaxMeasuredValue.lua b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MaxMeasuredValue.lua new file mode 100644 index 0000000000..2484d99ef3 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MaxMeasuredValue.lua @@ -0,0 +1,116 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +--- @class st.matter.clusters.PressureMeasurement.MaxMeasuredValue +--- @alias MaxMeasuredValue +--- +--- @field public ID number 0x0002 the ID of this attribute +--- @field public NAME string "MaxMeasuredValue" the name of this attribute +--- @field public data_type st.matter.data_types.Int16 the data type of this attribute + +local MaxMeasuredValue = { + ID = 0x0002, + NAME = "MaxMeasuredValue", + base_type = require "st.matter.data_types.Int16", +} + +--- Create a Int16 object of this attribute with any additional features provided for the attribute +--- This is also usable with the MaxMeasuredValue(...) syntax +--- +--- @vararg vararg the values needed to construct a Int16 +--- @return st.matter.data_types.Int16 +function MaxMeasuredValue:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +--- Constructs an st.matter.interaction_model.InteractionRequest to read +--- this attribute from a device +--- @param device st.matter.Device +--- @param endpoint_id number|nil +--- @return st.matter.interaction_model.InteractionRequest containing an Interaction Request +function MaxMeasuredValue:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + + +--- Reporting policy: MaxMeasuredValue => true => mandatory + +--- Sets up a Subscribe Interaction +--- +--- @param device any +--- @param endpoint_id number|nil +--- @return any +function MaxMeasuredValue:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function MaxMeasuredValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +--- Builds an MaxMeasuredValue test attribute reponse for the driver integration testing framework +--- +--- @param device st.matter.Device the device to build this message for +--- @param endpoint_id number|nil +--- @param value any +--- @param status string Interaction status associated with the path +--- @return st.matter.interaction_model.InteractionResponse of type REPORT_DATA +function MaxMeasuredValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MaxMeasuredValue:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(MaxMeasuredValue, {__call = MaxMeasuredValue.new_value, __index = MaxMeasuredValue.base_type}) +return MaxMeasuredValue + diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MaxScaledValue.lua b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MaxScaledValue.lua new file mode 100644 index 0000000000..56fe3132f9 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MaxScaledValue.lua @@ -0,0 +1,116 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +--- @class st.matter.clusters.PressureMeasurement.MaxScaledValue +--- @alias MaxScaledValue +--- +--- @field public ID number 0x0012 the ID of this attribute +--- @field public NAME string "MaxScaledValue" the name of this attribute +--- @field public data_type st.matter.data_types.Int16 the data type of this attribute + +local MaxScaledValue = { + ID = 0x0012, + NAME = "MaxScaledValue", + base_type = require "st.matter.data_types.Int16", +} + +--- Create a Int16 object of this attribute with any additional features provided for the attribute +--- This is also usable with the MaxScaledValue(...) syntax +--- +--- @vararg vararg the values needed to construct a Int16 +--- @return st.matter.data_types.Int16 +function MaxScaledValue:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +--- Constructs an st.matter.interaction_model.InteractionRequest to read +--- this attribute from a device +--- @param device st.matter.Device +--- @param endpoint_id number|nil +--- @return st.matter.interaction_model.InteractionRequest containing an Interaction Request +function MaxScaledValue:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + + +--- Reporting policy: MaxScaledValue => true => mandatory + +--- Sets up a Subscribe Interaction +--- +--- @param device any +--- @param endpoint_id number|nil +--- @return any +function MaxScaledValue:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function MaxScaledValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +--- Builds an MaxScaledValue test attribute reponse for the driver integration testing framework +--- +--- @param device st.matter.Device the device to build this message for +--- @param endpoint_id number|nil +--- @param value any +--- @param status string Interaction status associated with the path +--- @return st.matter.interaction_model.InteractionResponse of type REPORT_DATA +function MaxScaledValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MaxScaledValue:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(MaxScaledValue, {__call = MaxScaledValue.new_value, __index = MaxScaledValue.base_type}) +return MaxScaledValue + diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MeasuredValue.lua b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MeasuredValue.lua new file mode 100644 index 0000000000..7d850f1a7c --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MeasuredValue.lua @@ -0,0 +1,116 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +--- @class st.matter.clusters.PressureMeasurement.MeasuredValue +--- @alias MeasuredValue +--- +--- @field public ID number 0x0000 the ID of this attribute +--- @field public NAME string "MeasuredValue" the name of this attribute +--- @field public data_type st.matter.data_types.Int16 the data type of this attribute + +local MeasuredValue = { + ID = 0x0000, + NAME = "MeasuredValue", + base_type = require "st.matter.data_types.Int16", +} + +--- Create a Int16 object of this attribute with any additional features provided for the attribute +--- This is also usable with the MeasuredValue(...) syntax +--- +--- @vararg vararg the values needed to construct a Int16 +--- @return st.matter.data_types.Int16 +function MeasuredValue:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +--- Constructs an st.matter.interaction_model.InteractionRequest to read +--- this attribute from a device +--- @param device st.matter.Device +--- @param endpoint_id number|nil +--- @return st.matter.interaction_model.InteractionRequest containing an Interaction Request +function MeasuredValue:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + + +--- Reporting policy: MeasuredValue => true => suggested + +--- Sets up a Subscribe Interaction +--- +--- @param device any +--- @param endpoint_id number|nil +--- @return any +function MeasuredValue:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function MeasuredValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +--- Builds an MeasuredValue test attribute reponse for the driver integration testing framework +--- +--- @param device st.matter.Device the device to build this message for +--- @param endpoint_id number|nil +--- @param value any +--- @param status string Interaction status associated with the path +--- @return st.matter.interaction_model.InteractionResponse of type REPORT_DATA +function MeasuredValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MeasuredValue:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(MeasuredValue, {__call = MeasuredValue.new_value, __index = MeasuredValue.base_type}) +return MeasuredValue + diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MinMeasuredValue.lua b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MinMeasuredValue.lua new file mode 100644 index 0000000000..35ec62854f --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MinMeasuredValue.lua @@ -0,0 +1,116 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +--- @class st.matter.clusters.PressureMeasurement.MinMeasuredValue +--- @alias MinMeasuredValue +--- +--- @field public ID number 0x0001 the ID of this attribute +--- @field public NAME string "MinMeasuredValue" the name of this attribute +--- @field public data_type st.matter.data_types.Int16 the data type of this attribute + +local MinMeasuredValue = { + ID = 0x0001, + NAME = "MinMeasuredValue", + base_type = require "st.matter.data_types.Int16", +} + +--- Create a Int16 object of this attribute with any additional features provided for the attribute +--- This is also usable with the MinMeasuredValue(...) syntax +--- +--- @vararg vararg the values needed to construct a Int16 +--- @return st.matter.data_types.Int16 +function MinMeasuredValue:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +--- Constructs an st.matter.interaction_model.InteractionRequest to read +--- this attribute from a device +--- @param device st.matter.Device +--- @param endpoint_id number|nil +--- @return st.matter.interaction_model.InteractionRequest containing an Interaction Request +function MinMeasuredValue:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + + +--- Reporting policy: MinMeasuredValue => true => mandatory + +--- Sets up a Subscribe Interaction +--- +--- @param device any +--- @param endpoint_id number|nil +--- @return any +function MinMeasuredValue:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function MinMeasuredValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +--- Builds an MinMeasuredValue test attribute reponse for the driver integration testing framework +--- +--- @param device st.matter.Device the device to build this message for +--- @param endpoint_id number|nil +--- @param value any +--- @param status string Interaction status associated with the path +--- @return st.matter.interaction_model.InteractionResponse of type REPORT_DATA +function MinMeasuredValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MinMeasuredValue:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(MinMeasuredValue, {__call = MinMeasuredValue.new_value, __index = MinMeasuredValue.base_type}) +return MinMeasuredValue + diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MinScaledValue.lua b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MinScaledValue.lua new file mode 100644 index 0000000000..97bb0d44df --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/MinScaledValue.lua @@ -0,0 +1,116 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +--- @class st.matter.clusters.PressureMeasurement.MinScaledValue +--- @alias MinScaledValue +--- +--- @field public ID number 0x0011 the ID of this attribute +--- @field public NAME string "MinScaledValue" the name of this attribute +--- @field public data_type st.matter.data_types.Int16 the data type of this attribute + +local MinScaledValue = { + ID = 0x0011, + NAME = "MinScaledValue", + base_type = require "st.matter.data_types.Int16", +} + +--- Create a Int16 object of this attribute with any additional features provided for the attribute +--- This is also usable with the MinScaledValue(...) syntax +--- +--- @vararg vararg the values needed to construct a Int16 +--- @return st.matter.data_types.Int16 +function MinScaledValue:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +--- Constructs an st.matter.interaction_model.InteractionRequest to read +--- this attribute from a device +--- @param device st.matter.Device +--- @param endpoint_id number|nil +--- @return st.matter.interaction_model.InteractionRequest containing an Interaction Request +function MinScaledValue:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + + +--- Reporting policy: MinScaledValue => true => mandatory + +--- Sets up a Subscribe Interaction +--- +--- @param device any +--- @param endpoint_id number|nil +--- @return any +function MinScaledValue:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function MinScaledValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +--- Builds an MinScaledValue test attribute reponse for the driver integration testing framework +--- +--- @param device st.matter.Device the device to build this message for +--- @param endpoint_id number|nil +--- @param value any +--- @param status string Interaction status associated with the path +--- @return st.matter.interaction_model.InteractionResponse of type REPORT_DATA +function MinScaledValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function MinScaledValue:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(MinScaledValue, {__call = MinScaledValue.new_value, __index = MinScaledValue.base_type}) +return MinScaledValue + diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/Scale.lua b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/Scale.lua new file mode 100644 index 0000000000..5533e4e486 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/Scale.lua @@ -0,0 +1,116 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +--- @class st.matter.clusters.PressureMeasurement.Scale +--- @alias Scale +--- +--- @field public ID number 0x0014 the ID of this attribute +--- @field public NAME string "Scale" the name of this attribute +--- @field public data_type st.matter.data_types.Int8 the data type of this attribute + +local Scale = { + ID = 0x0014, + NAME = "Scale", + base_type = require "st.matter.data_types.Int8", +} + +--- Create a Int8 object of this attribute with any additional features provided for the attribute +--- This is also usable with the Scale(...) syntax +--- +--- @vararg vararg the values needed to construct a Int8 +--- @return st.matter.data_types.Int8 +function Scale:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +--- Constructs an st.matter.interaction_model.InteractionRequest to read +--- this attribute from a device +--- @param device st.matter.Device +--- @param endpoint_id number|nil +--- @return st.matter.interaction_model.InteractionRequest containing an Interaction Request +function Scale:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + + +--- Reporting policy: Scale => true => mandatory + +--- Sets up a Subscribe Interaction +--- +--- @param device any +--- @param endpoint_id number|nil +--- @return any +function Scale:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function Scale:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +--- Builds an Scale test attribute reponse for the driver integration testing framework +--- +--- @param device st.matter.Device the device to build this message for +--- @param endpoint_id number|nil +--- @param value any +--- @param status string Interaction status associated with the path +--- @return st.matter.interaction_model.InteractionResponse of type REPORT_DATA +function Scale:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function Scale:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(Scale, {__call = Scale.new_value, __index = Scale.base_type}) +return Scale + diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/ScaledTolerance.lua b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/ScaledTolerance.lua new file mode 100644 index 0000000000..d00d2a4195 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/ScaledTolerance.lua @@ -0,0 +1,116 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +--- @class st.matter.clusters.PressureMeasurement.ScaledTolerance +--- @alias ScaledTolerance +--- +--- @field public ID number 0x0013 the ID of this attribute +--- @field public NAME string "ScaledTolerance" the name of this attribute +--- @field public data_type st.matter.data_types.Uint16 the data type of this attribute + +local ScaledTolerance = { + ID = 0x0013, + NAME = "ScaledTolerance", + base_type = require "st.matter.data_types.Uint16", +} + +--- Create a Uint16 object of this attribute with any additional features provided for the attribute +--- This is also usable with the ScaledTolerance(...) syntax +--- +--- @vararg vararg the values needed to construct a Uint16 +--- @return st.matter.data_types.Uint16 +function ScaledTolerance:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +--- Constructs an st.matter.interaction_model.InteractionRequest to read +--- this attribute from a device +--- @param device st.matter.Device +--- @param endpoint_id number|nil +--- @return st.matter.interaction_model.InteractionRequest containing an Interaction Request +function ScaledTolerance:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + + +--- Reporting policy: ScaledTolerance => true => suggested + +--- Sets up a Subscribe Interaction +--- +--- @param device any +--- @param endpoint_id number|nil +--- @return any +function ScaledTolerance:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function ScaledTolerance:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +--- Builds an ScaledTolerance test attribute reponse for the driver integration testing framework +--- +--- @param device st.matter.Device the device to build this message for +--- @param endpoint_id number|nil +--- @param value any +--- @param status string Interaction status associated with the path +--- @return st.matter.interaction_model.InteractionResponse of type REPORT_DATA +function ScaledTolerance:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function ScaledTolerance:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(ScaledTolerance, {__call = ScaledTolerance.new_value, __index = ScaledTolerance.base_type}) +return ScaledTolerance + diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/ScaledValue.lua b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/ScaledValue.lua new file mode 100644 index 0000000000..f2e513477b --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/ScaledValue.lua @@ -0,0 +1,116 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +--- @class st.matter.clusters.PressureMeasurement.ScaledValue +--- @alias ScaledValue +--- +--- @field public ID number 0x0010 the ID of this attribute +--- @field public NAME string "ScaledValue" the name of this attribute +--- @field public data_type st.matter.data_types.Int16 the data type of this attribute + +local ScaledValue = { + ID = 0x0010, + NAME = "ScaledValue", + base_type = require "st.matter.data_types.Int16", +} + +--- Create a Int16 object of this attribute with any additional features provided for the attribute +--- This is also usable with the ScaledValue(...) syntax +--- +--- @vararg vararg the values needed to construct a Int16 +--- @return st.matter.data_types.Int16 +function ScaledValue:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +--- Constructs an st.matter.interaction_model.InteractionRequest to read +--- this attribute from a device +--- @param device st.matter.Device +--- @param endpoint_id number|nil +--- @return st.matter.interaction_model.InteractionRequest containing an Interaction Request +function ScaledValue:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + + +--- Reporting policy: ScaledValue => true => mandatory + +--- Sets up a Subscribe Interaction +--- +--- @param device any +--- @param endpoint_id number|nil +--- @return any +function ScaledValue:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function ScaledValue:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +--- Builds an ScaledValue test attribute reponse for the driver integration testing framework +--- +--- @param device st.matter.Device the device to build this message for +--- @param endpoint_id number|nil +--- @param value any +--- @param status string Interaction status associated with the path +--- @return st.matter.interaction_model.InteractionResponse of type REPORT_DATA +function ScaledValue:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function ScaledValue:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(ScaledValue, {__call = ScaledValue.new_value, __index = ScaledValue.base_type}) +return ScaledValue + diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/Tolerance.lua b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/Tolerance.lua new file mode 100644 index 0000000000..55a39514a3 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/Tolerance.lua @@ -0,0 +1,116 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. + +local cluster_base = require "st.matter.cluster_base" +local data_types = require "st.matter.data_types" +local TLVParser = require "st.matter.TLV.TLVParser" + +--- @class st.matter.clusters.PressureMeasurement.Tolerance +--- @alias Tolerance +--- +--- @field public ID number 0x0003 the ID of this attribute +--- @field public NAME string "Tolerance" the name of this attribute +--- @field public data_type st.matter.data_types.Uint16 the data type of this attribute + +local Tolerance = { + ID = 0x0003, + NAME = "Tolerance", + base_type = require "st.matter.data_types.Uint16", +} + +--- Create a Uint16 object of this attribute with any additional features provided for the attribute +--- This is also usable with the Tolerance(...) syntax +--- +--- @vararg vararg the values needed to construct a Uint16 +--- @return st.matter.data_types.Uint16 +function Tolerance:new_value(...) + local o = self.base_type(table.unpack({...})) + + return o +end + +--- Constructs an st.matter.interaction_model.InteractionRequest to read +--- this attribute from a device +--- @param device st.matter.Device +--- @param endpoint_id number|nil +--- @return st.matter.interaction_model.InteractionRequest containing an Interaction Request +function Tolerance:read(device, endpoint_id) + return cluster_base.read( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + + +--- Reporting policy: Tolerance => true => mandatory + +--- Sets up a Subscribe Interaction +--- +--- @param device any +--- @param endpoint_id number|nil +--- @return any +function Tolerance:subscribe(device, endpoint_id) + return cluster_base.subscribe( + device, + endpoint_id, + self._cluster.ID, + self.ID, + nil --event_id + ) +end + +function Tolerance:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +--- Builds an Tolerance test attribute reponse for the driver integration testing framework +--- +--- @param device st.matter.Device the device to build this message for +--- @param endpoint_id number|nil +--- @param value any +--- @param status string Interaction status associated with the path +--- @return st.matter.interaction_model.InteractionResponse of type REPORT_DATA +function Tolerance:build_test_report_data( + device, + endpoint_id, + value, + status +) + local data = data_types.validate_or_build_type(value, self.base_type) + + return cluster_base.build_test_report_data( + device, + endpoint_id, + self._cluster.ID, + self.ID, + data, + status + ) +end + +function Tolerance:deserialize(tlv_buf) + local data = TLVParser.decode_tlv(tlv_buf) + + return data +end + +setmetatable(Tolerance, {__call = Tolerance.new_value, __index = Tolerance.base_type}) +return Tolerance + diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/init.lua b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/init.lua new file mode 100644 index 0000000000..64ecbdf709 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/attributes/init.lua @@ -0,0 +1,59 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. + +local attr_mt = {} +attr_mt.__attr_cache = {} +attr_mt.__index = function(self, key) + if attr_mt.__attr_cache[key] == nil then + local req_loc = string.format("PressureMeasurement.server.attributes.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + raw_def:set_parent_cluster(cluster) + attr_mt.__attr_cache[key] = raw_def + end + return attr_mt.__attr_cache[key] +end + +--- @class PressureMeasurementServerAttributes +--- +--- @field public MeasuredValue PressureMeasurement.server.attributes.MeasuredValue +--- @field public MinMeasuredValue PressureMeasurement.server.attributes.MinMeasuredValue +--- @field public MaxMeasuredValue PressureMeasurement.server.attributes.MaxMeasuredValue +--- @field public Tolerance PressureMeasurement.server.attributes.Tolerance +--- @field public ScaledValue PressureMeasurement.server.attributes.ScaledValue +--- @field public MinScaledValue PressureMeasurement.server.attributes.MinScaledValue +--- @field public MaxScaledValue PressureMeasurement.server.attributes.MaxScaledValue +--- @field public ScaledTolerance PressureMeasurement.server.attributes.ScaledTolerance +--- @field public Scale PressureMeasurement.server.attributes.Scale +--- @field public AcceptedCommandList PressureMeasurement.server.attributes.AcceptedCommandList +--- @field public EventList PressureMeasurement.server.attributes.EventList +--- @field public AttributeList PressureMeasurement.server.attributes.AttributeList +local PressureMeasurementServerAttributes = {} + +function PressureMeasurementServerAttributes:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(PressureMeasurementServerAttributes, attr_mt) + +local has_aliases, aliases = pcall(require, "st.matter.clusters.aliases.PressureMeasurement.server.attributes") +if has_aliases then + aliases:add_to_class(PressureMeasurementServerAttributes) +end + +return PressureMeasurementServerAttributes + diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/commands/init.lua b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/commands/init.lua new file mode 100644 index 0000000000..3bb136bcb5 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/commands/init.lua @@ -0,0 +1,41 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. + +local command_mt = {} +command_mt.__command_cache = {} +command_mt.__index = function(self, key) + if command_mt.__command_cache[key] == nil then + local req_loc = string.format("PressureMeasurement.server.commands.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + command_mt.__command_cache[key] = raw_def:set_parent_cluster(cluster) + end + return command_mt.__command_cache[key] +end + +--- @class PressureMeasurementServerCommands +--- +local PressureMeasurementServerCommands = {} + +function PressureMeasurementServerCommands:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(PressureMeasurementServerCommands, command_mt) + +return PressureMeasurementServerCommands + diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/events/init.lua b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/events/init.lua new file mode 100644 index 0000000000..c6fec3b6b5 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/server/events/init.lua @@ -0,0 +1,42 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. + +local event_mt = {} +event_mt.__event_cache = {} +event_mt.__index = function(self, key) + if event_mt.__event_cache[key] == nil then + local req_loc = string.format("PressureMeasurement.server.events.%s", key) + local raw_def = require(req_loc) + local cluster = rawget(self, "_cluster") + raw_def:set_parent_cluster(cluster) + event_mt.__event_cache[key] = raw_def + end + return event_mt.__event_cache[key] +end + +--- @class PressureMeasurementEvents +--- +local PressureMeasurementEvents = {} + +function PressureMeasurementEvents:set_parent_cluster(cluster) + self._cluster = cluster + return self +end + +setmetatable(PressureMeasurementEvents, event_mt) + +return PressureMeasurementEvents + diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/types/Feature.lua b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/types/Feature.lua new file mode 100644 index 0000000000..5f5f070b7d --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/types/Feature.lua @@ -0,0 +1,81 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. + +local data_types = require "st.matter.data_types" +local UintABC = require "st.matter.data_types.base_defs.UintABC" + +--- @class st.matter.clusters.PressureMeasurement.types.Feature +--- @alias Feature +--- +--- @field public EXTENDED number 1 + +local Feature = {} +local new_mt = UintABC.new_mt({NAME = "Feature", ID = data_types.name_to_id_map["Uint32"]}, 4) + +Feature.BASE_MASK = 0xFFFF +Feature.EXTENDED = 0x0001 + +Feature.mask_fields = { + BASE_MASK = 0xFFFF, + EXTENDED = 0x0001, +} + +--- @function Feature:is_extended_set +--- @return boolean True if the value of EXTENDED is non-zero +Feature.is_extended_set = function(self) + return (self.value & self.EXTENDED) ~= 0 +end + +--- @function Feature:set_extended +--- Set the value of the bit in the EXTENDED field to 1 +Feature.set_extended = function(self) + if self.value ~= nil then + self.value = self.value | self.EXTENDED + else + self.value = self.EXTENDED + end +end + +--- @function Feature:unset_extended +--- Set the value of the bits in the EXTENDED field to 0 +Feature.unset_extended = function(self) + self.value = self.value & (~self.EXTENDED & self.BASE_MASK) +end + +function Feature.bits_are_valid(feature) + local max = + Feature.EXTENDED + if (feature <= max) and (feature >= 1) then + return true + else + return false + end +end + +Feature.mask_methods = { + is_extended_set = Feature.is_extended_set, + set_extended = Feature.set_extended, + unset_extended = Feature.unset_extended, +} + +Feature.augment_type = function(cls, val) + setmetatable(val, new_mt) +end + +setmetatable(Feature, new_mt) + +return Feature + diff --git a/drivers/SmartThings/matter-sensor/src/PressureMeasurement/types/init.lua b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/types/init.lua new file mode 100644 index 0000000000..682c6db041 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/PressureMeasurement/types/init.lua @@ -0,0 +1,35 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- DO NOT EDIT: this code is automatically generated by ZCL Advanced Platform generator. + +local types_mt = {} +types_mt.__types_cache = {} +types_mt.__index = function(self, key) + if types_mt.__types_cache[key] == nil then + types_mt.__types_cache[key] = require("PressureMeasurement.types." .. key) + end + return types_mt.__types_cache[key] +end + +--- @class PressureMeasurementTypes +--- + +--- @field public Feature PressureMeasurement.types.Feature +local PressureMeasurementTypes = {} + +setmetatable(PressureMeasurementTypes, types_mt) + +return PressureMeasurementTypes + diff --git a/drivers/SmartThings/matter-sensor/src/init.lua b/drivers/SmartThings/matter-sensor/src/init.lua index 443afc7538..bafffdf578 100644 --- a/drivers/SmartThings/matter-sensor/src/init.lua +++ b/drivers/SmartThings/matter-sensor/src/init.lua @@ -18,6 +18,12 @@ local clusters = require "st.matter.clusters" local MatterDriver = require "st.matter.driver" local utils = require "st.utils" +-- This can be removed once LuaLibs supports the PressureMeasurement cluster +if not pcall(function(cluster) return clusters[cluster] end, + "PressureMeasurement") then + clusters.PressureMeasurement = require "PressureMeasurement" +end + local BATTERY_CHECKED = "__battery_checked" local HUE_MANUFACTURER_ID = 0x100B @@ -55,6 +61,10 @@ local function check_for_battery(device) profile_name = profile_name .. "-humidity" end + if device:supports_capability(capabilities.atmosphericPressureMeasurement) then + profile_name = profile_name .. "-pressure" + end + if supports_battery_percentage_remaining(device) then profile_name = profile_name .. "-battery" end @@ -86,15 +96,20 @@ local function illuminance_attr_handler(driver, device, ib, response) end local function temperature_attr_handler(driver, device, ib, response) - local temp = ib.data.value / 100.0 - local unit = "C" - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.temperatureMeasurement.temperature({value = temp, unit = unit})) + local measured_value = ib.data.value + if measured_value ~= nil then + local temp = measured_value / 100.0 + local unit = "C" + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.temperatureMeasurement.temperature({value = temp, unit = unit})) + end end local function humidity_attr_handler(driver, device, ib, response) - - local humidity = utils.round(ib.data.value / 100.0) - device:emit_event_for_endpoint(ib.endpoint_id, capabilities.relativeHumidityMeasurement.humidity(humidity)) + local measured_value = ib.data.value + if measured_value ~= nil then + local humidity = utils.round(measured_value / 100.0) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.relativeHumidityMeasurement.humidity(humidity)) + end end local function boolean_attr_handler(driver, device, ib, response) @@ -115,6 +130,15 @@ local function occupancy_attr_handler(driver, device, ib, response) device:emit_event(ib.data.value == 0x01 and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) end +local function pressure_attr_handler(driver, device, ib, response) + local measured_value = ib.data.value + if measured_value ~= nil then + local kPa = utils.round(measured_value / 10.0) + local unit = "kPa" + device:emit_event(capabilities.atmosphericPressureMeasurement.atmosphericPressure({value = kPa, unit = unit})) + end +end + local matter_driver_template = { lifecycle_handlers = { init = device_init, @@ -140,6 +164,9 @@ local matter_driver_template = { [clusters.OccupancySensing.ID] = { [clusters.OccupancySensing.attributes.Occupancy.ID] = occupancy_attr_handler, }, + [clusters.PressureMeasurement.ID] = { + [clusters.PressureMeasurement.attributes.MeasuredValue.ID] = pressure_attr_handler, + }, } }, -- TODO Once capabilities all have default handlers move this info there, and @@ -162,7 +189,10 @@ local matter_driver_template = { }, [capabilities.battery.ID] = { clusters.PowerSource.attributes.BatPercentRemaining - } + }, + [capabilities.atmosphericPressureMeasurement.ID] = { + clusters.PressureMeasurement.attributes.MeasuredValue + }, }, capability_handlers = { }, @@ -173,6 +203,7 @@ local matter_driver_template = { capabilities.battery, capabilities.relativeHumidityMeasurement, capabilities.illuminanceMeasurement, + capabilities.atmosphericPressureMeasurement, }, } diff --git a/drivers/SmartThings/matter-sensor/src/test/test_matter_pressure_sensor.lua b/drivers/SmartThings/matter-sensor/src/test/test_matter_pressure_sensor.lua new file mode 100644 index 0000000000..9c439301b0 --- /dev/null +++ b/drivers/SmartThings/matter-sensor/src/test/test_matter_pressure_sensor.lua @@ -0,0 +1,156 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +local test = require "integration_test" +local capabilities = require "st.capabilities" + +local t_utils = require "integration_test.utils" +local clusters = require "st.matter.clusters" +local PressureMeasurementCluster = require "PressureMeasurement" + +--Note all endpoints are being mapped to the main component +-- in the matter-sensor driver. If any devices require invoke/write +-- requests to support the capabilities/preferences, custom mappings +-- will need to be setup. +local matter_endpoints = { + { + endpoint_id = 0, + clusters = { + {cluster_id = clusters.Basic.ID, cluster_type = "SERVER"}, + }, + device_types = { + device_type_id = 0x0016, device_type_revision = 1, -- RootNode + } + }, + { + endpoint_id = 1, + clusters = { + {cluster_id = PressureMeasurementCluster.ID, cluster_type = "SERVER"}, + {cluster_id = clusters.PowerSource.ID, cluster_type = "SERVER"}, + } + } +} + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("pressure-battery.yml"), + endpoints = matter_endpoints +}) + +local function subscribe_on_init(dev) + local subscribe_request = PressureMeasurementCluster.attributes.MeasuredValue:subscribe(mock_device) + subscribe_request:merge(clusters.PowerSource.attributes.BatPercentRemaining:subscribe(mock_device)) + return subscribe_request +end + +local function test_init() + test.socket.matter:__expect_send({mock_device.id, subscribe_on_init(mock_device)}) + test.mock_device.add_test_device(mock_device) + -- don't check the battery for this device since we are just testing the "pressure-battery" profile specifically + mock_device:set_field("__battery_checked", 1, {persist = true}) +end +test.set_test_init_function(test_init) + +test.register_message_test( + "Pressure measurement reports should generate correct messages", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + PressureMeasurementCluster.server.attributes.MeasuredValue:build_test_report_data(mock_device, 1, 1054) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.atmosphericPressureMeasurement.atmosphericPressure({ value = 105, unit = "kPa" })) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + PressureMeasurementCluster.server.attributes.MeasuredValue:build_test_report_data(mock_device, 1, 1055) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.atmosphericPressureMeasurement.atmosphericPressure({ value = 106, unit = "kPa" })) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + PressureMeasurementCluster.server.attributes.MeasuredValue:build_test_report_data(mock_device, 1, 0) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.atmosphericPressureMeasurement.atmosphericPressure({ value = 0, unit = "kPa" })) + } + } +) + +test.register_message_test( + "Battery percent reports should generate correct messages", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.PowerSource.attributes.BatPercentRemaining:build_test_report_data(mock_device, 1, 150) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.battery.battery(math.floor(150 / 2.0 + 0.5))) + } + } +) + + +local function refresh_commands(dev) + local req = clusters.PowerSource.attributes.BatPercentRemaining:read(dev) + req:merge(PressureMeasurementCluster.attributes.MeasuredValue:read(dev)) + return req +end + +test.register_message_test( + "Handle received refresh.", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "refresh", component = "main", command = "refresh", args = { } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + refresh_commands(mock_device) + } + }, + } +) +test.run_registered_tests() From e8cfc2bdea0a090d697b0891faf8df049ce76652 Mon Sep 17 00:00:00 2001 From: Doug Stephen Date: Tue, 5 Mar 2024 11:10:19 -0600 Subject: [PATCH 05/21] fix(hue): Address various nil field index issues The majority of these fixes are concurrency races: Due to Hue's heavy usage of additional Cosock coroutines, it can be the case that functions that have been passed references to device records will be operating on a device record for a deleted device. This can cause unexpected nil index errors and crashes. We attempt to minimize that here with heuristics; in particular, a device that has been deleted will return `nil` for *any* of its table keys. So we check a field that we *know* a device record should have, its `.id` field. We wrap all handlers in a closure that does a sanity check on the status of the device record using this heuristic, and we wrap the handler itself in a pcall. We also address some one-off locations where nil field indexes can occur for other reasons, and introduce some additional defensiveness around other places where we've observed nil index crashes in crash metrics. --- drivers/SmartThings/philips-hue/src/disco.lua | 2 +- .../SmartThings/philips-hue/src/hue/api.lua | 19 ++- drivers/SmartThings/philips-hue/src/init.lua | 142 +++++++++++------- 3 files changed, 105 insertions(+), 58 deletions(-) diff --git a/drivers/SmartThings/philips-hue/src/disco.lua b/drivers/SmartThings/philips-hue/src/disco.lua index 99c9a87519..a433e77c6e 100644 --- a/drivers/SmartThings/philips-hue/src/disco.lua +++ b/drivers/SmartThings/philips-hue/src/disco.lua @@ -262,7 +262,7 @@ function HueDiscovery.search_bridge_for_supported_devices(driver, bridge_id, api local parent_device_id = device.parent_device_id or device:get_field(Fields.PARENT_DEVICE_ID) or "" local parent_bridge_device = driver:get_device_info(parent_device_id) local is_child_of_bridge = parent_bridge_device and (parent_bridge_device:get_field(Fields.BRIDGE_ID) == bridge_id) - if parent_bridge_device and is_child_of_bridge and not not_known_to_bridge then + if parent_bridge_device and is_child_of_bridge and not not_known_to_bridge and device.id then device.log.info(string.format("Device is no longer joined to Hue Bridge %q, deleting", parent_bridge_device.label)) driver:do_hue_light_delete(device) end diff --git a/drivers/SmartThings/philips-hue/src/hue/api.lua b/drivers/SmartThings/philips-hue/src/hue/api.lua index 4116715879..6a8d2ef153 100644 --- a/drivers/SmartThings/philips-hue/src/hue/api.lua +++ b/drivers/SmartThings/philips-hue/src/hue/api.lua @@ -29,9 +29,20 @@ local ControlMessageBuilders = { end } +local function try_send(instance, message) + if not instance._ctrl_tx then + log.error(st_utils.stringify_table(message, "Couldn't send the followings due to closed transmit channel", false)) + end + + local success, err = pcall(instance._ctrl_tx.send, instance._ctrl_tx, message) + if not success then + log.error(string.format("Failed to transmit Hue Control Message: %s", err)) + end +end + local function do_shutdown(instance) if instance._running then - instance._ctrl_tx:send(ControlMessageBuilders.Shutdown()) + try_send(instance, ControlMessageBuilders.Shutdown()) instance._running = false end end @@ -178,7 +189,7 @@ end function PhilipsHueApi:update_connection(hub_base_url, api_key) local msg = ControlMessageBuilders.Update(hub_base_url, api_key) - self._ctrl_tx:send(msg) + try_send(self, msg) end ---@return table|nil response REST response, nil if error @@ -187,7 +198,7 @@ local function do_get(instance, path) local reply_tx, reply_rx = channel.new() reply_rx:settimeout(10) local msg = ControlMessageBuilders.Get(path, reply_tx); - instance._ctrl_tx:send(msg) + try_send(instance, msg) local recv, err = reply_rx:receive() if err ~= nil then instance.client:close_socket() @@ -202,7 +213,7 @@ local function do_put(instance, path, payload) local reply_tx, reply_rx = channel.new() reply_rx:settimeout(10) local msg = ControlMessageBuilders.Put(path, payload, reply_tx); - instance._ctrl_tx:send(msg) + try_send(instance, msg) local recv, err = reply_rx:receive() if err ~= nil then instance.client:close_socket() diff --git a/drivers/SmartThings/philips-hue/src/init.lua b/drivers/SmartThings/philips-hue/src/init.lua index 9be833cd0e..b8cdf5cb4b 100644 --- a/drivers/SmartThings/philips-hue/src/init.lua +++ b/drivers/SmartThings/philips-hue/src/init.lua @@ -51,6 +51,27 @@ local DEFAULT_MIREK = 153 -- "forward declare" some functions local _initialize, bridge_added, light_added, init_light, init_bridge +local function safe_wrap_handler(handler) + return function(driver, device, ...) + if device == nil or (device and device.id == nil) then + log.warn("Tried to handle capability command for device that has been deleted") + return + end + local success, result = pcall(handler, driver, device, ...) + if not success then + log.error_with({ hub_logs = true }, string.format("Failed to invoke capability command handler. Reason: %s", result)) + end + return result + end +end + +local refresh_handler = safe_wrap_handler(handlers.refresh_handler) +local switch_on_handler = safe_wrap_handler(handlers.switch_on_handler) +local switch_off_handler = safe_wrap_handler(handlers.switch_off_handler) +local switch_level_handler = safe_wrap_handler(handlers.switch_level_handler) +local set_color_handler = safe_wrap_handler(handlers.set_color_handler) +local set_color_temp_handler = safe_wrap_handler(handlers.set_color_temp_handler) + ---@param light_device HueChildDevice ---@param light table local function emit_light_status_events(light_device, light) @@ -260,7 +281,8 @@ local function spawn_bridge_add_api_key_task(driver, device) repeat local time_remaining = math.max(0, timeout_time - cosock.socket.gettime()) if time_remaining == 0 then - device.log.error_with({ hub_logs = true }, + local _log = device.log or log + _log.error_with({ hub_logs = true }, string.format( "Link button not pressed or API key not received for bridge \"%s\" after 30 seconds, sleeping then trying again in a few minutes.", device.label @@ -694,7 +716,7 @@ light_added = function(driver, device, parent_device_id, resource_id) driver.light_id_to_device[device_light_resource_id] = device -- the refresh handler adds lights that don't have a fully initialized bridge to a queue. - handlers.refresh_handler(driver, device) + refresh_handler(driver, device) end local function do_bridge_network_init(driver, bridge_device, bridge_url, api_key) @@ -726,7 +748,8 @@ local function do_bridge_network_init(driver, bridge_device, bridge_url, api_key end local child_device_map = {} local children = bridge_device:get_child_list() - bridge_device.log.debug(string.format("Scanning connectivity of %s child devices", #children)) + local _log = bridge_device.log or log + _log.debug(string.format("Scanning connectivity of %s child devices", #children)) for _, device_record in ipairs(children) do local hue_device_id = device_record:get_field(Fields.HUE_DEVICE_ID) if hue_device_id ~= nil then @@ -762,24 +785,28 @@ local function do_bridge_network_init(driver, bridge_device, bridge_url, api_key if connectivity_status.data and #connectivity_status.data > 0 then scanned = true - end - - for _, status in ipairs(connectivity_status.data) do - local hue_device_id = (status.owner and status.owner.rid) or "" - log.trace(string.format("Checking connectivity status for device resource id %s", hue_device_id)) - local child_device = child_device_map[hue_device_id] - if child_device then - if status.status == "connected" then - child_device.log.info_with({hub_logs=true}, "Marking Online after SSE Reconnect") - child_device:online() - child_device:set_field(Fields.IS_ONLINE, true) - elseif status.status == "connectivity_issue" then - child_device.log.info_with({hub_logs=true}, "Marking Offline after SSE Reconnect") - child_device:set_field(Fields.IS_ONLINE, false) - child_device:offline() + for _, status in ipairs(connectivity_status.data) do + local hue_device_id = (status.owner and status.owner.rid) or "" + log.trace(string.format("Checking connectivity status for device resource id %s", hue_device_id)) + local child_device = child_device_map[hue_device_id] + if child_device then + if not child_device.id then + child_device_map[hue_device_id] = nil + else + if status.status == "connected" then + child_device.log.info_with({hub_logs=true}, "Marking Online after SSE Reconnect") + child_device:online() + child_device:set_field(Fields.IS_ONLINE, true) + elseif status.status == "connectivity_issue" then + child_device.log.info_with({hub_logs=true}, "Marking Offline after SSE Reconnect") + child_device:set_field(Fields.IS_ONLINE, false) + child_device:offline() + end + end end end end + ::continue:: end end, string.format("Hue Bridge %s Zigbee Scan Task", bridge_device.label)) @@ -825,7 +852,7 @@ local function do_bridge_network_init(driver, bridge_device, bridge_url, api_key light_resource_id = driver.device_rid_to_light_rid[update_data.owner.rid] end local light_device = driver.light_id_to_device[light_resource_id] - if light_device ~= nil then + if light_device ~= nil and light_device.id ~= nil then driver.emit_light_status_events(light_device, update_data) end end @@ -834,7 +861,7 @@ local function do_bridge_network_init(driver, bridge_device, bridge_url, api_key if delete_data.type == "light" then local light_resource_id = delete_data.id local light_device = driver.light_id_to_device[light_resource_id] - if light_device ~= nil then + if light_device ~= nil and light_device.id ~= nil then log.info( string.format( "Light device \"%s\" was deleted from hue bridge %s", @@ -860,7 +887,8 @@ local function do_bridge_network_init(driver, bridge_device, bridge_url, api_key cosock.spawn(function() local hue_api = bridge_device:get_field(Fields.BRIDGE_API) if hue_api == nil then - bridge_device.log.warn("No Hue API instance available for new light event.") + local _log = bridge_device.log or log + _log.warn("No Hue API instance available for new light event.") return end @@ -978,7 +1006,7 @@ local function do_bridge_network_init(driver, bridge_device, bridge_url, api_key local bridge_id = light_device.parent_device_id or bridge_device:get_field(Fields.PARENT_DEVICE_ID) if bridge_id == bridge_device.id then table.insert(ids_to_remove, id) - handlers.refresh_handler(driver, light_device) + refresh_handler(driver, light_device) end end for _, id in ipairs(ids_to_remove) do @@ -1110,7 +1138,7 @@ init_light = function(driver, device) end device:set_field(Fields._INIT, true, { persist = false }) if device:get_field(Fields._REFRESH_AFTER_INIT) then - handlers.refresh_handler(driver, device) + refresh_handler(driver, device) device:set_field(Fields._REFRESH_AFTER_INIT, false, { persist = true }) end end @@ -1185,6 +1213,10 @@ _initialize = function(driver, device, event, args, parent_device_id) end end +local disco = Discovery.discover +local added = safe_wrap_handler(_initialize) +local init = safe_wrap_handler(_initialize) + local stray_bulb_tx, stray_bulb_rx = cosock.channel.new() stray_bulb_rx:settimeout(30) @@ -1291,23 +1323,25 @@ cosock.spawn(function() return end - for _, light in ipairs(light_resource.data or {}) do - local light_resource_description = { - hue_provided_name = device_data.metadata.name, - id = light.id, - on = light.on, - color = light.color, - dimming = light.dimming, - color_temperature = light.color_temperature, - mode = light.mode, - parent_device_id = bridge_device_uuid, - hue_device_id = light.owner.rid, - hue_device_data = device_data - } - if not Discovery.light_state_disco_cache[light.id] then - log.info(string.format("Caching previously unknown light service description for %s", - device_data.metadata.name)) - Discovery.light_state_disco_cache[light.id] = light_resource_description + if light_resource.data and #light_resource.data > 0 then + for _, light in ipairs(light_resource.data) do + local light_resource_description = { + hue_provided_name = device_data.metadata.name, + id = light.id, + on = light.on, + color = light.color, + dimming = light.dimming, + color_temperature = light.color_temperature, + mode = light.mode, + parent_device_id = bridge_device_uuid, + hue_device_id = light.owner.rid, + hue_device_data = device_data + } + if not Discovery.light_state_disco_cache[light.id] then + log.info(string.format("Caching previously unknown light service description for %s", + device_data.metadata.name)) + Discovery.light_state_disco_cache[light.id] = light_resource_description + end end end @@ -1401,15 +1435,6 @@ cosock.spawn(function() end end, "Stray Hue Bulb Resolution Task") -local disco = Discovery.discover -local added = _initialize -local init = _initialize - -local refresh_handler = handlers.refresh_handler -local switch_on_handler = handlers.switch_on_handler -local switch_off_handler = handlers.switch_off_handler -local switch_level_handler = handlers.switch_level_handler -local set_color_temp_handler = handlers.set_color_temp_handler local function remove(driver, device) driver.datastore.dni_to_device_id[device.device_network_id] = nil @@ -1483,7 +1508,7 @@ local hue = Driver("hue", [capabilities.switchLevel.commands.setLevel.NAME] = switch_level_handler, }, [capabilities.colorControl.ID] = { - [capabilities.colorControl.commands.setColor.NAME] = handlers.set_color_handler, + [capabilities.colorControl.commands.setColor.NAME] = set_color_handler, }, [capabilities.colorTemperature.ID] = { [capabilities.colorTemperature.commands.setColorTemperature.NAME] = set_color_temp_handler, @@ -1499,10 +1524,21 @@ local hue = Driver("hue", api_key_to_bridge_id = {}, stray_bulb_tx = stray_bulb_tx, _lights_pending_refresh = {}, - emit_light_status_events = emit_light_status_events, + emit_light_status_events = function(light_device, light_table) + if light_device == nil or (light_device and light_device.id == nil) then + log.warn("Tried to emit light status event for device that has been deleted") + return + end + local success, result = pcall(emit_light_status_events, light_device, light_table) + if not success then + log.error_with({ hub_logs = true }, string.format("Failed to invoke emit light status handler. Reason: %s", result)) + end + return result + end, do_hue_light_delete = function(driver, device) if type(driver.try_delete_device) ~= "function" then - device.log.warn("Requesting device delete on API version that doesn't support it. Marking device offline.") + local _log = device.log or log + _log.warn("Requesting device delete on API version that doesn't support it. Marking device offline.") device:offline() return end @@ -1575,7 +1611,7 @@ end Discovery.api_keys = setmetatable({}, { __newindex = function (self, k, v) assert( - type(v) == "string", + type(v) == "string" or type(v) == "nil", string.format("Attempted to store value of type %s in application_key table which expects \"string\" types", type(v) ) From a15b7ef82f4476c333c1daaf221b75b0139545ec Mon Sep 17 00:00:00 2001 From: lelandblue <79465613+lelandblue@users.noreply.github.com> Date: Tue, 5 Mar 2024 19:41:18 -0500 Subject: [PATCH 06/21] new device (Matter Window Covering): WWST Cert request to add the Zemismart MT25B, MT82, MT25A, ZM25M (#1121) * new device/ Zemismart MT25B and MT82 * Adding two new fingerprints for the MT25A and ZM25M Devices * Change the MT25B profile to be window-covering Change the MT25B profile to be window-covering, since we do not have confirmation from the partner that this supports battery. If it does, then the driver should still accommodate for this --- .../matter-window-covering/fingerprints.yml | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/matter-window-covering/fingerprints.yml b/drivers/SmartThings/matter-window-covering/fingerprints.yml index 2133928f2c..110bb07338 100644 --- a/drivers/SmartThings/matter-window-covering/fingerprints.yml +++ b/drivers/SmartThings/matter-window-covering/fingerprints.yml @@ -5,7 +5,27 @@ matterManufacturer: vendorId: 0x130A productId: 0x55 deviceProfileName: window-covering-battery - +#Zemismart + - id: "5020/65376" + deviceLabel: Zemismart MT25B Roller Motor + vendorId: 0x139C + productId: 0xFF60 + deviceProfileName: window-covering + - id: "5020/65296" + deviceLabel: Zemismart MT82 Smart Curtain + vendorId: 0x139C + productId: 0xFF10 + deviceProfileName: window-covering + - id: "5020/65301" + deviceLabel: Zemismart MT25A Thread Roller Motor + vendorId: 0x139C + productId: 0xFF15 + deviceProfileName: window-covering + - id: "5020/65535" + deviceLabel: Zemismart ZM25M Matter Roller Motor + vendorId: 0x139C + productId: 0xFFFF + deviceProfileName: window-covering matterGeneric: - id: "windowcovering" deviceLabel: Matter Window Covering From 97b0d52000421b6991f6151329d2cc821c4251ab Mon Sep 17 00:00:00 2001 From: Carter Swedal Date: Wed, 6 Mar 2024 08:19:10 -0700 Subject: [PATCH 07/21] fixup! allow specified port lunchbox (#1257) --- drivers/SmartThings/jbl/src/lunchbox/rest.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/jbl/src/lunchbox/rest.lua b/drivers/SmartThings/jbl/src/lunchbox/rest.lua index 340b73e114..3d682718b7 100644 --- a/drivers/SmartThings/jbl/src/lunchbox/rest.lua +++ b/drivers/SmartThings/jbl/src/lunchbox/rest.lua @@ -30,7 +30,8 @@ local function connect(client) port = 443 use_ssl = true end - + + local port = client.base_url.port or default_port local sock, err = client.socket_builder(client.base_url.host, port, use_ssl) if sock == nil then From 7221665bd14c0d367cc726b75435f25a9244a1b4 Mon Sep 17 00:00:00 2001 From: Hongming6 <150672162+Hongming6@users.noreply.github.com> Date: Thu, 7 Mar 2024 04:56:21 +0800 Subject: [PATCH 08/21] Aqara LED Light Bulb T1 (Tunable White) (#1116) * Aqara LED Light Bulb T1 (Tunable White) * modification time * Fixed the intermediate level reported during device dimming --- .../zigbee-switch/fingerprints.yml | 5 + .../zigbee-switch/profiles/aqara-led-bulb.yml | 27 +++ .../zigbee-switch/src/aqara-light/init.lua | 16 +- .../zigbee-switch/src/preferences.lua | 9 + .../src/test/test_aqara_led_bulb.lua | 159 ++++++++++++++++++ 5 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 drivers/SmartThings/zigbee-switch/profiles/aqara-led-bulb.yml create mode 100644 drivers/SmartThings/zigbee-switch/src/test/test_aqara_led_bulb.lua diff --git a/drivers/SmartThings/zigbee-switch/fingerprints.yml b/drivers/SmartThings/zigbee-switch/fingerprints.yml index e1d212be01..9fe0d2e184 100644 --- a/drivers/SmartThings/zigbee-switch/fingerprints.yml +++ b/drivers/SmartThings/zigbee-switch/fingerprints.yml @@ -111,6 +111,11 @@ zigbeeManufacturer: manufacturer: LUMI model: lumi.light.acn004 deviceProfileName: aqara-light + - id: "Aqara/lumi.light.acn014" + deviceLabel: Aqara LED Light Bulb T1 (Tunable White) + manufacturer: Aqara + model: lumi.light.acn014 + deviceProfileName: aqara-led-bulb # VIMAR - id: "Vimar/xx592-2-way-smart-switch" deviceLabel: Vimar 2-way Smart Switch diff --git a/drivers/SmartThings/zigbee-switch/profiles/aqara-led-bulb.yml b/drivers/SmartThings/zigbee-switch/profiles/aqara-led-bulb.yml new file mode 100644 index 0000000000..65ee11beb0 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/profiles/aqara-led-bulb.yml @@ -0,0 +1,27 @@ +name: aqara-led-bulb +components: + - id: main + capabilities: + - id: switch + version: 1 + - id: switchLevel + version: 1 + config: + values: + - key: "level.value" + range: [1, 100] + - id: colorTemperature + version: 1 + config: + values: + - key: "colorTemperature.value" + range: [2700, 6500] + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Light +preferences: + - preferenceId: stse.restorePowerState + explicit: true diff --git a/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua b/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua index 64b3a31649..528902864f 100644 --- a/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua +++ b/drivers/SmartThings/zigbee-switch/src/aqara-light/init.lua @@ -1,6 +1,7 @@ local clusters = require "st.zigbee.zcl.clusters" local cluster_base = require "st.zigbee.cluster_base" local data_types = require "st.zigbee.data_types" +local capabilities = require "st.capabilities" local OnOff = clusters.OnOff local Level = clusters.Level @@ -11,7 +12,8 @@ local PRIVATE_ATTRIBUTE_ID = 0x0009 local MFG_CODE = 0x115F local FINGERPRINTS = { - { mfr = "LUMI", model = "lumi.light.acn004" } + { mfr = "LUMI", model = "lumi.light.acn004" }, + { mfr = "Aqara", model = "lumi.light.acn014" } } local function is_aqara_products(opts, driver, device) @@ -43,12 +45,24 @@ local function device_added(driver, device, event) device:send(Level.attributes.OffTransitionTime:write(device, 0)) end +local function set_level_handler(driver, device, cmd) + local level = math.floor(cmd.args.level / 100.0 * 254) + local dimming_rate = 0x0000 + + device:send(Level.commands.MoveToLevelWithOnOff(device, level, dimming_rate)) +end + local aqara_light_handler = { NAME = "Aqara Light Handler", lifecycle_handlers = { added = device_added, doConfigure = do_configure }, + capability_handlers = { + [capabilities.switchLevel.ID] = { + [capabilities.switchLevel.commands.setLevel.NAME] = set_level_handler + } + }, can_handle = is_aqara_products } diff --git a/drivers/SmartThings/zigbee-switch/src/preferences.lua b/drivers/SmartThings/zigbee-switch/src/preferences.lua index 20900d5a52..64c003eb56 100644 --- a/drivers/SmartThings/zigbee-switch/src/preferences.lua +++ b/drivers/SmartThings/zigbee-switch/src/preferences.lua @@ -37,6 +37,15 @@ local devices = { return clusters.Level.attributes.OffTransitionTime:write(device, raw_value) end } + }, + AQARA_LIGHT_BULB = { + MATCHING_MATRIX = { mfr = "Aqara", model = "lumi.light.acn014" }, + PARAMETERS = { + ["stse.restorePowerState"] = function(device, value) + return cluster_base.write_manufacturer_specific_attribute(device, 0xFCC0, + 0x0201, 0x115F, data_types.Boolean, value) + end + } } } local preferences = {} diff --git a/drivers/SmartThings/zigbee-switch/src/test/test_aqara_led_bulb.lua b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_led_bulb.lua new file mode 100644 index 0000000000..86c8d63058 --- /dev/null +++ b/drivers/SmartThings/zigbee-switch/src/test/test_aqara_led_bulb.lua @@ -0,0 +1,159 @@ +-- Copyright 2023 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local clusters = require "st.zigbee.zcl.clusters" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" + +local OnOff = clusters.OnOff +local Level = clusters.Level +local ColorControl = clusters.ColorControl + +local PRIVATE_CLUSTER_ID = 0xFCC0 +local PRIVATE_ATTRIBUTE_ID = 0x0009 +local MFG_CODE = 0x115F + +local RESTORE_POWER_STATE_ATTRIBUTE_ID = 0x0201 + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("aqara-led-bulb.yml"), + fingerprinted_endpoint_id = 0x01, + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "Aqara", + model = "lumi.light.acn014", + server_clusters = { 0x0006, 0x0008, 0x0300 } + } + } + } +) + +zigbee_test_utils.prepare_zigbee_env_info() + +local function test_init() + test.mock_device.add_test_device(mock_device) +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Handle added lifecycle", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + + test.socket.zigbee:__expect_send( + { + mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID + , MFG_CODE, data_types.Uint8, 1) + } + ) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OnTransitionTime:write(mock_device, 0) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.OffTransitionTime:write(mock_device, 0) }) + end +) + +test.register_coroutine_test( + "Configure should configure all necessary attributes and refresh device", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "doConfigure" }) + test.socket.zigbee:__set_channel_ordering("relaxed") + + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.commands.MoveToColorTemperature(mock_device, 200, 0) + } + ) + + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, Level.ID) + }) + test.socket.zigbee:__expect_send( + { + mock_device.id, + Level.attributes.CurrentLevel:configure_reporting(mock_device, 1, 3600, 1) + } + ) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, ColorControl.ID) + }) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.attributes.ColorTemperatureMireds:configure_reporting(mock_device, 1, 3600, 16) + } + ) + test.socket.zigbee:__expect_send({ + mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, OnOff.ID) + }) + test.socket.zigbee:__expect_send( + { + mock_device.id, + OnOff.attributes.OnOff:configure_reporting(mock_device, 0, 300, 1) + } + ) + + test.socket.zigbee:__expect_send({ mock_device.id, OnOff.attributes.OnOff:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, Level.attributes.CurrentLevel:read(mock_device) }) + test.socket.zigbee:__expect_send({ mock_device.id, ColorControl.attributes.ColorTemperatureMireds:read(mock_device) }) + + mock_device:expect_metadata_update({ provisioning_state = "PROVISIONED" }) + end +) + +test.register_coroutine_test( + "Set Color Temperature command test", + function() + test.timer.__create_and_queue_test_time_advance_timer(1, "oneshot") + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "colorTemperature", component = "main", command = "setColorTemperature", args = { 200 } } }) + + local temp_in_mired = math.floor(1000000 / 200) + test.socket.zigbee:__expect_send( + { + mock_device.id, + OnOff.commands.On(mock_device) + } + ) + test.socket.zigbee:__expect_send( + { + mock_device.id, + ColorControl.commands.MoveToColorTemperature(mock_device, temp_in_mired, 0x0000) + } + ) + end +) + +test.register_coroutine_test( + "Handle restorePowerState in infochanged", + function() + test.socket.device_lifecycle:__queue_receive(mock_device:generate_info_changed({ + preferences = { ["stse.restorePowerState"] = true } + })) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + RESTORE_POWER_STATE_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) + end +) + +test.run_registered_tests() From 4b6a5a27e9bc1c0224ba044710f518655c0d7be3 Mon Sep 17 00:00:00 2001 From: Douglas Stephen Date: Wed, 6 Mar 2024 20:54:27 -0600 Subject: [PATCH 09/21] Update Zigbee tests for next Lua Libs release (#1156) The next Lua Libs release has a few changes on timing and tolerance for some clusters to ease battery drain, so these test updates will need to be merged in at that time. Co-authored-by: cjswedes --- .../SmartThings/zigbee-button/src/test/test_samjin_button.lua | 2 +- .../zigbee-contact/src/test/test_centralite_multi_sensor.lua | 2 +- .../zigbee-contact/src/test/test_samjin_multi_sensor.lua | 2 +- .../zigbee-contact/src/test/test_smartthings_multi_sensor.lua | 2 +- .../zigbee-contact/src/test/test_zigbee_contact.lua | 4 ++-- .../src/test/test_centralite_sensor.lua | 2 +- .../zigbee-humidity-sensor/src/test/test_heiman_sensor.lua | 2 +- .../src/test/test_humidity_temperature.lua | 2 +- .../src/test/test_humidity_temperature_sensor.lua | 2 +- .../src/test/test_all_capabilities_zigbee_motion.lua | 2 +- .../zigbee-motion-sensor/src/test/test_compacta_motion.lua | 2 +- .../zigbee-motion-sensor/src/test/test_frient_sensor.lua | 2 +- .../zigbee-motion-sensor/src/test/test_samjin_sensor.lua | 2 +- .../zigbee-sound-sensor/src/test/test_zigbee_sound_sensor.lua | 2 +- .../src/test/test_centralite_water_leak_sensor.lua | 2 +- .../src/test/test_frient_water_leak_sensor.lua | 2 +- .../src/test/test_samjin_water_leak_sensor.lua | 2 +- .../src/test/test_sinope_zigbee_water.lua | 4 ++-- .../src/test/test_smartthings_water_leak_sensor.lua | 2 +- .../zigbee-water-leak-sensor/src/test/test_zigbee_water.lua | 4 ++-- .../src/test/test_zigbee_water_freeze.lua | 4 ++-- 21 files changed, 25 insertions(+), 25 deletions(-) diff --git a/drivers/SmartThings/zigbee-button/src/test/test_samjin_button.lua b/drivers/SmartThings/zigbee-button/src/test/test_samjin_button.lua index 7a59312850..9e986f097f 100644 --- a/drivers/SmartThings/zigbee-button/src/test/test_samjin_button.lua +++ b/drivers/SmartThings/zigbee-button/src/test/test_samjin_button.lua @@ -89,7 +89,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:configure_reporting( - mock_device, 30, 300, 16 + mock_device, 30, 600, 100 ) }) test.socket.zigbee:__expect_send({ diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_centralite_multi_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_centralite_multi_sensor.lua index 2b93291472..e3628f6ec7 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_centralite_multi_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_centralite_multi_sensor.lua @@ -240,7 +240,7 @@ test.register_coroutine_test( }) test.socket.zigbee:__expect_send({ mock_device.id, - TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 300, 16) + TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 600, 100) }) test.socket.zigbee:__expect_send({ mock_device.id, diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_samjin_multi_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_samjin_multi_sensor.lua index a1b72c1344..d8a23f4af5 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_samjin_multi_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_samjin_multi_sensor.lua @@ -147,7 +147,7 @@ test.register_coroutine_test( }) test.socket.zigbee:__expect_send({ mock_device.id, - TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 300, 16) + TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 600, 100) }) test.socket.zigbee:__expect_send({ mock_device.id, diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua b/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua index 0953858c0d..05e5fdd0d4 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_smartthings_multi_sensor.lua @@ -294,7 +294,7 @@ test.register_coroutine_test( }) test.socket.zigbee:__expect_send({ mock_device.id, - TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 300, 16) + TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 600, 100) }) test.socket.zigbee:__expect_send({ mock_device.id, diff --git a/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact.lua b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact.lua index 1f5be76ca2..0c89cbb6aa 100644 --- a/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact.lua +++ b/drivers/SmartThings/zigbee-contact/src/test/test_zigbee_contact.lua @@ -190,8 +190,8 @@ test.register_coroutine_test( mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, - 300, - 0x10) + 600, + 100) } ) test.socket.zigbee:__expect_send( diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_centralite_sensor.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_centralite_sensor.lua index 96ddd665e0..626d704f2a 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_centralite_sensor.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_centralite_sensor.lua @@ -141,7 +141,7 @@ test.register_coroutine_test( }) test.socket.zigbee:__expect_send({ mock_device.id, - TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 300, 16) + TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 600, 100) }) test.socket.zigbee:__expect_send({ mock_device.id, diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_heiman_sensor.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_heiman_sensor.lua index 45795d7faa..3dd7f5c719 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_heiman_sensor.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_heiman_sensor.lua @@ -127,7 +127,7 @@ test.register_coroutine_test( }) test.socket.zigbee:__expect_send({ mock_device.id, - TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 300, 16) + TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 600, 100) }) test.socket.zigbee:__expect_send({ mock_device.id, RelativeHumidity.attributes.MeasuredValue:read(mock_device):to_endpoint(0x02) }) test.socket.zigbee:__expect_send({ mock_device.id, TemperatureMeasurement.attributes.MeasuredValue:read(mock_device) }) diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_temperature.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_temperature.lua index cf22600ec1..3fa1b5719b 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_temperature.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_temperature.lua @@ -94,7 +94,7 @@ test.register_coroutine_test( }) test.socket.zigbee:__expect_send({ mock_device.id, - TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 300, 0x10) + TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 600, 100) }) test.socket.zigbee:__expect_send({ mock_device.id, diff --git a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_temperature_sensor.lua b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_temperature_sensor.lua index 9114c04bc8..1defdb1b71 100644 --- a/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_temperature_sensor.lua +++ b/drivers/SmartThings/zigbee-humidity-sensor/src/test/test_humidity_temperature_sensor.lua @@ -101,7 +101,7 @@ test.register_coroutine_test( }) test.socket.zigbee:__expect_send({ mock_device.id, - TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 300, 0x10) + TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 600, 100) }) test.socket.zigbee:__expect_send({ mock_device.id, diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_all_capabilities_zigbee_motion.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_all_capabilities_zigbee_motion.lua index 4f348b727f..c68eb33ef9 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_all_capabilities_zigbee_motion.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_all_capabilities_zigbee_motion.lua @@ -194,7 +194,7 @@ test.register_coroutine_test( }) test.socket.zigbee:__expect_send({ mock_device.id, - TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 300, 0x10) + TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 600, 100) }) test.socket.zigbee:__expect_send({ mock_device.id, diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_compacta_motion.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_compacta_motion.lua index 4b558cc36e..a5264683e5 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_compacta_motion.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_compacta_motion.lua @@ -60,7 +60,7 @@ test.register_coroutine_test( }) test.socket.zigbee:__expect_send({ mock_device.id, - TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 300, 16) + TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 600, 100) }) test.socket.zigbee:__expect_send({ mock_device.id, diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_frient_sensor.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_frient_sensor.lua index bf4d309272..c2eb704ccc 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_frient_sensor.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_frient_sensor.lua @@ -62,7 +62,7 @@ test.register_coroutine_test( }) test.socket.zigbee:__expect_send({ mock_device.id, - TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 300, 16) + TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 600, 100) }) test.socket.zigbee:__expect_send({ mock_device.id, diff --git a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_samjin_sensor.lua b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_samjin_sensor.lua index 637b2346f8..a645c46c75 100644 --- a/drivers/SmartThings/zigbee-motion-sensor/src/test/test_samjin_sensor.lua +++ b/drivers/SmartThings/zigbee-motion-sensor/src/test/test_samjin_sensor.lua @@ -70,7 +70,7 @@ test.register_coroutine_test( }) test.socket.zigbee:__expect_send({ mock_device.id, - TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 300, 16) + TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 600, 100) }) test.socket.zigbee:__expect_send({ mock_device.id, diff --git a/drivers/SmartThings/zigbee-sound-sensor/src/test/test_zigbee_sound_sensor.lua b/drivers/SmartThings/zigbee-sound-sensor/src/test/test_zigbee_sound_sensor.lua index 85901991b6..90249669ba 100644 --- a/drivers/SmartThings/zigbee-sound-sensor/src/test/test_zigbee_sound_sensor.lua +++ b/drivers/SmartThings/zigbee-sound-sensor/src/test/test_zigbee_sound_sensor.lua @@ -84,7 +84,7 @@ test.register_coroutine_test( }) test.socket.zigbee:__expect_send({ mock_device.id, - TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 300, 0x10) + TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 600, 100) }) test.socket.zigbee:__expect_send({ mock_device.id, diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_centralite_water_leak_sensor.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_centralite_water_leak_sensor.lua index 1ecf651e16..30258012d4 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_centralite_water_leak_sensor.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_centralite_water_leak_sensor.lua @@ -96,7 +96,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send( { mock_device.id, - TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 300, 16) + TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 600, 100) } ) diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_frient_water_leak_sensor.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_frient_water_leak_sensor.lua index 5412b94afa..4cae9883ca 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_frient_water_leak_sensor.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_frient_water_leak_sensor.lua @@ -96,7 +96,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send( { mock_device.id, - TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 300, 16) + TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 600, 100) } ) test.socket.zigbee:__expect_send( diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_samjin_water_leak_sensor.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_samjin_water_leak_sensor.lua index 5afaa68f4b..352841bdd4 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_samjin_water_leak_sensor.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_samjin_water_leak_sensor.lua @@ -96,7 +96,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send( { mock_device.id, - TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 300, 16) + TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 600, 100) } ) diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_sinope_zigbee_water.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_sinope_zigbee_water.lua index 3306fbb336..33f6c28f24 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_sinope_zigbee_water.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_sinope_zigbee_water.lua @@ -215,8 +215,8 @@ test.register_coroutine_test( TemperatureMeasurement.attributes.MeasuredValue:configure_reporting( mock_device, 30, - 300, - 0x10 + 600, + 100 ) }) test.socket.zigbee:__expect_send({ diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_smartthings_water_leak_sensor.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_smartthings_water_leak_sensor.lua index 390e505bcd..5cf7801637 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_smartthings_water_leak_sensor.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_smartthings_water_leak_sensor.lua @@ -96,7 +96,7 @@ test.register_coroutine_test( test.socket.zigbee:__expect_send( { mock_device.id, - TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 300, 16) + TemperatureMeasurement.attributes.MeasuredValue:configure_reporting(mock_device, 30, 600, 100) } ) diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_zigbee_water.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_zigbee_water.lua index c6c712063d..b202dd450c 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_zigbee_water.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_zigbee_water.lua @@ -189,8 +189,8 @@ test.register_coroutine_test( TemperatureMeasurement.attributes.MeasuredValue:configure_reporting( mock_device, 30, - 300, - 0x10 + 600, + 100 ) }) test.socket.zigbee:__expect_send({ diff --git a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_zigbee_water_freeze.lua b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_zigbee_water_freeze.lua index c6ea0e7760..ed03d10974 100644 --- a/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_zigbee_water_freeze.lua +++ b/drivers/SmartThings/zigbee-water-leak-sensor/src/test/test_zigbee_water_freeze.lua @@ -111,8 +111,8 @@ test.register_coroutine_test( TemperatureMeasurement.attributes.MeasuredValue:configure_reporting( mock_device, 30, - 300, - 0x10 + 600, + 100 ) }) test.socket.zigbee:__expect_send({ From e7ba1e7e2ad6d43259e04ecd2cde650f75450986 Mon Sep 17 00:00:00 2001 From: Carter Swedal Date: Wed, 6 Mar 2024 21:20:31 -0700 Subject: [PATCH 10/21] fixup! jbl variable sccope (#1258) --- drivers/SmartThings/jbl/src/lunchbox/rest.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/SmartThings/jbl/src/lunchbox/rest.lua b/drivers/SmartThings/jbl/src/lunchbox/rest.lua index 3d682718b7..1128dd7f96 100644 --- a/drivers/SmartThings/jbl/src/lunchbox/rest.lua +++ b/drivers/SmartThings/jbl/src/lunchbox/rest.lua @@ -31,7 +31,7 @@ local function connect(client) use_ssl = true end - local port = client.base_url.port or default_port + if client.base_url.port ~= port then port = client.base_url.port end local sock, err = client.socket_builder(client.base_url.host, port, use_ssl) if sock == nil then @@ -268,7 +268,7 @@ RestClient.__index = RestClient function RestClient.one_shot_get(full_url, additional_headers, socket_builder) local url_table = lb_utils.force_url_table(full_url) - local client = RestClient.new(url_table.scheme .. "://" .. url_table.host, socket_builder) + local client = RestClient.new(url_table.scheme .. "://" .. url_table.authority, socket_builder) local ret, err = client:get(url_table.path, additional_headers) client:shutdown() return ret, err @@ -276,7 +276,7 @@ end function RestClient.one_shot_post(full_url, body, additional_headers, socket_builder) local url_table = lb_utils.force_url_table(full_url) - local client = RestClient.new(url_table.scheme .. "://" .. url_table.host, socket_builder) + local client = RestClient.new(url_table.scheme .. "://" .. url_table.authority, socket_builder) local ret, err = client:post(url_table.path, body, additional_headers) client:shutdown() return ret, err From 6b54273281bac6da44088590a6d79adb4f7bf701 Mon Sep 17 00:00:00 2001 From: YongSung Lee Date: Thu, 7 Mar 2024 21:09:58 +0900 Subject: [PATCH 11/21] add Resideo Korea DT300ST-M0000 Thermostat (#1119) * add Resideo Korea DT300ST-M0000 Thermostat Resideo Korea Thermostat for WWST * add unittest for child devices --- .../zigbee-thermostat/fingerprints.yml | 5 + .../thermostat-resideo-dt300st-m000.yml | 34 + .../zigbee-thermostat/src/init.lua | 3 +- .../src/resideo_korea/init.lua | 186 +++++ .../src/test/test_resideo_dt300st_m000.lua | 719 ++++++++++++++++++ 5 files changed, 946 insertions(+), 1 deletion(-) create mode 100644 drivers/SmartThings/zigbee-thermostat/profiles/thermostat-resideo-dt300st-m000.yml create mode 100644 drivers/SmartThings/zigbee-thermostat/src/resideo_korea/init.lua create mode 100644 drivers/SmartThings/zigbee-thermostat/src/test/test_resideo_dt300st_m000.lua diff --git a/drivers/SmartThings/zigbee-thermostat/fingerprints.yml b/drivers/SmartThings/zigbee-thermostat/fingerprints.yml index 1dd3137f16..b8793b0043 100644 --- a/drivers/SmartThings/zigbee-thermostat/fingerprints.yml +++ b/drivers/SmartThings/zigbee-thermostat/fingerprints.yml @@ -119,6 +119,11 @@ zigbeeManufacturer: manufacturer: Centralite model: 3156105 deviceProfileName: base-thermostat-no-operating-state + - id: "Resideo/DT300ST-M000" + deviceLabel: Main Thermostat + manufacturer: Resideo Korea + model: DT300ST-M000 + deviceProfileName: thermostat-resideo-dt300st-m000 zigbeeGeneric: - id: "genericThermostat" deviceLabel: Zigbee Thermostat diff --git a/drivers/SmartThings/zigbee-thermostat/profiles/thermostat-resideo-dt300st-m000.yml b/drivers/SmartThings/zigbee-thermostat/profiles/thermostat-resideo-dt300st-m000.yml new file mode 100644 index 0000000000..a31234eb90 --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/profiles/thermostat-resideo-dt300st-m000.yml @@ -0,0 +1,34 @@ +name: thermostat-resideo-dt300st-m000 +components: +- id: main + capabilities: + - id: temperatureMeasurement + version: 1 + - id: thermostatHeatingSetpoint + version: 1 + config: + values: + - key: "heatingSetpoint.value" + range: [ 5, 35 ] + - id: thermostatMode + version: 1 + config: + values: + - key: "thermostatMode.value" + enabledValues: + - heating + - idle + - id: thermostatOperatingState + version: 1 + config: + values: + - key: "thermostatOperatingState.value" + enabledValues: + - heating + - idle + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Thermostat diff --git a/drivers/SmartThings/zigbee-thermostat/src/init.lua b/drivers/SmartThings/zigbee-thermostat/src/init.lua index 0f35ada2d5..6602c1c3db 100644 --- a/drivers/SmartThings/zigbee-thermostat/src/init.lua +++ b/drivers/SmartThings/zigbee-thermostat/src/init.lua @@ -294,7 +294,8 @@ local zigbee_thermostat_driver = { require("leviton"), require("danfoss"), require("popp"), - require("vimar") + require("vimar"), + require("resideo_korea") }, } diff --git a/drivers/SmartThings/zigbee-thermostat/src/resideo_korea/init.lua b/drivers/SmartThings/zigbee-thermostat/src/resideo_korea/init.lua new file mode 100644 index 0000000000..a1e68ef02a --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/resideo_korea/init.lua @@ -0,0 +1,186 @@ +-- Copyright 2023 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 device_lib = require "st.device" +local device_management = require "st.zigbee.device_management" + +-- Zigbee Spec Utils +local clusters = require "st.zigbee.zcl.clusters" +local Thermostat = clusters.Thermostat +local ThermostatSystemMode = Thermostat.attributes.SystemMode + +local capabilities = require "st.capabilities" +local ThermostatMode = capabilities.thermostatMode +local ThermostatOperatingState = capabilities.thermostatOperatingState + +local do_refresh = function(self, device) + local attributes = {Thermostat.attributes.OccupiedHeatingSetpoint, Thermostat.attributes.LocalTemperature, + Thermostat.attributes.ControlSequenceOfOperation, Thermostat.attributes.ThermostatRunningState, + Thermostat.attributes.SystemMode} + for _, attribute in pairs(attributes) do + if device.network_type == device_lib.NETWORK_TYPE_ZIGBEE then + -- SmartThings Hub has a issue with setting parent endpoint + device:send(attribute:read(device):to_endpoint(0x01)) + else + device:send(attribute:read(device)) + end + end +end + +local do_configure = function(self, device) + if device.network_type == device_lib.NETWORK_TYPE_ZIGBEE then + for endpoint = 1, 6 do + device:send(device_management.build_bind_request(device, Thermostat.ID, self.environment_info.hub_zigbee_eui, + endpoint)) + end + end + device:send(Thermostat.attributes.OccupiedHeatingSetpoint:configure_reporting(device, 20, 300, 100)) + device:send(Thermostat.attributes.LocalTemperature:configure_reporting(device, 20, 300, 100)) + device:send(Thermostat.attributes.ThermostatRunningState:configure_reporting(device, 20, 300)) + device:send(Thermostat.attributes.SystemMode:configure_reporting(device, 20, 300)) + + do_refresh(self, device) +end + +local SUPPORTED_THERMOSTAT_MODES = {ThermostatMode.thermostatMode.away.NAME, ThermostatMode.thermostatMode.heat.NAME} + +local supported_thermostat_modes_handler = function(driver, device, supported_modes, zb_rx) + device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, + ThermostatMode.supportedThermostatModes(SUPPORTED_THERMOSTAT_MODES, { + visibility = { + displayed = false + } + })) +end + +local thermostat_operating_state_handler = function(driver, device, operating_state, zb_rx) + if (operating_state:is_heat_second_stage_on_set() or operating_state:is_heat_on_set()) then + device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, + ThermostatOperatingState.thermostatOperatingState.heating()) + elseif (operating_state:is_cool_second_stage_on_set() or operating_state:is_cool_on_set()) then + device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, + ThermostatOperatingState.thermostatOperatingState.cooling()) + elseif (operating_state:is_fan_on_set()) then + device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, + ThermostatOperatingState.thermostatOperatingState.fan_only()) + else + device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, + ThermostatOperatingState.thermostatOperatingState.idle()) + end +end + +local function thermostat_occupied_heating_setpoint_handler(driver, device, value, zb_rx) + device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, + capabilities.thermostatHeatingSetpoint.heatingSetpoint({ + value = value.value / 100.0, + unit = "C" + })) +end + +local set_thermostat_mode = function(driver, device, command) + if command.args.mode == ThermostatMode.thermostatMode.off.NAME or command.args.mode == + ThermostatMode.thermostatMode.away.NAME then + device:send_to_component(command.component, Thermostat.attributes.SystemMode:write(device, ThermostatSystemMode.OFF)) + device.thread:call_with_delay(1, function(d) + device:send_to_component(command.component, Thermostat.attributes.SystemMode:read(device)) + end) + elseif command.args.mode == ThermostatMode.thermostatMode.auto.NAME or command.args.mode == + ThermostatMode.thermostatMode.heat.NAME then + device:send_to_component(command.component, + Thermostat.attributes.SystemMode:write(device, ThermostatSystemMode.HEAT)) + device.thread:call_with_delay(1, function(d) + device:send_to_component(command.component, Thermostat.attributes.SystemMode:read(device)) + end) + end +end + +local thermostat_mode_setter = function(mode_name) + return function(driver, device, command) + return set_thermostat_mode(driver, device, { + component = command.component, + args = { + mode = mode_name + } + }) + end +end + +local thermostat_mode_handler = function(driver, device, thermostat_mode, zb_rx) + if thermostat_mode.value == ThermostatSystemMode.OFF then + device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, ThermostatMode.thermostatMode.away()) + else + device:emit_event_for_endpoint(zb_rx.address_header.src_endpoint.value, ThermostatMode.thermostatMode.heat()) + end +end + +local function added(driver, device, event) + if device.network_type == device_lib.NETWORK_TYPE_ZIGBEE then + for i = 2, 6, 1 do + local name = string.format("Room %d", i - 1) + local metadata = { + type = "EDGE_CHILD", + label = name, + profile = "thermostat-resideo-dt300st-m000", + parent_device_id = device.id, + parent_assigned_child_key = string.format("%02X", i), + vendor_provided_label = name + } + driver:try_create_device(metadata) + end + end +end + +local function find_child(parent, ep_id) + return parent:get_child_by_parent_assigned_key(string.format("%02X", ep_id)) +end + +local function init(driver, device, event) + if device.network_type == device_lib.NETWORK_TYPE_ZIGBEE then + device:set_find_child(find_child) + end +end + +local resideo_thermostat = { + NAME = "Resideo Thermostat Handler", + lifecycle_handlers = { + init = init, + added = added, + doConfigure = do_configure + }, + zigbee_handlers = { + attr = { + [Thermostat.ID] = { + [Thermostat.attributes.ControlSequenceOfOperation.ID] = supported_thermostat_modes_handler, + [Thermostat.attributes.ThermostatRunningState.ID] = thermostat_operating_state_handler, + [Thermostat.attributes.SystemMode.ID] = thermostat_mode_handler, + [Thermostat.attributes.OccupiedHeatingSetpoint.ID] = thermostat_occupied_heating_setpoint_handler + } + } + }, + capability_handlers = { + [ThermostatMode.ID] = { + [ThermostatMode.commands.setThermostatMode.NAME] = set_thermostat_mode, + [ThermostatMode.commands.auto.NAME] = thermostat_mode_setter(ThermostatMode.thermostatMode.heat.NAME), + [ThermostatMode.commands.off.NAME] = thermostat_mode_setter(ThermostatMode.thermostatMode.off.NAME), + [ThermostatMode.commands.heat.NAME] = thermostat_mode_setter(ThermostatMode.thermostatMode.heat.NAME) + }, + [capabilities.refresh.ID] = { + [capabilities.refresh.commands.refresh.NAME] = do_refresh + } + }, + can_handle = function(opts, driver, device, ...) + return device:get_manufacturer() == "Resideo Korea" and device:get_model() == "DT300ST-M000" + end +} + +return resideo_thermostat diff --git a/drivers/SmartThings/zigbee-thermostat/src/test/test_resideo_dt300st_m000.lua b/drivers/SmartThings/zigbee-thermostat/src/test/test_resideo_dt300st_m000.lua new file mode 100644 index 0000000000..2a3348e3d0 --- /dev/null +++ b/drivers/SmartThings/zigbee-thermostat/src/test/test_resideo_dt300st_m000.lua @@ -0,0 +1,719 @@ +-- Copyright 2023 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +local test = require "integration_test" +local t_utils = require "integration_test.utils" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local clusters = require "st.zigbee.zcl.clusters" + +local Thermostat = clusters.Thermostat +local capabilities = require "st.capabilities" + +local mock_device = test.mock_device.build_test_zigbee_device({ + profile = t_utils.get_profile_definition("thermostat-resideo-dt300st-m000.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "Resideo Korea", + model = "DT300ST-M000", + server_clusters = {0x0201, 0x0402} + } + } +}) + +-- Room 1 (2nd Thermostat) +local mock_first_child = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("thermostat-resideo-dt300st-m000.yml"), + device_network_id = string.format("%04X:%02X", mock_device:get_short_address(), 2), + parent_device_id = mock_device.id, + parent_assigned_child_key = string.format("%02X", 2) +}) + +-- Room 2 (3rd Thermostat) +local mock_second_child = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("thermostat-resideo-dt300st-m000.yml"), + device_network_id = string.format("%04X:%02X", mock_device:get_short_address(), 3), + parent_device_id = mock_device.id, + parent_assigned_child_key = string.format("%02X", 3) +}) + +-- Room 3 (4th Thermostat) +local mock_third_child = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("thermostat-resideo-dt300st-m000.yml"), + device_network_id = string.format("%04X:%02X", mock_device:get_short_address(), 4), + parent_device_id = mock_device.id, + parent_assigned_child_key = string.format("%02X", 4) +}) + +-- Room 4 (5th Thermostat) +local mock_forth_child = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("thermostat-resideo-dt300st-m000.yml"), + device_network_id = string.format("%04X:%02X", mock_device:get_short_address(), 5), + parent_device_id = mock_device.id, + parent_assigned_child_key = string.format("%02X", 5) +}) + +-- Room 5 (6th Thermostat) +local mock_fifth_child = test.mock_device.build_test_child_device({ + profile = t_utils.get_profile_definition("thermostat-resideo-dt300st-m000.yml"), + device_network_id = string.format("%04X:%02X", mock_device:get_short_address(), 6), + parent_device_id = mock_device.id, + parent_assigned_child_key = string.format("%02X", 6) +}) + +zigbee_test_utils.prepare_zigbee_env_info() +local function test_init() + test.mock_device.add_test_device(mock_device) + test.mock_device.add_test_device(mock_first_child) + test.mock_device.add_test_device(mock_second_child) + test.mock_device.add_test_device(mock_third_child) + test.mock_device.add_test_device(mock_forth_child) + test.mock_device.add_test_device(mock_fifth_child) + zigbee_test_utils.init_noop_health_check_timer() +end + +test.set_test_init_function(test_init) + +test.register_coroutine_test("Configure should configure all necessary attributes", function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.device_lifecycle:__queue_receive({mock_device.id, "doConfigure"}) + for endpoint = 1, 6 do + test.socket.zigbee:__expect_send({mock_device.id, + zigbee_test_utils.build_bind_request(mock_device, zigbee_test_utils.mock_hub_eui, + Thermostat.ID, endpoint)}) + end + + test.socket.zigbee:__expect_send({mock_device.id, + Thermostat.attributes.OccupiedHeatingSetpoint:configure_reporting(mock_device, 20, + 300, 100)}) + test.socket.zigbee:__expect_send({mock_device.id, + Thermostat.attributes.LocalTemperature:configure_reporting(mock_device, 20, 300, 100)}) + test.socket.zigbee:__expect_send({mock_device.id, + Thermostat.attributes.ThermostatRunningState:configure_reporting(mock_device, 20, + 300)}) + test.socket.zigbee:__expect_send({mock_device.id, + Thermostat.attributes.SystemMode:configure_reporting(mock_device, 20, 300)}) + + local attributes = {Thermostat.attributes.OccupiedHeatingSetpoint, Thermostat.attributes.LocalTemperature, + Thermostat.attributes.ControlSequenceOfOperation, Thermostat.attributes.ThermostatRunningState, + Thermostat.attributes.SystemMode} + for _, attribute in pairs(attributes) do + test.socket.zigbee:__expect_send({mock_device.id, attribute:read(mock_device)}) + end + mock_device:expect_metadata_update({ + provisioning_state = "PROVISIONED" + }) +end) + +-------------------------------------------------------------------------------- +-- Parent thermostat device + +test.register_coroutine_test("Refresh should read all necessary attributes", function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__queue_receive({mock_device.id, { + capability = "refresh", + component = "main", + command = "refresh", + args = {} + }}) + local attributes = {Thermostat.attributes.OccupiedHeatingSetpoint, Thermostat.attributes.LocalTemperature, + Thermostat.attributes.ControlSequenceOfOperation, Thermostat.attributes.ThermostatRunningState, + Thermostat.attributes.SystemMode} + for _, attribute in pairs(attributes) do + test.socket.zigbee:__expect_send({mock_device.id, attribute:read(mock_device)}) + end +end) + +test.register_coroutine_test("Temperature reporting should create the appropriate events", function() + test.socket.zigbee:__queue_receive({mock_device.id, + Thermostat.attributes.LocalTemperature:build_test_attr_report(mock_device, 2100)}) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.temperatureMeasurement.temperature({ + value = 21.0, + unit = "C" + }))) +end) + +test.register_coroutine_test("Thermostat mode reporting should create the appropriate events", function() + test.socket.zigbee:__queue_receive({mock_device.id, + Thermostat.attributes.SystemMode:build_test_attr_report(mock_device, + Thermostat.attributes.SystemMode.OFF)}) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.thermostatMode + .thermostatMode.away())) + test.socket.zigbee:__queue_receive({mock_device.id, + Thermostat.attributes.SystemMode:build_test_attr_report(mock_device, + Thermostat.attributes.SystemMode.HEAT)}) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.thermostatMode + .thermostatMode.heat())) +end) + +test.register_coroutine_test("ControlSequenceOfOperation reporting should create the appropriate events", function() + test.socket.zigbee:__queue_receive({mock_device.id, + Thermostat.attributes.ControlSequenceOfOperation:build_test_attr_report( + mock_device, 0x02)}) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.thermostatMode.supportedThermostatModes({"away", "heat"}, { + visibility = { + displayed = false + } + }))) +end) + +test.register_coroutine_test("OccupiedHeatingSetpoint reporting shoulb create the appropriate events", function() + test.socket.zigbee:__queue_receive({mock_device.id, + Thermostat.attributes.OccupiedHeatingSetpoint:build_test_attr_report(mock_device, + 2100)}) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.thermostatHeatingSetpoint.heatingSetpoint({ + value = 21.0, + unit = "C" + }))) +end) + +test.register_coroutine_test("Setting the heating setpoint should generate the appropriate messages", function() + test.socket.capability:__queue_receive({mock_device.id, { + component = "main", + capability = capabilities.thermostatHeatingSetpoint.ID, + command = "setHeatingSetpoint", + args = {21} + }}) + test.socket.zigbee:__expect_send({mock_device.id, + Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device, 2100)}) +end) + +test.register_coroutine_test("Setting the thermostat mode to away should generate the appropriate messages", function() + test.socket.capability:__queue_receive({mock_device.id, { + component = "main", + capability = capabilities.thermostatMode.ID, + command = "setThermostatMode", + args = {"away"} + }}) + test.socket.zigbee:__expect_send({mock_device.id, + Thermostat.attributes.SystemMode:write(mock_device, + Thermostat.attributes.SystemMode.OFF)}) +end) + +test.register_coroutine_test("Setting the thermostat mode to heat should generate the appropriate messages", function() + test.socket.capability:__queue_receive({mock_device.id, { + component = "main", + capability = capabilities.thermostatMode.ID, + command = "heat", + args = {} + }}) + test.socket.zigbee:__expect_send({mock_device.id, + Thermostat.attributes.SystemMode:write(mock_device, + Thermostat.attributes.SystemMode.HEAT)}) +end) + +-------------------------------------------------------------------------------- +-- First child thermostat device + +test.register_coroutine_test("Refresh should read all necessary attributes with first child device", function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__queue_receive({mock_first_child.id, { + capability = "refresh", + component = "main", + command = "refresh", + args = {} + }}) + local attributes = {Thermostat.attributes.OccupiedHeatingSetpoint, Thermostat.attributes.LocalTemperature, + Thermostat.attributes.ControlSequenceOfOperation, Thermostat.attributes.ThermostatRunningState, + Thermostat.attributes.SystemMode} + for _, attribute in pairs(attributes) do + test.socket.zigbee:__expect_send({mock_device.id, attribute:read(mock_first_child)}) + end +end) + +test.register_coroutine_test("Temperature reporting should create the appropriate events with first child device", function() + test.socket.zigbee:__queue_receive({mock_first_child.id, + Thermostat.attributes.LocalTemperature:build_test_attr_report(mock_first_child, 2100)}) + test.socket.capability:__expect_send(mock_first_child:generate_test_message("main", + capabilities.temperatureMeasurement.temperature({ + value = 21.0, + unit = "C" + }))) +end) + +test.register_coroutine_test("Thermostat mode reporting should create the appropriate events with first child device", function() + test.socket.zigbee:__queue_receive({mock_first_child.id, + Thermostat.attributes.SystemMode:build_test_attr_report(mock_first_child, + Thermostat.attributes.SystemMode.OFF)}) + test.socket.capability:__expect_send(mock_first_child:generate_test_message("main", capabilities.thermostatMode + .thermostatMode.away())) + test.socket.zigbee:__queue_receive({mock_first_child.id, + Thermostat.attributes.SystemMode:build_test_attr_report(mock_first_child, + Thermostat.attributes.SystemMode.HEAT)}) + test.socket.capability:__expect_send(mock_first_child:generate_test_message("main", capabilities.thermostatMode + .thermostatMode.heat())) +end) + +test.register_coroutine_test("ControlSequenceOfOperation reporting should create the appropriate events with first child device", function() + test.socket.zigbee:__queue_receive({mock_first_child.id, + Thermostat.attributes.ControlSequenceOfOperation:build_test_attr_report( + mock_first_child, 0x02)}) + test.socket.capability:__expect_send(mock_first_child:generate_test_message("main", + capabilities.thermostatMode.supportedThermostatModes({"away", "heat"}, { + visibility = { + displayed = false + } + }))) +end) + +test.register_coroutine_test("OccupiedHeatingSetpoint reporting shoulb create the appropriate events with first child device", function() + test.socket.zigbee:__queue_receive({mock_first_child.id, + Thermostat.attributes.OccupiedHeatingSetpoint:build_test_attr_report(mock_first_child, + 2100)}) + test.socket.capability:__expect_send(mock_first_child:generate_test_message("main", + capabilities.thermostatHeatingSetpoint.heatingSetpoint({ + value = 21.0, + unit = "C" + }))) +end) + +test.register_coroutine_test("Setting the heating setpoint should generate the appropriate messages with first child device", function() + test.socket.capability:__queue_receive({mock_first_child.id, { + component = "main", + capability = capabilities.thermostatHeatingSetpoint.ID, + command = "setHeatingSetpoint", + args = {21} + }}) + test.socket.zigbee:__expect_send({mock_device.id, + Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_first_child, 2100)}) +end) + +test.register_coroutine_test("Setting the thermostat mode to away should generate the appropriate messages with first child device", function() + test.socket.capability:__queue_receive({mock_first_child.id, { + component = "main", + capability = capabilities.thermostatMode.ID, + command = "setThermostatMode", + args = {"away"} + }}) + test.socket.zigbee:__expect_send({mock_device.id, + Thermostat.attributes.SystemMode:write(mock_first_child, + Thermostat.attributes.SystemMode.OFF)}) +end) + +test.register_coroutine_test("Setting the thermostat mode to heat should generate the appropriate messages with first child device", function() + test.socket.capability:__queue_receive({mock_first_child.id, { + component = "main", + capability = capabilities.thermostatMode.ID, + command = "heat", + args = {} + }}) + test.socket.zigbee:__expect_send({mock_device.id, + Thermostat.attributes.SystemMode:write(mock_first_child, + Thermostat.attributes.SystemMode.HEAT)}) +end) + +-------------------------------------------------------------------------------- +-- Second child thermostat device + +test.register_coroutine_test("Refresh should read all necessary attributes with second child device", function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__queue_receive({mock_second_child.id, { + capability = "refresh", + component = "main", + command = "refresh", + args = {} + }}) + local attributes = {Thermostat.attributes.OccupiedHeatingSetpoint, Thermostat.attributes.LocalTemperature, + Thermostat.attributes.ControlSequenceOfOperation, Thermostat.attributes.ThermostatRunningState, + Thermostat.attributes.SystemMode} + for _, attribute in pairs(attributes) do + test.socket.zigbee:__expect_send({mock_device.id, attribute:read(mock_second_child)}) + end +end) + +test.register_coroutine_test("Temperature reporting should create the appropriate events with second child device", function() + test.socket.zigbee:__queue_receive({mock_second_child.id, + Thermostat.attributes.LocalTemperature:build_test_attr_report(mock_second_child, 2100)}) + test.socket.capability:__expect_send(mock_second_child:generate_test_message("main", + capabilities.temperatureMeasurement.temperature({ + value = 21.0, + unit = "C" + }))) +end) + +test.register_coroutine_test("Thermostat mode reporting should create the appropriate events with second child device", function() + test.socket.zigbee:__queue_receive({mock_second_child.id, + Thermostat.attributes.SystemMode:build_test_attr_report(mock_second_child, + Thermostat.attributes.SystemMode.OFF)}) + test.socket.capability:__expect_send(mock_second_child:generate_test_message("main", capabilities.thermostatMode + .thermostatMode.away())) + test.socket.zigbee:__queue_receive({mock_second_child.id, + Thermostat.attributes.SystemMode:build_test_attr_report(mock_second_child, + Thermostat.attributes.SystemMode.HEAT)}) + test.socket.capability:__expect_send(mock_second_child:generate_test_message("main", capabilities.thermostatMode + .thermostatMode.heat())) +end) + +test.register_coroutine_test("ControlSequenceOfOperation reporting should create the appropriate events with second child device", function() + test.socket.zigbee:__queue_receive({mock_second_child.id, + Thermostat.attributes.ControlSequenceOfOperation:build_test_attr_report( + mock_second_child, 0x02)}) + test.socket.capability:__expect_send(mock_second_child:generate_test_message("main", + capabilities.thermostatMode.supportedThermostatModes({"away", "heat"}, { + visibility = { + displayed = false + } + }))) +end) + +test.register_coroutine_test("OccupiedHeatingSetpoint reporting shoulb create the appropriate events with second child device", function() + test.socket.zigbee:__queue_receive({mock_second_child.id, + Thermostat.attributes.OccupiedHeatingSetpoint:build_test_attr_report(mock_second_child, + 2100)}) + test.socket.capability:__expect_send(mock_second_child:generate_test_message("main", + capabilities.thermostatHeatingSetpoint.heatingSetpoint({ + value = 21.0, + unit = "C" + }))) +end) + +test.register_coroutine_test("Setting the heating setpoint should generate the appropriate messages with second child device", function() + test.socket.capability:__queue_receive({mock_second_child.id, { + component = "main", + capability = capabilities.thermostatHeatingSetpoint.ID, + command = "setHeatingSetpoint", + args = {21} + }}) + test.socket.zigbee:__expect_send({mock_device.id, + Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_second_child, 2100)}) +end) + +test.register_coroutine_test("Setting the thermostat mode to away should generate the appropriate messages with second child device", function() + test.socket.capability:__queue_receive({mock_second_child.id, { + component = "main", + capability = capabilities.thermostatMode.ID, + command = "setThermostatMode", + args = {"away"} + }}) + test.socket.zigbee:__expect_send({mock_device.id, + Thermostat.attributes.SystemMode:write(mock_second_child, + Thermostat.attributes.SystemMode.OFF)}) +end) + +test.register_coroutine_test("Setting the thermostat mode to heat should generate the appropriate messages with second child device", function() + test.socket.capability:__queue_receive({mock_second_child.id, { + component = "main", + capability = capabilities.thermostatMode.ID, + command = "heat", + args = {} + }}) + test.socket.zigbee:__expect_send({mock_device.id, + Thermostat.attributes.SystemMode:write(mock_second_child, + Thermostat.attributes.SystemMode.HEAT)}) +end) + +-------------------------------------------------------------------------------- +-- Third child thermostat device + +test.register_coroutine_test("Refresh should read all necessary attributes with third child device", function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__queue_receive({mock_third_child.id, { + capability = "refresh", + component = "main", + command = "refresh", + args = {} + }}) + local attributes = {Thermostat.attributes.OccupiedHeatingSetpoint, Thermostat.attributes.LocalTemperature, + Thermostat.attributes.ControlSequenceOfOperation, Thermostat.attributes.ThermostatRunningState, + Thermostat.attributes.SystemMode} + for _, attribute in pairs(attributes) do + test.socket.zigbee:__expect_send({mock_device.id, attribute:read(mock_third_child)}) + end +end) + +test.register_coroutine_test("Temperature reporting should create the appropriate events with third child device", function() + test.socket.zigbee:__queue_receive({mock_third_child.id, + Thermostat.attributes.LocalTemperature:build_test_attr_report(mock_third_child, 2100)}) + test.socket.capability:__expect_send(mock_third_child:generate_test_message("main", + capabilities.temperatureMeasurement.temperature({ + value = 21.0, + unit = "C" + }))) +end) + +test.register_coroutine_test("Thermostat mode reporting should create the appropriate events with third child device", function() + test.socket.zigbee:__queue_receive({mock_third_child.id, + Thermostat.attributes.SystemMode:build_test_attr_report(mock_third_child, + Thermostat.attributes.SystemMode.OFF)}) + test.socket.capability:__expect_send(mock_third_child:generate_test_message("main", capabilities.thermostatMode + .thermostatMode.away())) + test.socket.zigbee:__queue_receive({mock_third_child.id, + Thermostat.attributes.SystemMode:build_test_attr_report(mock_third_child, + Thermostat.attributes.SystemMode.HEAT)}) + test.socket.capability:__expect_send(mock_third_child:generate_test_message("main", capabilities.thermostatMode + .thermostatMode.heat())) +end) + +test.register_coroutine_test("ControlSequenceOfOperation reporting should create the appropriate events with third child device", function() + test.socket.zigbee:__queue_receive({mock_third_child.id, + Thermostat.attributes.ControlSequenceOfOperation:build_test_attr_report( + mock_third_child, 0x02)}) + test.socket.capability:__expect_send(mock_third_child:generate_test_message("main", + capabilities.thermostatMode.supportedThermostatModes({"away", "heat"}, { + visibility = { + displayed = false + } + }))) +end) + +test.register_coroutine_test("OccupiedHeatingSetpoint reporting shoulb create the appropriate events with third child device", function() + test.socket.zigbee:__queue_receive({mock_third_child.id, + Thermostat.attributes.OccupiedHeatingSetpoint:build_test_attr_report(mock_third_child, + 2100)}) + test.socket.capability:__expect_send(mock_third_child:generate_test_message("main", + capabilities.thermostatHeatingSetpoint.heatingSetpoint({ + value = 21.0, + unit = "C" + }))) +end) + +test.register_coroutine_test("Setting the heating setpoint should generate the appropriate messages with third child device", function() + test.socket.capability:__queue_receive({mock_third_child.id, { + component = "main", + capability = capabilities.thermostatHeatingSetpoint.ID, + command = "setHeatingSetpoint", + args = {21} + }}) + test.socket.zigbee:__expect_send({mock_device.id, + Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_third_child, 2100)}) +end) + +test.register_coroutine_test("Setting the thermostat mode to away should generate the appropriate messages with third child device", function() + test.socket.capability:__queue_receive({mock_third_child.id, { + component = "main", + capability = capabilities.thermostatMode.ID, + command = "setThermostatMode", + args = {"away"} + }}) + test.socket.zigbee:__expect_send({mock_device.id, + Thermostat.attributes.SystemMode:write(mock_third_child, + Thermostat.attributes.SystemMode.OFF)}) +end) + +test.register_coroutine_test("Setting the thermostat mode to heat should generate the appropriate messages with third child device", function() + test.socket.capability:__queue_receive({mock_third_child.id, { + component = "main", + capability = capabilities.thermostatMode.ID, + command = "heat", + args = {} + }}) + test.socket.zigbee:__expect_send({mock_device.id, + Thermostat.attributes.SystemMode:write(mock_third_child, + Thermostat.attributes.SystemMode.HEAT)}) +end) + +-------------------------------------------------------------------------------- +-- Forth child thermostat device + +test.register_coroutine_test("Refresh should read all necessary attributes with forth child device", function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__queue_receive({mock_forth_child.id, { + capability = "refresh", + component = "main", + command = "refresh", + args = {} + }}) + local attributes = {Thermostat.attributes.OccupiedHeatingSetpoint, Thermostat.attributes.LocalTemperature, + Thermostat.attributes.ControlSequenceOfOperation, Thermostat.attributes.ThermostatRunningState, + Thermostat.attributes.SystemMode} + for _, attribute in pairs(attributes) do + test.socket.zigbee:__expect_send({mock_device.id, attribute:read(mock_forth_child)}) + end +end) + +test.register_coroutine_test("Temperature reporting should create the appropriate events with forth child device", function() + test.socket.zigbee:__queue_receive({mock_forth_child.id, + Thermostat.attributes.LocalTemperature:build_test_attr_report(mock_forth_child, 2100)}) + test.socket.capability:__expect_send(mock_forth_child:generate_test_message("main", + capabilities.temperatureMeasurement.temperature({ + value = 21.0, + unit = "C" + }))) +end) + +test.register_coroutine_test("Thermostat mode reporting should create the appropriate events with forth child device", function() + test.socket.zigbee:__queue_receive({mock_forth_child.id, + Thermostat.attributes.SystemMode:build_test_attr_report(mock_forth_child, + Thermostat.attributes.SystemMode.OFF)}) + test.socket.capability:__expect_send(mock_forth_child:generate_test_message("main", capabilities.thermostatMode + .thermostatMode.away())) + test.socket.zigbee:__queue_receive({mock_forth_child.id, + Thermostat.attributes.SystemMode:build_test_attr_report(mock_forth_child, + Thermostat.attributes.SystemMode.HEAT)}) + test.socket.capability:__expect_send(mock_forth_child:generate_test_message("main", capabilities.thermostatMode + .thermostatMode.heat())) +end) + +test.register_coroutine_test("ControlSequenceOfOperation reporting should create the appropriate events with forth child device", function() + test.socket.zigbee:__queue_receive({mock_forth_child.id, + Thermostat.attributes.ControlSequenceOfOperation:build_test_attr_report( + mock_forth_child, 0x02)}) + test.socket.capability:__expect_send(mock_forth_child:generate_test_message("main", + capabilities.thermostatMode.supportedThermostatModes({"away", "heat"}, { + visibility = { + displayed = false + } + }))) +end) + +test.register_coroutine_test("OccupiedHeatingSetpoint reporting shoulb create the appropriate events with forth child device", function() + test.socket.zigbee:__queue_receive({mock_forth_child.id, + Thermostat.attributes.OccupiedHeatingSetpoint:build_test_attr_report(mock_forth_child, + 2100)}) + test.socket.capability:__expect_send(mock_forth_child:generate_test_message("main", + capabilities.thermostatHeatingSetpoint.heatingSetpoint({ + value = 21.0, + unit = "C" + }))) +end) + +test.register_coroutine_test("Setting the heating setpoint should generate the appropriate messages with forth child device", function() + test.socket.capability:__queue_receive({mock_forth_child.id, { + component = "main", + capability = capabilities.thermostatHeatingSetpoint.ID, + command = "setHeatingSetpoint", + args = {21} + }}) + test.socket.zigbee:__expect_send({mock_device.id, + Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_forth_child, 2100)}) +end) + +test.register_coroutine_test("Setting the thermostat mode to away should generate the appropriate messages with forth child device", function() + test.socket.capability:__queue_receive({mock_forth_child.id, { + component = "main", + capability = capabilities.thermostatMode.ID, + command = "setThermostatMode", + args = {"away"} + }}) + test.socket.zigbee:__expect_send({mock_device.id, + Thermostat.attributes.SystemMode:write(mock_forth_child, + Thermostat.attributes.SystemMode.OFF)}) +end) + +test.register_coroutine_test("Setting the thermostat mode to heat should generate the appropriate messages with forth child device", function() + test.socket.capability:__queue_receive({mock_forth_child.id, { + component = "main", + capability = capabilities.thermostatMode.ID, + command = "heat", + args = {} + }}) + test.socket.zigbee:__expect_send({mock_device.id, + Thermostat.attributes.SystemMode:write(mock_forth_child, + Thermostat.attributes.SystemMode.HEAT)}) +end) + +-------------------------------------------------------------------------------- +-- Fifth child thermostat device + +test.register_coroutine_test("Refresh should read all necessary attributes with fifth child device", function() + test.socket.zigbee:__set_channel_ordering("relaxed") + test.socket.capability:__queue_receive({mock_fifth_child.id, { + capability = "refresh", + component = "main", + command = "refresh", + args = {} + }}) + local attributes = {Thermostat.attributes.OccupiedHeatingSetpoint, Thermostat.attributes.LocalTemperature, + Thermostat.attributes.ControlSequenceOfOperation, Thermostat.attributes.ThermostatRunningState, + Thermostat.attributes.SystemMode} + for _, attribute in pairs(attributes) do + test.socket.zigbee:__expect_send({mock_device.id, attribute:read(mock_fifth_child)}) + end +end) + +test.register_coroutine_test("Temperature reporting should create the appropriate events with fifth child device", function() + test.socket.zigbee:__queue_receive({mock_fifth_child.id, + Thermostat.attributes.LocalTemperature:build_test_attr_report(mock_fifth_child, 2100)}) + test.socket.capability:__expect_send(mock_fifth_child:generate_test_message("main", + capabilities.temperatureMeasurement.temperature({ + value = 21.0, + unit = "C" + }))) +end) + +test.register_coroutine_test("Thermostat mode reporting should create the appropriate events with fifth child device", function() + test.socket.zigbee:__queue_receive({mock_fifth_child.id, + Thermostat.attributes.SystemMode:build_test_attr_report(mock_fifth_child, + Thermostat.attributes.SystemMode.OFF)}) + test.socket.capability:__expect_send(mock_fifth_child:generate_test_message("main", capabilities.thermostatMode + .thermostatMode.away())) + test.socket.zigbee:__queue_receive({mock_fifth_child.id, + Thermostat.attributes.SystemMode:build_test_attr_report(mock_fifth_child, + Thermostat.attributes.SystemMode.HEAT)}) + test.socket.capability:__expect_send(mock_fifth_child:generate_test_message("main", capabilities.thermostatMode + .thermostatMode.heat())) +end) + +test.register_coroutine_test("ControlSequenceOfOperation reporting should create the appropriate events with fifth child device", function() + test.socket.zigbee:__queue_receive({mock_fifth_child.id, + Thermostat.attributes.ControlSequenceOfOperation:build_test_attr_report( + mock_fifth_child, 0x02)}) + test.socket.capability:__expect_send(mock_fifth_child:generate_test_message("main", + capabilities.thermostatMode.supportedThermostatModes({"away", "heat"}, { + visibility = { + displayed = false + } + }))) +end) + +test.register_coroutine_test("OccupiedHeatingSetpoint reporting shoulb create the appropriate events with fifth child device", function() + test.socket.zigbee:__queue_receive({mock_fifth_child.id, + Thermostat.attributes.OccupiedHeatingSetpoint:build_test_attr_report(mock_fifth_child, + 2100)}) + test.socket.capability:__expect_send(mock_fifth_child:generate_test_message("main", + capabilities.thermostatHeatingSetpoint.heatingSetpoint({ + value = 21.0, + unit = "C" + }))) +end) + +test.register_coroutine_test("Setting the heating setpoint should generate the appropriate messages with fifth child device", function() + test.socket.capability:__queue_receive({mock_fifth_child.id, { + component = "main", + capability = capabilities.thermostatHeatingSetpoint.ID, + command = "setHeatingSetpoint", + args = {21} + }}) + test.socket.zigbee:__expect_send({mock_device.id, + Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_fifth_child, 2100)}) +end) + +test.register_coroutine_test("Setting the thermostat mode to away should generate the appropriate messages with fifth child device", function() + test.socket.capability:__queue_receive({mock_fifth_child.id, { + component = "main", + capability = capabilities.thermostatMode.ID, + command = "setThermostatMode", + args = {"away"} + }}) + test.socket.zigbee:__expect_send({mock_device.id, + Thermostat.attributes.SystemMode:write(mock_fifth_child, + Thermostat.attributes.SystemMode.OFF)}) +end) + +test.register_coroutine_test("Setting the thermostat mode to heat should generate the appropriate messages with fifth child device", function() + test.socket.capability:__queue_receive({mock_fifth_child.id, { + component = "main", + capability = capabilities.thermostatMode.ID, + command = "heat", + args = {} + }}) + test.socket.zigbee:__expect_send({mock_device.id, + Thermostat.attributes.SystemMode:write(mock_fifth_child, + Thermostat.attributes.SystemMode.HEAT)}) +end) + + +test.run_registered_tests() From 0b18e67da35aa8b002ff3e204b77e2191554aa60 Mon Sep 17 00:00:00 2001 From: kdbang Date: Fri, 8 Mar 2024 00:07:45 +0900 Subject: [PATCH 12/21] Cannot onboading JBL 300 (#1261) Signed-off-by: Keuckdo Bang --- drivers/SmartThings/jbl/src/lunchbox/rest.lua | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/drivers/SmartThings/jbl/src/lunchbox/rest.lua b/drivers/SmartThings/jbl/src/lunchbox/rest.lua index 1128dd7f96..f3db77745b 100644 --- a/drivers/SmartThings/jbl/src/lunchbox/rest.lua +++ b/drivers/SmartThings/jbl/src/lunchbox/rest.lua @@ -144,6 +144,38 @@ local function parse_chunked_response(original_response, sock) return full_response end +local function recv_additional_response(original_response, sock) + local full_response = Response.new(original_response.status, nil) + local headers = original_response:get_headers() + local content_length_str = headers:get_one("Content-Length") + local content_length = nil + local bytes_read = 0 + if content_length_str then + content_length = math.tointeger(content_length_str) + end + + local next_recv, next_err, partial + + repeat + next_recv, next_err, partial = sock:receive(content_length - bytes_read) + + if next_recv ~= nil and #next_recv >= 1 then + full_response:append_body(next_recv) + bytes_read = bytes_read + #next_recv + end + + if partial ~= nil and #partial >= 1 then + full_response:append_body(partial) + bytes_read = bytes_read + #partial + end + until next_err == "closed" or bytes_read >= content_length + + full_response._received_body = true + full_response._parsed_headers = true + + return full_response +end + local function handle_response(sock) if api_version >= 9 then local response, err = Response.tcp_source(sock) @@ -158,7 +190,9 @@ local function handle_response(sock) if initial_recv ~= nil then local headers = initial_recv:get_headers() - if headers and headers:get_one("Transfer-Encoding") == "chunked" then + if headers:get_one("Content-Length") then + full_response = recv_additional_response(initial_recv, sock) + elseif headers and headers:get_one("Transfer-Encoding") == "chunked" then local response, err = parse_chunked_response(initial_recv, sock) if err ~= nil then return nil, err From c0f8d55a939f3ad9f1cddafb520e6d09fb0801cd Mon Sep 17 00:00:00 2001 From: Nick DeBoom Date: Fri, 8 Mar 2024 15:33:13 -0600 Subject: [PATCH 13/21] Matter Switch: Set switchLevel range to 1-100 for light matter profiles Updating the "light" matter profiles in the matter switch driver to set the supported range of switchLevel to 1-100 to resolve CHAD-12814. --- .../matter-switch/profiles/light-color-level-2000K-7000K.yml | 4 ++++ .../SmartThings/matter-switch/profiles/light-color-level.yml | 4 ++++ .../profiles/light-level-ColorTemperature-1500-9000k.yml | 4 ++++ .../matter-switch/profiles/light-level-colorTemperature.yml | 4 ++++ drivers/SmartThings/matter-switch/profiles/light-level.yml | 4 ++++ 5 files changed, 20 insertions(+) diff --git a/drivers/SmartThings/matter-switch/profiles/light-color-level-2000K-7000K.yml b/drivers/SmartThings/matter-switch/profiles/light-color-level-2000K-7000K.yml index c987ffbfea..3fb0742f0d 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-color-level-2000K-7000K.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-color-level-2000K-7000K.yml @@ -6,6 +6,10 @@ components: version: 1 - id: switchLevel version: 1 + config: + values: + - key: "level.value" + range: [1, 100] - id: colorTemperature version: 1 config: diff --git a/drivers/SmartThings/matter-switch/profiles/light-color-level.yml b/drivers/SmartThings/matter-switch/profiles/light-color-level.yml index 83eaa79381..27e3680d63 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-color-level.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-color-level.yml @@ -6,6 +6,10 @@ components: version: 1 - id: switchLevel version: 1 + config: + values: + - key: "level.value" + range: [1, 100] - id: colorTemperature version: 1 - id: colorControl diff --git a/drivers/SmartThings/matter-switch/profiles/light-level-ColorTemperature-1500-9000k.yml b/drivers/SmartThings/matter-switch/profiles/light-level-ColorTemperature-1500-9000k.yml index 62d8bcc72c..e2542bef5c 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-level-ColorTemperature-1500-9000k.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-level-ColorTemperature-1500-9000k.yml @@ -6,6 +6,10 @@ components: version: 1 - id: switchLevel version: 1 + config: + values: + - key: "level.value" + range: [1, 100] - id: colorTemperature version: 1 config: diff --git a/drivers/SmartThings/matter-switch/profiles/light-level-colorTemperature.yml b/drivers/SmartThings/matter-switch/profiles/light-level-colorTemperature.yml index 1e093f406e..2c32b7b45e 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-level-colorTemperature.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-level-colorTemperature.yml @@ -6,6 +6,10 @@ components: version: 1 - id: switchLevel version: 1 + config: + values: + - key: "level.value" + range: [1, 100] - id: colorTemperature version: 1 - id: firmwareUpdate diff --git a/drivers/SmartThings/matter-switch/profiles/light-level.yml b/drivers/SmartThings/matter-switch/profiles/light-level.yml index 1227cad1cc..e266f497c9 100644 --- a/drivers/SmartThings/matter-switch/profiles/light-level.yml +++ b/drivers/SmartThings/matter-switch/profiles/light-level.yml @@ -6,6 +6,10 @@ components: version: 1 - id: switchLevel version: 1 + config: + values: + - key: "level.value" + range: [1, 100] - id: firmwareUpdate version: 1 - id: refresh From e58b967971701e1dac896e9a8fcc71439199622b Mon Sep 17 00:00:00 2001 From: lelandblue <79465613+lelandblue@users.noreply.github.com> Date: Mon, 11 Mar 2024 10:28:11 -0400 Subject: [PATCH 14/21] CYnc-Matter-Bulbs-1.01.170 (#1232) --- .../matter-switch/fingerprints.yml | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index c42af115d9..34c1fc5447 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -92,6 +92,41 @@ matterManufacturer: vendorId: 0x1339 productId: 0xAC deviceProfileName: plug-binary + - id: "4921/46" + deviceLabel: Cync Full Color RS Can + vendorId: 0x1339 + productId: 0x002E + deviceProfileName: light-color-level-2000K-7000K + - id: "4921/98" + deviceLabel: Cync Full Color Globe + vendorId: 0x1339 + productId: 0x0062 + deviceProfileName: light-color-level-2000K-7000K + - id: "4921/171" + deviceLabel: Cync Full Color A19 + vendorId: 0x1339 + productId: 0x00AB + deviceProfileName: light-color-level-2000K-7000K + - id: "4921/97" + deviceLabel: Cync Full Color ST19 + vendorId: 0x1339 + productId: 0x0061 + deviceProfileName: light-color-level-2000K-7000K + - id: "4921/104" + deviceLabel: Cync Full Color PAR38 + vendorId: 0x1339 + productId: 0x0068 + deviceProfileName: light-color-level-2000K-7000K + - id: "4921/110" + deviceLabel: Cync Indoor Light Strip 16ft + vendorId: 0x1339 + productId: 0x006E + deviceProfileName: light-color-level-2000K-7000K + - id: "4921/123" + deviceLabel: Cync Indoor Light Strip 32ft + vendorId: 0x1339 + productId: 0x007B + deviceProfileName: light-color-level-2000K-7000K - id: "4921/111" deviceLabel: Cync Outdoor Plug vendorId: 0x1339 From 7b38f82ddafed1134a8e31fb255f34d607536687 Mon Sep 17 00:00:00 2001 From: seungkwan-choi <136560804+seungkwan-choi@users.noreply.github.com> Date: Thu, 14 Mar 2024 23:09:42 +0900 Subject: [PATCH 15/21] Create to Zigbee fan for SLED (#1115) * Add Zigbee fan Add Zigbee fan * Update configurations.lua change date * Delete drivers/SmartThings/zigbee-fan/README.md Delete README file * Update config.yml generic in this config * Update init.lua change code * Update config.yml dfd * Update config.yml Modify config file to eneric * Update itm-fan-light.yml Changing profile * Update fingerprints.yml changing fingerprints * Update init.lua changing init file * Update configurations.lua change year * Update init.lua change year * Update init.lua change year * Create test_fan_light.lua Added the test code for the fan light * Update test_fan_light.lua update test code * Update init.lua update init code * Update init.lua update init code * Update test_fan_light.lua update test fan light code * Update init.lua update init code * Update and rename itm-fan-light.yml to fan-light.yml update profiles code(revise using multi-component) * Delete drivers/SmartThings/zigbee-fan/profiles/switch-level.yml Delete child device * Update fingerprints.yml update fingerprint(name change) * Update test_fan_light.lua update test code * Update init.lua Update init file(name change) * Update and rename init.lua to init.lua Update and rename init file (Revise using multi-component) * Update init.lua update init (update change in fan speed status value * Update test_fan_light.lua update test code (changes in test code due to changes in init code) * Update init.lua I have modified contents as per your request. Please review it. * Update test_fan_light.lua I have modified the test code to match init. * Update fan-light.yml I have modified the speed level to 3. --------- Co-authored-by: lelandblue <79465613+lelandblue@users.noreply.github.com> --- drivers/SmartThings/zigbee-fan/config.yml | 7 + .../SmartThings/zigbee-fan/fingerprints.yml | 7 + .../zigbee-fan/profiles/fan-light.yml | 34 ++ .../zigbee-fan/src/configurations.lua | 66 +++ .../zigbee-fan/src/fan-light/init.lua | 119 +++++ drivers/SmartThings/zigbee-fan/src/init.lua | 47 ++ .../zigbee-fan/src/test/test_fan_light.lua | 447 ++++++++++++++++++ 7 files changed, 727 insertions(+) create mode 100644 drivers/SmartThings/zigbee-fan/config.yml create mode 100644 drivers/SmartThings/zigbee-fan/fingerprints.yml create mode 100644 drivers/SmartThings/zigbee-fan/profiles/fan-light.yml create mode 100644 drivers/SmartThings/zigbee-fan/src/configurations.lua create mode 100644 drivers/SmartThings/zigbee-fan/src/fan-light/init.lua create mode 100644 drivers/SmartThings/zigbee-fan/src/init.lua create mode 100644 drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua diff --git a/drivers/SmartThings/zigbee-fan/config.yml b/drivers/SmartThings/zigbee-fan/config.yml new file mode 100644 index 0000000000..14d5bac020 --- /dev/null +++ b/drivers/SmartThings/zigbee-fan/config.yml @@ -0,0 +1,7 @@ +name: 'Zigbee Fan' +packageKey: 'zigbee-fan' +permissions: + zigbee: {} +description: "SmartThings driver for zigbee fan devices" +vendorSupportInformation: "https://support.smartthings.com" + diff --git a/drivers/SmartThings/zigbee-fan/fingerprints.yml b/drivers/SmartThings/zigbee-fan/fingerprints.yml new file mode 100644 index 0000000000..64b64c6258 --- /dev/null +++ b/drivers/SmartThings/zigbee-fan/fingerprints.yml @@ -0,0 +1,7 @@ +zigbeeManufacturer: + # FAN LIGHT + - id: "Samsung/SAMSUNG-ITM-Z-003" + deviceLabel: Fan light + manufacturer: Samsung Electronics + model: SAMSUNG-ITM-Z-003 + deviceProfileName: fan-light diff --git a/drivers/SmartThings/zigbee-fan/profiles/fan-light.yml b/drivers/SmartThings/zigbee-fan/profiles/fan-light.yml new file mode 100644 index 0000000000..259a0f7dbb --- /dev/null +++ b/drivers/SmartThings/zigbee-fan/profiles/fan-light.yml @@ -0,0 +1,34 @@ +name: fan-light +components: + - id: main + label: Fan + capabilities: + - id: switch + version: 1 + - id: fanSpeed + version: 1 + config: + values: + - key: "fanSpeed.value" + range: [0, 3] + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Fan + - id: light + label: Light + capabilities: + - id: switch + version: 1 + - id: switchLevel + version: 1 + config: + values: + - key: "level.value" + range: [0, 100] + - id: refresh + version: 1 + categories: + - name: Light diff --git a/drivers/SmartThings/zigbee-fan/src/configurations.lua b/drivers/SmartThings/zigbee-fan/src/configurations.lua new file mode 100644 index 0000000000..c2cbb57de0 --- /dev/null +++ b/drivers/SmartThings/zigbee-fan/src/configurations.lua @@ -0,0 +1,66 @@ +-- 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 clusters = require "st.zigbee.zcl.clusters" + +local OnOff = clusters.OnOff +local Level = clusters.Level +local FanControl = clusters.FanControl + +local devices = { + ITM_FAN_LIGHT = { + FINGERPRINTS = { + { mfr = "Samsung Electronics", model = "SAMSUNG-ITM-Z-003" }, + }, + CONFIGURATION = { + { + cluster = OnOff.ID, + attribute = OnOff.attributes.OnOff.ID, + minimum_interval = 0, + maximum_interval = 600, + data_type = OnOff.attributes.OnOff.base_type + }, + { + cluster = Level.ID, + attribute = Level.attributes.CurrentLevel.ID, + minimum_interval = 1, + maximum_interval = 600, + data_type = Level.attributes.CurrentLevel.base_type, + reportable_change = 1 + }, + { + cluster = FanControl.ID, + attribute = FanControl.attributes.FanMode.ID, + minimum_interval = 1, + maximum_interval = 600, + data_type = FanControl.attributes.FanMode.base_type + } + } + }, +} + +local configurations = {} + +configurations.get_device_configuration = function(zigbee_device) + for _, device in pairs(devices) do + for _, fingerprint in pairs(device.FINGERPRINTS) do + if zigbee_device:get_manufacturer() == fingerprint.mfr and zigbee_device:get_model() == fingerprint.model then + return device.CONFIGURATION + end + end + end + return nil +end + +return configurations diff --git a/drivers/SmartThings/zigbee-fan/src/fan-light/init.lua b/drivers/SmartThings/zigbee-fan/src/fan-light/init.lua new file mode 100644 index 0000000000..95329c1ddf --- /dev/null +++ b/drivers/SmartThings/zigbee-fan/src/fan-light/init.lua @@ -0,0 +1,119 @@ +-- 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 clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" +local FanControl = clusters.FanControl +local Level = clusters.Level +local OnOff = clusters.OnOff + +local FINGERPRINTS = { + { mfr = "Samsung Electronics", model = "SAMSUNG-ITM-Z-003" }, +} + +local function can_handle_itm_fanlight(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 + +-- CAPABILITY HANDLERS + +local function on_handler(driver, device, command) + if command.component == 'light' then + device:send(OnOff.server.commands.On(device)) + else + local speed = device:get_field('LAST_FAN_SPD') or 1 + device:send(FanControl.attributes.FanMode:write(device, speed)) + end + device:send(FanControl.attributes.FanMode:read(device)) +end + +local function off_handler(driver, device, command) + if command.component == 'light' then + device:send(OnOff.server.commands.Off(device)) + else + device:send(FanControl.attributes.FanMode:write(device, FanControl.attributes.FanMode.OFF)) + end + device:send(FanControl.attributes.FanMode:read(device)) +end + +local function switch_level_handler(driver, device, command) + local level = math.floor(command.args.level/100.0 * 254) + device:send(Level.server.commands.MoveToLevelWithOnOff(device, level, command.args.rate or 0xFFFF)) +end + +local function fan_speed_handler(driver, device, command) + device:send(FanControl.attributes.FanMode:write(device, command.args.speed)) + device:send(FanControl.attributes.FanMode:read(device)) +end + +-- ZIGBEE HANDLERS + +local function zb_fan_control_handler(driver, device, value, zb_rx) + device:emit_event(capabilities.fanSpeed.fanSpeed(value.value)) + local evt = capabilities.switch.switch(value.value > 0 and 'on' or 'off', { visibility = { displayed = true } }) + device:emit_component_event(device.profile.components.main, evt) + device:emit_component_event(device.profile.components.main, capabilities.fanSpeed.fanSpeed(value.value)) + if value.value > 0 then + device:set_field('LAST_FAN_SPD', value.value, {persist = true}) + end +end + +local function zb_level_handler(driver, device, value, zb_rx) + local evt = capabilities.switchLevel.level(math.floor((value.value / 254.0 * 100) + 0.5)) + device:emit_component_event(device.profile.components.light, evt) +end + +local function zb_onoff_handler(driver, device, value, zb_rx) + local attr = capabilities.switch.switch + local evt = value.value and attr.on() or attr.off() + device:emit_component_event(device.profile.components.light, evt) +end + +local itm_fan_light = { + NAME = "ITM Fan Light", + zigbee_handlers = { + attr = { + [FanControl.ID] = { + [FanControl.attributes.FanMode.ID] = zb_fan_control_handler + }, + [Level.ID] = { + [Level.attributes.CurrentLevel.ID] = zb_level_handler + }, + [OnOff.ID] = { + [OnOff.attributes.OnOff.ID] = zb_onoff_handler + } + } + }, + capability_handlers = { + [capabilities.switch.ID] = { + [capabilities.switch.commands.on.NAME] = on_handler, + [capabilities.switch.commands.off.NAME] = off_handler, + }, + [capabilities.switchLevel.ID] = { + [capabilities.switchLevel.commands.setLevel.NAME] = switch_level_handler + }, + [capabilities.fanSpeed.ID] = { + [capabilities.fanSpeed.commands.setFanSpeed.NAME] = fan_speed_handler + } + }, + can_handle = can_handle_itm_fanlight +} + +return itm_fan_light + + diff --git a/drivers/SmartThings/zigbee-fan/src/init.lua b/drivers/SmartThings/zigbee-fan/src/init.lua new file mode 100644 index 0000000000..eff62434ef --- /dev/null +++ b/drivers/SmartThings/zigbee-fan/src/init.lua @@ -0,0 +1,47 @@ +-- 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 ZigbeeDriver = require "st.zigbee" +local defaults = require "st.zigbee.defaults" +local configurationMap = require "configurations" + +local device_init = function(self, device) + local configuration = configurationMap.get_device_configuration(device) + if configuration ~= nil then + for _, attribute in ipairs(configuration) do + device:add_configured_attribute(attribute) + device:add_monitored_attribute(attribute) + end + end +end + +local zigbee_fan_driver = { + supported_capabilities = { + capabilities.switch, + capabilities.switchLevel, + capabilities.fanspeed + }, + sub_drivers = { + require("fan-light") + }, + lifecycle_handlers = { + init = device_init + } +} + +defaults.register_for_default_handlers(zigbee_fan_driver,zigbee_fan_driver.supported_capabilities) +local fan = ZigbeeDriver("zigbee-fan", zigbee_fan_driver) +fan:run() + diff --git a/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua b/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua new file mode 100644 index 0000000000..f83a33074b --- /dev/null +++ b/drivers/SmartThings/zigbee-fan/src/test/test_fan_light.lua @@ -0,0 +1,447 @@ +-- 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 test = require "integration_test" +local t_utils = require "integration_test.utils" +local clusters = require "st.zigbee.zcl.clusters" +local capabilities = require "st.capabilities" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local FanControl = clusters.FanControl +local OnOff = clusters.OnOff +local Level = clusters.Level + +-- create test device (Multifunction) +local mock_base_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("fan-light.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "Samsung Electronics", + model = "SAMSUNG-ITM-Z-003", + server_clusters = { 0x0000, 0x0003, 0x0006, 0x0008, 0x0202, 0x0300 } + } + }, + fingerprinted_endpoint_id = 0x01 + } +) + +zigbee_test_utils.prepare_zigbee_env_info() + +local function test_init() + test.mock_device.add_test_device(mock_base_device) + zigbee_test_utils.init_noop_health_check_timer() +end +test.set_test_init_function(test_init) + +-- create test commands +test.register_message_test( + " Light switchLevel command : 100% ", + { + { + channel = "capability", + direction = "receive", + message = { mock_base_device.id, { capability = "switchLevel", component = "light", + command = "setLevel", args = { 100, 0 } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, Level.server.commands.MoveToLevelWithOnOff + (mock_base_device, 254, 0) } + } + } +) + +test.register_message_test( + " Light switchLevel command : 50% ", + { + { + channel = "capability", + direction = "receive", + message = { mock_base_device.id, { capability = "switchLevel", component = "light", + command = "setLevel", args = { 50, 0 } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, Level.server.commands.MoveToLevelWithOnOff + (mock_base_device, 127, 0) } + } + } +) + +test.register_message_test( + " Light switchLevel command : 50% ", + { + { + channel = "capability", + direction = "receive", + message = { mock_base_device.id, { capability = "switchLevel", component = "light", + command = "setLevel", args = { 50, 0 } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, Level.server.commands.MoveToLevelWithOnOff + (mock_base_device, 127, 0) } + } + } +) + +test.register_message_test( + " Light switchLevel command : 0% ", + { + { + channel = "capability", + direction = "receive", + message = { mock_base_device.id, { capability = "switchLevel", component = "light", + command = "setLevel", args = { 0, 0 } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, Level.server.commands.MoveToLevelWithOnOff + (mock_base_device, 0, 0) } + } + } +) + +test.register_message_test( + " Light OnOff command : Off ", + { + { + channel = "capability", + direction = "receive", + message = { mock_base_device.id, { capability = "switch", component = "light", command = "off", + args = {} } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, OnOff.server.commands.Off(mock_base_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, FanControl.attributes.FanMode:read(mock_base_device) } + } + } +) + +test.register_message_test( + " Light OnOff command : On ", + { + { + channel = "capability", + direction = "receive", + message = { mock_base_device.id, { capability = "switch", component = "light", command = "on", + args = {} } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, OnOff.server.commands.On(mock_base_device) } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, FanControl.attributes.FanMode:read(mock_base_device) } + } + } +) + +test.register_message_test( + " Light switch attribute : on ", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_base_device.id, OnOff.attributes.OnOff: + build_test_attr_report(mock_base_device, true):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("light", capabilities.switch.switch.on()) + } + } +) + +test.register_message_test( + " Light switch attribute : off ", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_base_device.id, OnOff.attributes.OnOff: + build_test_attr_report(mock_base_device, false):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("light", capabilities.switch.switch.off()) + } + } +) + +test.register_message_test( + " Light level attribute : 100% ", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_base_device.id, Level.attributes.CurrentLevel: + build_test_attr_report(mock_base_device, 254):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("light", capabilities.switchLevel.level(100)) + } + } +) + +test.register_message_test( + " Light level attribute : 50% ", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_base_device.id, Level.attributes.CurrentLevel: + build_test_attr_report(mock_base_device, 127):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("light", capabilities.switchLevel.level(50)) + } + } +) + +test.register_message_test( + " Light level attribute : 0%", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_base_device.id, Level.attributes.CurrentLevel: + build_test_attr_report(mock_base_device, 0):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("light", capabilities.switchLevel.level(0)) + } + } +) + +test.register_message_test( + " FanSpeed command : 0% ", + { + { + channel = "capability", + direction = "receive", + message = { mock_base_device.id, { capability = "fanSpeed", command = "setFanSpeed", args = { 0 } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, FanControl.attributes.FanMode:write(mock_base_device, 0x00) } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, FanControl.attributes.FanMode:read(mock_base_device) } + } + } +) + +test.register_message_test( + " 'FanSpeed command : Low ", + { + { + channel = "capability", + direction = "receive", + message = { mock_base_device.id, { capability = "fanSpeed", command = "setFanSpeed", args = { 1 } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, FanControl.attributes.FanMode:write(mock_base_device, 0x01) } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, FanControl.attributes.FanMode:read(mock_base_device) } + } + } +) + +test.register_message_test( + " 'FanSpeed command : High ", + { + { + channel = "capability", + direction = "receive", + message = { mock_base_device.id, { capability = "fanSpeed", command = "setFanSpeed", args = { 3 } } } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, FanControl.attributes.FanMode:write(mock_base_device, 0x03) } + }, + { + channel = "zigbee", + direction = "send", + message = { mock_base_device.id, FanControl.attributes.FanMode:read(mock_base_device) } + } + } +) + +test.register_message_test( + " FanSpeed attribute : LOW", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_base_device.id, FanControl.attributes.FanMode: + build_test_attr_report(mock_base_device, 1):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(1)) + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.switch.switch.on({ visibility = { displayed = true } })) + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(1)) + } + } +) + +test.register_message_test( + " FanSpeed attribute : Middle", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_base_device.id, FanControl.attributes.FanMode: + build_test_attr_report(mock_base_device, 2):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(2)) + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.switch.switch.on({ visibility = { displayed = true } })) + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(2)) + } + } +) + +test.register_message_test( + " FanSpeed attribute : High", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_base_device.id, FanControl.attributes.FanMode: + build_test_attr_report(mock_base_device, 3):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(3)) + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.switch.switch.on({ visibility = { displayed = true } })) + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(3)) + } + } +) + +test.register_message_test( + " Fan control attribute : On", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_base_device.id, FanControl.attributes.FanMode: + build_test_attr_report(mock_base_device, 4):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(4)) + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.switch.switch.on({ visibility = { displayed = true } })) + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(4)) + } + } +) + +test.register_message_test( + " Fan control attribute : Off", + { + { + channel = "zigbee", + direction = "receive", + message = { mock_base_device.id, FanControl.attributes.FanMode: + build_test_attr_report(mock_base_device, 0):from_endpoint(0x01) } + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(0)) + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.switch.switch.off({ visibility = { displayed = true } })) + }, + { + channel = "capability", + direction = "send", + message = mock_base_device:generate_test_message("main", capabilities.fanSpeed.fanSpeed(0)) + } + } +) + +test.run_registered_tests() From ae399f05a1ea7541bd2720f71cb6dc593297c3cb Mon Sep 17 00:00:00 2001 From: Hongming6 <150672162+Hongming6@users.noreply.github.com> Date: Thu, 14 Mar 2024 22:14:51 +0800 Subject: [PATCH 16/21] Aqara Smart Natural Gas Detector (#1160) * Aqara Smart Natural Gas Detector * Fix formatting issues * Example Delete the configurations that support self-check * Adding CN translation for Aqara Smart Natural Gas Detector * Update the self-check code of the device * Update self-check * Fixed repeated setting sensitivity device does not respond, refer to -Aqara zigbee motion sensor * Update PR * Update correct device information * Update the configuration of unmute and metadata * Solve formatting problems * Fix formatting issues * Fix known issues * Update Test Code * Adding the correct CN translation for Aqara Smart Natural Gas Detector * Remove duplicate definition * Update Test Code * Delete .yml --------- Co-authored-by: lelandblue <79465613+lelandblue@users.noreply.github.com> --- .../capabilities/lifeTimeReport.yaml | 22 ++ .../capabilities/sensitivityAdjustment.yaml | 55 ++++ .../zigbee-smoke-detector/fingerprints.yml | 5 + .../profiles/gas-lifetime-selfcheck-aqara.yml | 21 ++ .../src/aqara-gas/init.lua | 189 ++++++++++++++ .../zigbee-smoke-detector/src/init.lua | 2 + .../src/test/test_aqara_gas_detector.lua | 239 ++++++++++++++++++ 7 files changed, 533 insertions(+) create mode 100644 drivers/SmartThings/zigbee-smoke-detector/capabilities/lifeTimeReport.yaml create mode 100644 drivers/SmartThings/zigbee-smoke-detector/capabilities/sensitivityAdjustment.yaml create mode 100644 drivers/SmartThings/zigbee-smoke-detector/profiles/gas-lifetime-selfcheck-aqara.yml create mode 100644 drivers/SmartThings/zigbee-smoke-detector/src/aqara-gas/init.lua create mode 100644 drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.lua diff --git a/drivers/SmartThings/zigbee-smoke-detector/capabilities/lifeTimeReport.yaml b/drivers/SmartThings/zigbee-smoke-detector/capabilities/lifeTimeReport.yaml new file mode 100644 index 0000000000..2d893adf5b --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/capabilities/lifeTimeReport.yaml @@ -0,0 +1,22 @@ +id: stse.lifeTimeReport +version: 1 +status: proposed +name: Life Time Report +ephemeral: false +attributes: + lifeTimeState: + schema: + type: object + properties: + value: + type: string + enum: + - normal + - beAboutToEnd + - endOfLife + additionalProperties: false + required: + - value + enumCommands: [] +commands: { +} diff --git a/drivers/SmartThings/zigbee-smoke-detector/capabilities/sensitivityAdjustment.yaml b/drivers/SmartThings/zigbee-smoke-detector/capabilities/sensitivityAdjustment.yaml new file mode 100644 index 0000000000..c6e973a502 --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/capabilities/sensitivityAdjustment.yaml @@ -0,0 +1,55 @@ +id: stse.sensitivityAdjustment +version: 1 +status: proposed +name: Sensitivity Adjustment +ephemeral: false +attributes: + sensitivityAdjustment: + schema: + type: object + properties: + value: + type: string + enum: + - High + - Medium + - Low + - Desc + additionalProperties: false + required: + - value + setter: setSensitivityAdjustment + enumCommands: + - command: High + value: High + - command: Medium + value: Medium + - command: Low + value: Low + - command: Desc + value: Desc +commands: + High: + name: High + arguments: [] + Desc: + name: Desc + arguments: [] + setSensitivityAdjustment: + name: setSensitivityAdjustment + arguments: + - name: sensitivity + optional: false + schema: + type: string + enum: + - High + - Medium + - Low + - Desc + Low: + name: Low + arguments: [] + Medium: + name: Medium + arguments: [] diff --git a/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml b/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml index fe6ac2f884..a8384b886c 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml +++ b/drivers/SmartThings/zigbee-smoke-detector/fingerprints.yml @@ -1,4 +1,9 @@ zigbeeManufacturer: + - id: "LUMI/lumi.sensor_gas.acn02" + deviceLabel: Aqara Smart Natural Gas Detector + manufacturer: LUMI + model: lumi.sensor_gas.acn02 + deviceProfileName: gas-lifetime-selfcheck-aqara - id: "LUMI/lumi.sensor_smoke.acn03" deviceLabel: Aqara Smart Smoke Detector manufacturer: LUMI diff --git a/drivers/SmartThings/zigbee-smoke-detector/profiles/gas-lifetime-selfcheck-aqara.yml b/drivers/SmartThings/zigbee-smoke-detector/profiles/gas-lifetime-selfcheck-aqara.yml new file mode 100644 index 0000000000..f6f482067f --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/profiles/gas-lifetime-selfcheck-aqara.yml @@ -0,0 +1,21 @@ +name: gas-lifetime-selfcheck-aqara +components: +- id: main + capabilities: + - id: gasDetector + version: 1 + - id: refresh + version: 1 + - id: audioMute + version: 1 + - id: stse.selfCheck + version: 1 + - id: stse.lifeTimeReport + version: 1 + - id: stse.sensitivityAdjustment + version: 1 +categories: + - name: SmokeDetector +metadata: + mnmn: SmartThingsCommunity + vid: 407e7b4f-4e9c-3cb3-9849-bd0d57f9b40a diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/aqara-gas/init.lua b/drivers/SmartThings/zigbee-smoke-detector/src/aqara-gas/init.lua new file mode 100644 index 0000000000..3b17912add --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/src/aqara-gas/init.lua @@ -0,0 +1,189 @@ +-- 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 data_types = require "st.zigbee.data_types" +local cluster_base = require "st.zigbee.cluster_base" +local capabilities = require "st.capabilities" + +local sensitivityAdjustment = capabilities["stse.sensitivityAdjustment"] +local sensitivityAdjustmentCommandName = "setSensitivityAdjustment" +local selfCheck = capabilities["stse.selfCheck"] +local startSelfCheckCommandName = "startSelfCheck" +local lifeTimeReport = capabilities["stse.lifeTimeReport"] + +local PRIVATE_CLUSTER_ID = 0xFCC0 +local PRIVATE_ATTRIBUTE_ID = 0x0009 +local MFG_CODE = 0x115F +local PRIVATE_SENSITIVITY_ADJUSTMENT_ATTRIBUTE_ID = 0x010C +local PRIVATE_MUTE_ATTRIBUTE_ID = 0x0126 +local PRIVATE_SELF_CHECK_ATTRIBUTE_ID = 0x0127 +local PRIVATE_LIFE_TIME_ATTRIBUTE_ID = 0x0128 +local PRIVATE_GAS_ZONE_STATUS_ATTRIBUTE_ID = 0x013A + + +local FINGERPRINTS = { + { mfr = "LUMI", model = "lumi.sensor_gas.acn02" } +} + + +local CONFIGURATIONS = { + { + cluster = PRIVATE_CLUSTER_ID, + attribute = PRIVATE_GAS_ZONE_STATUS_ATTRIBUTE_ID, + minimum_interval = 1, + maximum_interval = 3600, + data_type = data_types.Uint16, + reportable_change = 1 + } +} + +local function gas_zone_status_handler(driver, device, value, zb_rx) + if value.value == 1 then + device:emit_event(capabilities.gasDetector.gas.detected()) + elseif value.value == 0 then + device:emit_event(capabilities.gasDetector.gas.clear()) + end +end + +local function buzzer_status_handler(driver, device, value, zb_rx) + if value.value == 1 then + device:emit_event(capabilities.audioMute.mute.muted()) + elseif value.value == 0 then + device:emit_event(capabilities.audioMute.mute.unmuted()) + end +end + +local function lifetime_status_handler(driver, device, value, zb_rx) + if value.value == 1 then + device:emit_event(lifeTimeReport.lifeTimeState.endOfLife()) + elseif value.value == 0 then + device:emit_event(lifeTimeReport.lifeTimeState.normal()) + end +end + +local function selfcheck_status_handler(driver, device, value, zb_rx) + if value.value == 0 then + device:emit_event(selfCheck.selfCheckState.idle()) + elseif value.value == 1 then + device:emit_event(selfCheck.selfCheckState.selfCheckCompleted()) + end +end + +local function sensitivity_adjustment_handler(driver, device, value, zb_rx) + if value.value == 0x01 then + device:emit_event(sensitivityAdjustment.sensitivityAdjustment.Low()) + elseif value.value == 0x02 then + device:emit_event(sensitivityAdjustment.sensitivityAdjustment.High()) + end +end + +local function mute_handler(driver, device, cmd) + device:send(cluster_base.write_manufacturer_specific_attribute(device, + PRIVATE_CLUSTER_ID, PRIVATE_MUTE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 1)) +end + +local function unmute_handler(driver, device, cmd) + -- device:send(cluster_base.write_manufacturer_specific_attribute(device, + -- PRIVATE_CLUSTER_ID, PRIVATE_MUTE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0)) + device:emit_event(capabilities.audioMute.mute.muted()) +end + +local function sensitivity_adjustment_capability_handler(driver, device, command) + local sensitivity = command.args.sensitivity + local pre_sensitivity_value = device:get_latest_state("main", sensitivityAdjustment.ID, sensitivityAdjustment.sensitivityAdjustment.NAME) + + if pre_sensitivity_value ~= sensitivity then + if sensitivity == 'High' then + device:send(cluster_base.write_manufacturer_specific_attribute(device, + PRIVATE_CLUSTER_ID, PRIVATE_SENSITIVITY_ADJUSTMENT_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0x02)) + elseif sensitivity == 'Low' then + device:send(cluster_base.write_manufacturer_specific_attribute(device, + PRIVATE_CLUSTER_ID, PRIVATE_SENSITIVITY_ADJUSTMENT_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0x01)) + end + else + if sensitivity == 'High' then + device:emit_event(sensitivityAdjustment.sensitivityAdjustment.High()) + elseif sensitivity == 'Low' then + device:emit_event(sensitivityAdjustment.sensitivityAdjustment.Low()) + end + end +end + +local function self_check_attr_handler(self, device, zone_status, zb_rx) + device:emit_event(selfCheck.selfCheckState.selfChecking()) + device:send(cluster_base.write_manufacturer_specific_attribute(device, + PRIVATE_CLUSTER_ID, PRIVATE_SELF_CHECK_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true)) +end + +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 device_init(driver, device) + if CONFIGURATIONS ~= nil then + for _, attribute in ipairs(CONFIGURATIONS) do + device:add_configured_attribute(attribute) + device:add_monitored_attribute(attribute) + end + end +end + +local function device_added(driver, device) + device:send(cluster_base.write_manufacturer_specific_attribute(device, + PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0x01)) + device:emit_event(capabilities.gasDetector.gas.clear()) + device:emit_event(capabilities.audioMute.mute.unmuted()) + device:emit_event(sensitivityAdjustment.sensitivityAdjustment.High()) + device:emit_event(selfCheck.selfCheckState.idle()) + device:emit_event(lifeTimeReport.lifeTimeState.normal()) +end + +local aqara_gas_detector_handler = { + NAME = "Aqara Gas Detector Handler", + lifecycle_handlers = { + init = device_init, + added = device_added + }, + zigbee_handlers = { + attr = { + [PRIVATE_CLUSTER_ID] = { + [PRIVATE_GAS_ZONE_STATUS_ATTRIBUTE_ID] = gas_zone_status_handler, + [PRIVATE_MUTE_ATTRIBUTE_ID] = buzzer_status_handler, + [PRIVATE_LIFE_TIME_ATTRIBUTE_ID] = lifetime_status_handler, + [PRIVATE_SELF_CHECK_ATTRIBUTE_ID] = selfcheck_status_handler, + [PRIVATE_SENSITIVITY_ADJUSTMENT_ATTRIBUTE_ID] = sensitivity_adjustment_handler + }, + } + }, + capability_handlers = { + [capabilities.audioMute.ID] = { + [capabilities.audioMute.commands.mute.NAME] = mute_handler, + [capabilities.audioMute.commands.unmute.NAME] = unmute_handler + }, + [sensitivityAdjustment.ID] = { + [sensitivityAdjustmentCommandName] = sensitivity_adjustment_capability_handler + }, + [selfCheck.ID] = { + [startSelfCheckCommandName] = self_check_attr_handler + }, + }, + can_handle = is_aqara_products +} + +return aqara_gas_detector_handler + diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/init.lua b/drivers/SmartThings/zigbee-smoke-detector/src/init.lua index 1a704222f1..9ac3026208 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/init.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/init.lua @@ -23,6 +23,8 @@ local zigbee_smoke_driver_template = { capabilities.battery }, sub_drivers = { + require("frient"), + require("aqara-gas") require("aqara"), require("frient") }, diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.lua b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.lua new file mode 100644 index 0000000000..c24b46f172 --- /dev/null +++ b/drivers/SmartThings/zigbee-smoke-detector/src/test/test_aqara_gas_detector.lua @@ -0,0 +1,239 @@ +-- 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 test = require "integration_test" +local t_utils = require "integration_test.utils" +local zigbee_test_utils = require "integration_test.zigbee_test_utils" +local capabilities = require "st.capabilities" +local cluster_base = require "st.zigbee.cluster_base" +local data_types = require "st.zigbee.data_types" + +local selfCheck = capabilities["stse.selfCheck"] +local lifeTimeReport = capabilities["stse.lifeTimeReport"] +local sensitivityAdjustment = capabilities["stse.sensitivityAdjustment"] + +local sensitivityAdjustmentId = "stse.sensitivityAdjustment" +local selfCheckId = "stse.selfCheck" + +test.add_package_capability("lifeTimeReport.yaml") +test.add_package_capability("selfCheck.yaml") +test.add_package_capability("sensitivityAdjustment.yaml") + +local PRIVATE_CLUSTER_ID = 0xFCC0 +local PRIVATE_ATTRIBUTE_ID = 0x0009 +local MFG_CODE = 0x115F +local PRIVATE_SENSITIVITY_ADJUSTMENT_ATTRIBUTE_ID = 0x010C +local PRIVATE_MUTE_ATTRIBUTE_ID = 0x0126 +local PRIVATE_SELF_CHECK_ATTRIBUTE_ID = 0x0127 +local PRIVATE_LIFE_TIME_ATTRIBUTE_ID = 0x0128 +local PRIVATE_GAS_ZONE_STATUS_ATTRIBUTE_ID = 0x013A + + + +local mock_device = test.mock_device.build_test_zigbee_device( + { + profile = t_utils.get_profile_definition("gas-lifetime-selfcheck-aqara.yml"), + zigbee_endpoints = { + [1] = { + id = 1, + manufacturer = "LUMI", + model = "lumi.sensor_gas.acn02", + server_clusters = { 0x0001, 0xFCC0 } + } + } + } +) + + + +zigbee_test_utils.prepare_zigbee_env_info() +local function test_init() + test.mock_device.add_test_device(mock_device) +end + + + +test.set_test_init_function(test_init) + +test.register_coroutine_test( + "Handle added lifecycle", + function() + test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, PRIVATE_ATTRIBUTE_ID, MFG_CODE, + data_types.Uint8, 0x01) }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.gasDetector.gas.clear())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.audioMute.mute.unmuted())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", sensitivityAdjustment.sensitivityAdjustment.High())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", selfCheck.selfCheckState.idle())) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", lifeTimeReport.lifeTimeState.normal())) + end +) + + + +test.register_coroutine_test( + "gasDetector report should be handled", + function() + local attr_report_data = { + { PRIVATE_GAS_ZONE_STATUS_ATTRIBUTE_ID, data_types.Uint16.ID, 0x0001 } + } + 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", + capabilities.gasDetector.gas.detected())) + end +) + + + +test.register_coroutine_test( + "audioMute report should be handled", + function() + local attr_report_data = { + { PRIVATE_MUTE_ATTRIBUTE_ID, data_types.Uint8.ID, 0x01 } + } + 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", + capabilities.audioMute.mute.muted())) + end +) + + + +test.register_coroutine_test( + "Capability on command should be handled : device mute", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "audioMute", component = "main", command = "mute", args = {} } }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + PRIVATE_MUTE_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 1) }) + end +) + +test.register_coroutine_test( + "Capability on command should not be handled : device unmute", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = "audioMute", component = "main", command = "unmute", args = {} } }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + capabilities.audioMute.mute.muted())) + end +) + + + +test.register_coroutine_test( + "selfCheck report should be handled", + function() + local attr_report_data = { + { PRIVATE_SELF_CHECK_ATTRIBUTE_ID, data_types.Uint8.ID, 0x01 } + } + 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", + selfCheck.selfCheckState.selfCheckCompleted())) + end +) + + + +test.register_coroutine_test( + "Capability on command should be handled : device selfCheck", + function() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = selfCheckId, component = "main", command = "startSelfCheck", args = {state = "selfCheckCompleted"} } }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main",selfCheck.selfCheckState.selfChecking())) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + PRIVATE_SELF_CHECK_ATTRIBUTE_ID, MFG_CODE, data_types.Boolean, true) }) + end +) + + + +test.register_coroutine_test( + "lifetime report should be handled", + function() + local attr_report_data = { + { PRIVATE_LIFE_TIME_ATTRIBUTE_ID, data_types.Uint8.ID, 0x01 } + } + 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", + lifeTimeReport.lifeTimeState.endOfLife())) + end +) + + + +test.register_coroutine_test( + "sensitivityAdjustment report should be handled", + function() + local attr_report_data = { + { PRIVATE_SENSITIVITY_ADJUSTMENT_ATTRIBUTE_ID, data_types.Uint8.ID, 0x01 } + } + 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", + sensitivityAdjustment.sensitivityAdjustment.Low())) + end +) + + + +test.register_coroutine_test( + "Capability on command should be handled : setSensitivityAdjustment Low", + function() + local attr_report_data = { + { PRIVATE_SENSITIVITY_ADJUSTMENT_ATTRIBUTE_ID, data_types.Uint8.ID, 0x01 } + } + test.socket.capability:__queue_receive({ mock_device.id, + { capability = sensitivityAdjustmentId, component = "main", command = "setSensitivityAdjustment", args = {"Low"}} + }) + test.socket.zigbee:__expect_send({ mock_device.id, + cluster_base.write_manufacturer_specific_attribute(mock_device, PRIVATE_CLUSTER_ID, + PRIVATE_SENSITIVITY_ADJUSTMENT_ATTRIBUTE_ID, MFG_CODE, data_types.Uint8, 0x01) + }) + test.wait_for_events() + 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", + sensitivityAdjustment.sensitivityAdjustment.Low()) + ) + test.wait_for_events() + test.socket.capability:__queue_receive({ mock_device.id, + { capability = sensitivityAdjustmentId, component = "main", command = "setSensitivityAdjustment", args = {"Low"}} + }) + test.socket.capability:__expect_send(mock_device:generate_test_message("main", + sensitivityAdjustment.sensitivityAdjustment.Low()) + ) + end +) + + +test.run_registered_tests() From 04b780b6fd888133922b1bdd3f5ccfb0358f57ea Mon Sep 17 00:00:00 2001 From: Doug Stephen Date: Thu, 14 Mar 2024 09:38:05 -0500 Subject: [PATCH 17/21] Fix missing comma in subdriver list --- drivers/SmartThings/zigbee-smoke-detector/src/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/SmartThings/zigbee-smoke-detector/src/init.lua b/drivers/SmartThings/zigbee-smoke-detector/src/init.lua index 9ac3026208..2d46a5dc8c 100644 --- a/drivers/SmartThings/zigbee-smoke-detector/src/init.lua +++ b/drivers/SmartThings/zigbee-smoke-detector/src/init.lua @@ -24,7 +24,7 @@ local zigbee_smoke_driver_template = { }, sub_drivers = { require("frient"), - require("aqara-gas") + require("aqara-gas"), require("aqara"), require("frient") }, From 05b2f3f80ef2183edd47f7269b37f9cb8adb8201 Mon Sep 17 00:00:00 2001 From: Raj Kumar <108387779+raj-1219@users.noreply.github.com> Date: Fri, 15 Mar 2024 21:04:31 +0530 Subject: [PATCH 18/21] audio TTS fix on samsung speakers (#1268) * audio TTS fix on samsung speakers --------- Co-authored-by: Carter Swedal --- drivers/SmartThings/samsung-audio/README.md | 15 ++++++++ .../SmartThings/samsung-audio/src/command.lua | 38 +------------------ .../samsung-audio/src/handlers.lua | 6 ++- 3 files changed, 21 insertions(+), 38 deletions(-) create mode 100644 drivers/SmartThings/samsung-audio/README.md diff --git a/drivers/SmartThings/samsung-audio/README.md b/drivers/SmartThings/samsung-audio/README.md new file mode 100644 index 0000000000..5a8b6e06ae --- /dev/null +++ b/drivers/SmartThings/samsung-audio/README.md @@ -0,0 +1,15 @@ +# Samsung-Audio integration documentation + +The purpose of this readme is to document the behavior of various samsung lan speaker devices, +since during the development of the driver there have been some weird behavior. +The document will also explain any mitigations the driver makes to provide a +consistent integration despite the weird behavior. + +## Device testing notes + +Audio Speaker Device shows some weird behavior while responding to various commands. Due to non uniform response by devices, device state mismatch can be +observed sometimes in the ST app. + +## Samsung Audio TTS Notification + +Currently, Audio TTS notification is played with certain non expected behaviors. For example, when the speakers are in paused state, the notification plays but the queued music resumes playing. Similarly, when speakers are in mute state, it will unmute and play the notification but the speakers will remain unmuted after this. diff --git a/drivers/SmartThings/samsung-audio/src/command.lua b/drivers/SmartThings/samsung-audio/src/command.lua index 38bf2ff2e7..deca7c4416 100644 --- a/drivers/SmartThings/samsung-audio/src/command.lua +++ b/drivers/SmartThings/samsung-audio/src/command.lua @@ -349,27 +349,6 @@ function Command.getPlayStatus(ip) return response_map end ---- Return err code from xml handler or nil if it doesn't exist -local get_resp = function(response_map) - if response_map ~= nil and - response_map.handler_res~= nil and - response_map.handler_res.root ~= nil and - response_map.handler_res.root.UIC ~= nil and - response_map.handler_res.root.UIC.response ~=nil then - return { - method = response_map.handler_res.root.UIC.method, - err_code= response_map.handler_res.root.UIC.response.errCode - } - else - log.trace("errorCode not found") - return { - method = nil, - err_code = nil - } - end - -end - local format_streaming_path = function(uri) return "/UIC?cmd=%3Cpwron%3Eon%3C/pwron%3E%3Cname%3ESetUrlPlayback%3C/name%3E%3Cp%20type=%22cdata%22%20name=%22url%22%20val=%22empty%22%3E%3C![CDATA[" .. uri .. "]]%3E%3C/p%3E%3Cp%20type=%22dec%22%20name=%22buffersize%22%20val=%220%22/%3E%3Cp%20type=%22dec%22%20name=%22seektime%22%20val=%220%22/%3E%3Cp%20type=%22dec%22%20name=%22resume%22%20val=%221%22/%3E" end @@ -388,22 +367,7 @@ function Command.play_streaming_uri(ip, uri) log.trace("Triggering UPnP Command Request for [Audio Notification -> SetUrlPlayback]") local response_map = nil if ip then - local path = format_streaming_path(uri) - local url = format_url(ip, path) - log.trace(string.format("Final Notification Command URL for making Audio Notification http request = %s", url)) - response_map = handle_http_request(ip, url) - --- Adding extra debug logs in case some issue comes in future - log.debug("Response Map table with https: ", utils.stringify_table(response_map)) - local resp = get_resp(response_map) - log.debug("resp method and error code : ", utils.stringify_table(resp)) - if resp.method == "ErrorEvent" then - log.info(string.format("Recieved error code %s",resp.err_code)) - response_map= fallback_to_http(ip,uri) - end - if resp.method == "PausePlaybackEvent" then - log.info(string.format("Recieved error code %s",resp.err_code)) - response_map= fallback_to_http(ip,uri) - end + response_map = fallback_to_http(ip, uri) end return response_map end diff --git a/drivers/SmartThings/samsung-audio/src/handlers.lua b/drivers/SmartThings/samsung-audio/src/handlers.lua index 8ff2722757..6233a86326 100644 --- a/drivers/SmartThings/samsung-audio/src/handlers.lua +++ b/drivers/SmartThings/samsung-audio/src/handlers.lua @@ -136,7 +136,11 @@ end function CapabilityHandlers.handle_audio_notification(driver, device, cmd) local ip = device:get_field("ip") - -- Audio Notification working fine when checked through routine. Need to confirm later for any issue whether ST app expect any emit_event here + local mute_status = command.getMute(ip) + if mute_status.muted ~= "off" then + --unmute before playig notification + command.unmute(ip) + end command.play_streaming_uri(ip, cmd.args.uri) end From 561aa74bfac73d0971d5ae318c3a6a379b737a4f Mon Sep 17 00:00:00 2001 From: ThirdReality Support <117250319+3reality-support@users.noreply.github.com> Date: Mon, 18 Mar 2024 04:14:03 +0800 Subject: [PATCH 19/21] Add ThirdReality Driver for Matter Night Light (#1048) * Add ThirdReality Driver for Matter Night Light * move third reality nl support to subdriver format * rename profile file name -nl to illuminance-motion clean third-reality/init useless code * Add illuminance and motion to base matter-switch driver * Commit:Add colorTemperature range 1000-15000k support in profile * Commit: add new profile loight-color-level-illuminance-motion-1000-15000k;update the fingerptint * Commit: update the file name to be light-color-level-illuminance-motion-1000K-15000K * Commit: update device label by writing out "NL" to "Night Light" * fix unit tests for illiminance motion light --------- Co-authored-by: Zhangyi Co-authored-by: xdong Co-authored-by: Cooper Towns Co-authored-by: lelandblue <79465613+lelandblue@users.noreply.github.com> --- .../matter-switch/fingerprints.yml | 7 + ...-level-illuminance-motion-1000K-15000K.yml | 27 + .../light-color-level-illuminance-motion.yml | 23 + .../SmartThings/matter-switch/src/init.lua | 26 +- .../test/test_light_illuminance_motion.lua | 537 ++++++++++++++++++ 5 files changed, 619 insertions(+), 1 deletion(-) create mode 100644 drivers/SmartThings/matter-switch/profiles/light-color-level-illuminance-motion-1000K-15000K.yml create mode 100644 drivers/SmartThings/matter-switch/profiles/light-color-level-illuminance-motion.yml create mode 100644 drivers/SmartThings/matter-switch/src/test/test_light_illuminance_motion.lua diff --git a/drivers/SmartThings/matter-switch/fingerprints.yml b/drivers/SmartThings/matter-switch/fingerprints.yml index 34c1fc5447..180584609b 100644 --- a/drivers/SmartThings/matter-switch/fingerprints.yml +++ b/drivers/SmartThings/matter-switch/fingerprints.yml @@ -1,4 +1,11 @@ matterManufacturer: +#ThirdReality + - id: "ThirdReality/WiFi" + deviceLabel: THIRDREALITY Night Light + vendorId: 0x1407 + productId: 0x1088 + deviceProfileName: light-color-level-illuminance-motion-1000K-15000K + #Eve - id: "Eve/Energy/US" deviceLabel: Eve Energy diff --git a/drivers/SmartThings/matter-switch/profiles/light-color-level-illuminance-motion-1000K-15000K.yml b/drivers/SmartThings/matter-switch/profiles/light-color-level-illuminance-motion-1000K-15000K.yml new file mode 100644 index 0000000000..0b815134b3 --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/light-color-level-illuminance-motion-1000K-15000K.yml @@ -0,0 +1,27 @@ +name: light-color-level-illuminance-motion-1000K-15000K +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: switchLevel + version: 1 + - id: colorTemperature + version: 1 + config: + values: + - key: "colorTemperature.value" + range: [ 1000, 15000 ] + - id: colorControl + version: 1 + - id: motionSensor + version: 1 + - id: illuminanceMeasurement + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Light + diff --git a/drivers/SmartThings/matter-switch/profiles/light-color-level-illuminance-motion.yml b/drivers/SmartThings/matter-switch/profiles/light-color-level-illuminance-motion.yml new file mode 100644 index 0000000000..e2d33f6929 --- /dev/null +++ b/drivers/SmartThings/matter-switch/profiles/light-color-level-illuminance-motion.yml @@ -0,0 +1,23 @@ +name: light-color-level-illuminance-motion +components: +- id: main + capabilities: + - id: switch + version: 1 + - id: switchLevel + version: 1 + - id: colorTemperature + version: 1 + - id: colorControl + version: 1 + - id: motionSensor + version: 1 + - id: illuminanceMeasurement + version: 1 + - id: firmwareUpdate + version: 1 + - id: refresh + version: 1 + categories: + - name: Light + diff --git a/drivers/SmartThings/matter-switch/src/init.lua b/drivers/SmartThings/matter-switch/src/init.lua index 27823095a8..b4cac166f3 100644 --- a/drivers/SmartThings/matter-switch/src/init.lua +++ b/drivers/SmartThings/matter-switch/src/init.lua @@ -338,6 +338,16 @@ local function color_cap_attr_handler(driver, device, ib, response) end end + +local function illuminance_attr_handler(driver, device, ib, response) + local lux = math.floor(10 ^ ((ib.data.value - 1) / 10000)) + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.illuminanceMeasurement.illuminance(lux)) +end + +local function occupancy_attr_handler(driver, device, ib, response) + device:emit_event(ib.data.value == 0x01 and capabilities.motionSensor.motion.active() or capabilities.motionSensor.motion.inactive()) +end + local function info_changed(driver, device, event, args) if device.profile.id ~= args.old_st_store.profile.id then device:subscribe() @@ -374,6 +384,12 @@ local matter_driver_template = { [clusters.ColorControl.attributes.CurrentX.ID] = x_attr_handler, [clusters.ColorControl.attributes.CurrentY.ID] = y_attr_handler, [clusters.ColorControl.attributes.ColorCapabilities.ID] = color_cap_attr_handler, + }, + [clusters.IlluminanceMeasurement.ID] = { + [clusters.IlluminanceMeasurement.attributes.MeasuredValue.ID] = illuminance_attr_handler + }, + [clusters.OccupancySensing.ID] = { + [clusters.OccupancySensing.attributes.Occupancy.ID] = occupancy_attr_handler, } }, fallback = matter_handler, @@ -394,6 +410,12 @@ local matter_driver_template = { [capabilities.colorTemperature.ID] = { clusters.ColorControl.attributes.ColorTemperatureMireds, }, + [capabilities.illuminanceMeasurement.ID] = { + clusters.IlluminanceMeasurement.attributes.MeasuredValue + }, + [capabilities.motionSensor.ID] = { + clusters.OccupancySensing.attributes.Occupancy + } }, capability_handlers = { [capabilities.switch.ID] = { @@ -420,9 +442,11 @@ local matter_driver_template = { capabilities.switchLevel, capabilities.colorControl, capabilities.colorTemperature, + capabilities.motionSensor, + capabilities.illuminanceMeasurement }, sub_drivers = { - require("eve-energy") + require("eve-energy"), } } diff --git a/drivers/SmartThings/matter-switch/src/test/test_light_illuminance_motion.lua b/drivers/SmartThings/matter-switch/src/test/test_light_illuminance_motion.lua new file mode 100644 index 0000000000..96be10bf6a --- /dev/null +++ b/drivers/SmartThings/matter-switch/src/test/test_light_illuminance_motion.lua @@ -0,0 +1,537 @@ +-- Copyright 2022 SmartThings +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +local test = require "integration_test" +local capabilities = require "st.capabilities" +local t_utils = require "integration_test.utils" + +local clusters = require "st.matter.clusters" +local TRANSITION_TIME = 0 +local OPTIONS_MASK = 0x01 +local OPTIONS_OVERRIDE = 0x01 + +local mock_device = test.mock_device.build_test_matter_device({ + profile = t_utils.get_profile_definition("light-color-level-illuminance-motion.yml"), + manufacturer_info = { + vendor_id = 0x0000, + product_id = 0x0000, + }, + endpoints = { + { + endpoint_id = 0, + clusters = { + {cluster_id = clusters.Basic.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0016, device_type_revision = 1} -- RootNode + } + }, + { + endpoint_id = 1, + clusters = { + { + cluster_id = clusters.OnOff.ID, + cluster_type = "SERVER", + cluster_revision = 1, + feature_map = 0, --u32 bitmap + }, + {cluster_id = clusters.ColorControl.ID, cluster_type = "BOTH", feature_map = 31}, + {cluster_id = clusters.LevelControl.ID, cluster_type = "SERVER"} + }, + device_types = { + {device_type_id = 0x010C, device_type_revision = 1} -- ColorTemperatureLight + } + }, + { + endpoint_id = 2, + clusters = { + {cluster_id = clusters.IlluminanceMeasurement.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0106, device_type_revision = 1} -- LightSensor + } + }, + { + endpoint_id = 3, + clusters = { + {cluster_id = clusters.OccupancySensing.ID, cluster_type = "SERVER"}, + }, + device_types = { + {device_type_id = 0x0107, device_type_revision = 1} -- OccupancySensor + } + } + } +}) + + +local function test_init() + local cluster_subscribe_list = { + clusters.OnOff.attributes.OnOff, + clusters.LevelControl.attributes.CurrentLevel, + clusters.ColorControl.attributes.CurrentHue, + clusters.ColorControl.attributes.CurrentSaturation, + clusters.ColorControl.attributes.CurrentX, + clusters.ColorControl.attributes.CurrentY, + clusters.ColorControl.attributes.ColorTemperatureMireds, + clusters.IlluminanceMeasurement.attributes.MeasuredValue, + clusters.OccupancySensing.attributes.Occupancy + } + test.socket.matter:__set_channel_ordering("relaxed") + local subscribe_request = cluster_subscribe_list[1]:subscribe(mock_device) + for i, cluster in ipairs(cluster_subscribe_list) do + if i > 1 then + subscribe_request:merge(cluster:subscribe(mock_device)) + end + end + test.socket.matter:__expect_send({mock_device.id, subscribe_request}) + test.mock_device.add_test_device(mock_device) +end +test.set_test_init_function(test_init) + +test.register_message_test( + "On command should send the appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "switch", component = "main", command = "on", args = { } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OnOff.server.commands.On(mock_device, 1) + } + } + } +) + +test.register_message_test( + "Off command should send the appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "switch", component = "main", command = "off", args = { } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.OnOff.server.commands.Off(mock_device, 1) + } + } + } +) + +test.register_message_test( + "Set level command should send the appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "switchLevel", component = "main", command = "setLevel", args = {20,20} } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.LevelControl.server.commands.MoveToLevelWithOnOff(mock_device, 1, math.floor(20/100.0 * 254), 20, 0 ,0) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.LevelControl.server.commands.MoveToLevelWithOnOff:build_test_command_response(mock_device, 1) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.LevelControl.attributes.CurrentLevel:build_test_report_data(mock_device, 1, 50) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.switchLevel.level(20)) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OnOff.attributes.OnOff:build_test_report_data(mock_device, 1, true) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.switch.switch.on()) + } + } +) + +test.register_message_test( + "Current level reports should generate appropriate events", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.LevelControl.server.attributes.CurrentLevel:build_test_report_data(mock_device, 1, 50) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.switchLevel.level(math.floor((50 / 254.0 * 100) + 0.5))) + }, + } +) + +local hue = math.floor((50 * 0xFE) / 100.0 + 0.5) +local sat = math.floor((50 * 0xFE) / 100.0 + 0.5) + +test.register_message_test( + "Set color command should send huesat commands when supported", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.ColorCapabilities:build_test_report_data(mock_device, 1, 0x01) + } + }, + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "colorControl", component = "main", command = "setColor", args = { { hue = 50, saturation = 50 } } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.ColorControl.server.commands.MoveToHueAndSaturation(mock_device, 1, hue, sat, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.server.commands.MoveToHueAndSaturation:build_test_command_response(mock_device, 1) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.CurrentHue:build_test_report_data(mock_device, 1, hue) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.colorControl.hue(50)) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.CurrentSaturation:build_test_report_data(mock_device, 1, sat) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.colorControl.saturation(50)) + } + } +) + +test.register_message_test( + "Set Hue command should send MoveToHue", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "colorControl", component = "main", command = "setHue", args = { 50 } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.ColorControl.server.commands.MoveToHue(mock_device, 1, hue, 0, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) + } + }, + } +) + +test.register_message_test( + "Set Saturation command should send MoveToSaturation", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "colorControl", component = "main", command = "setSaturation", args = { 50 } } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.ColorControl.server.commands.MoveToSaturation(mock_device, 1, sat, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) + } + }, + } +) + +test.register_message_test( + "Set color temperature should send the appropriate commands", + { + { + channel = "capability", + direction = "receive", + message = { + mock_device.id, + { capability = "colorTemperature", component = "main", command = "setColorTemperature", args = {1800} } + } + }, + { + channel = "matter", + direction = "send", + message = { + mock_device.id, + clusters.ColorControl.server.commands.MoveToColorTemperature(mock_device, 1, 556, TRANSITION_TIME, OPTIONS_MASK, OPTIONS_OVERRIDE) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.server.commands.MoveToColorTemperature:build_test_command_response(mock_device, 1) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.ColorTemperatureMireds:build_test_report_data(mock_device, 1, 556) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.colorTemperature.colorTemperature(1800)) + }, + } +) + +test.register_message_test( + "X and Y color values should report hue and saturation once both have been received", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device, 1, 15091) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, 1, 21547) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.colorControl.hue(50)) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.colorControl.saturation(72)) + } + } +) + +test.register_message_test( + "X and Y color values have 0 value", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device, 1, 0) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, 1, 0) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.colorControl.hue(33)) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.colorControl.saturation(100)) + } + } +) + + +test.register_message_test( + "Y and X color values should report hue and saturation once both have been received", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.CurrentY:build_test_report_data(mock_device, 1, 21547) + } + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.CurrentX:build_test_report_data(mock_device, 1, 15091) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.colorControl.hue(50)) + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.colorControl.saturation(72)) + } + } +) + +test.register_message_test( + "Do not report when receiving a color temperature of 0 mireds", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.ColorControl.attributes.ColorTemperatureMireds:build_test_report_data(mock_device, 1, 0) + } + } + } +) + +test.register_message_test( + "Illuminance reports should generate correct messages", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.IlluminanceMeasurement.server.attributes.MeasuredValue:build_test_report_data(mock_device, 2, 21370) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.illuminanceMeasurement.illuminance({ value = 137 })) + } + } +) + +test.register_message_test( + "Occupancy reports should generate correct messages", + { + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OccupancySensing.attributes.Occupancy:build_test_report_data(mock_device, 3, 1) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.active()) + }, + { + channel = "matter", + direction = "receive", + message = { + mock_device.id, + clusters.OccupancySensing.attributes.Occupancy:build_test_report_data(mock_device, 3, 0) + } + }, + { + channel = "capability", + direction = "send", + message = mock_device:generate_test_message("main", capabilities.motionSensor.motion.inactive()) + } + } +) + +test.run_registered_tests() From ce6b03cf1c92a4f316ef9d7c361226b1fe4cb109 Mon Sep 17 00:00:00 2001 From: HunsupJung <59987061+HunsupJung@users.noreply.github.com> Date: Wed, 20 Mar 2024 07:40:28 +0900 Subject: [PATCH 20/21] Fix incorrect operational state issue by workaround (#1266) Fix incorrect operational state issue Operational state is mismatching with actual status of window covering Signed-off-by: Hunsup Jung --- .../matter-window-covering/src/init.lua | 68 +++++++++++--- .../src/test/test_matter_window_covering.lua | 90 +++++++++++++++++++ 2 files changed, 144 insertions(+), 14 deletions(-) diff --git a/drivers/SmartThings/matter-window-covering/src/init.lua b/drivers/SmartThings/matter-window-covering/src/init.lua index 6f438a7dcf..68575056c3 100644 --- a/drivers/SmartThings/matter-window-covering/src/init.lua +++ b/drivers/SmartThings/matter-window-covering/src/init.lua @@ -21,6 +21,14 @@ local MatterDriver = require "st.matter.driver" local DEFAULT_LEVEL = 0 local PROFILE_MATCHED = "__profile_matched" +local IS_MOVING = "__is_moving" +local EVENT_STATE = "__event_state" + +local WindowCoveringEventEnum = { + NO_EVENT = 0x00, + CURRENT_POSITION_EVENT = 0x01, + OPERATIONAL_STATE_EVENT = 0x02 +} local function find_default_endpoint(device, cluster) local res = device.MATTER_DEFAULT_ENDPOINT @@ -54,6 +62,8 @@ local function match_profile(device) end local function device_init(driver, device) + device:set_field(EVENT_STATE, WindowCoveringEventEnum.NO_EVENT) + device:set_field(IS_MOVING, false) if not device:get_field(PROFILE_MATCHED) then match_profile(device) end @@ -127,12 +137,29 @@ end -- current lift percentage, changed to 100ths percent local function current_pos_handler(driver, device, ib, response) + local position = 0 if ib.data.value ~= nil then - local position = 100 - math.floor((ib.data.value / 100)) + position = 100 - math.floor((ib.data.value / 100)) device:emit_event_for_endpoint( ib.endpoint_id, capabilities.windowShadeLevel.shadeLevel(position) ) end + if device:get_field(EVENT_STATE) == WindowCoveringEventEnum.OPERATIONAL_STATE_EVENT then + if not device:get_field(IS_MOVING) then + if position == 0 then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.windowShade.windowShade.closed()) + elseif position == 100 then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.windowShade.windowShade.open()) + elseif position > 0 and position < 100 then + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.windowShade.windowShade.partially_open()) + else + device:emit_event_for_endpoint(ib.endpoint_id, capabilities.windowShade.windowShade.unknown()) + end + end + device:set_field(EVENT_STATE, WindowCoveringEventEnum.NO_EVENT) + else + device:set_field(EVENT_STATE, WindowCoveringEventEnum.CURRENT_POSITION_EVENT) + end end -- checks the current position of the shade @@ -150,20 +177,34 @@ local function current_status_handler(driver, device, ib, response) end end local state = ib.data.value & clusters.WindowCovering.types.OperationalStatus.GLOBAL --Could use LIFT instead - if state == 0 then -- not moving - if position == 100 then -- open - device:emit_event_for_endpoint(ib.endpoint_id, attr.open()) - elseif position == 0 then -- closed - device:emit_event_for_endpoint(ib.endpoint_id, attr.closed()) + if device:get_field(EVENT_STATE) == WindowCoveringEventEnum.CURRENT_POSITION_EVENT then + if state == 0 then -- not moving + if position == 100 then -- open + device:emit_event_for_endpoint(ib.endpoint_id, attr.open()) + elseif position == 0 then -- closed + device:emit_event_for_endpoint(ib.endpoint_id, attr.closed()) + else + device:emit_event_for_endpoint(ib.endpoint_id, attr.partially_open()) + end + elseif state == 1 then -- opening + device:emit_event_for_endpoint(ib.endpoint_id, attr.opening()) + elseif state == 2 then -- closing + device:emit_event_for_endpoint(ib.endpoint_id, attr.closing()) else - device:emit_event_for_endpoint(ib.endpoint_id, attr.partially_open()) + device:emit_event_for_endpoint(ib.endpoint_id, attr.unknown()) end - elseif state == 1 then -- opening - device:emit_event_for_endpoint(ib.endpoint_id, attr.opening()) - elseif state == 2 then -- closing - device:emit_event_for_endpoint(ib.endpoint_id, attr.closing()) + device:set_field(EVENT_STATE, WindowCoveringEventEnum.NO_EVENT) else - device:emit_event_for_endpoint(ib.endpoint_id, attr.unknown()) + if state == 1 then -- opening + device:emit_event_for_endpoint(ib.endpoint_id, attr.opening()) + device:set_field(IS_MOVING, true) + elseif state == 2 then -- closing + device:emit_event_for_endpoint(ib.endpoint_id, attr.closing()) + device:set_field(IS_MOVING, true) + else + device:set_field(IS_MOVING, false) + end + device:set_field(EVENT_STATE, WindowCoveringEventEnum.OPERATIONAL_STATE_EVENT) end end @@ -181,7 +222,6 @@ local function battery_percent_remaining_attr_handler(driver, device, ib, respon end end - local matter_driver_template = { lifecycle_handlers = {init = device_init, removed = device_removed, added = device_added, infoChanged = info_changed}, matter_handlers = { @@ -238,4 +278,4 @@ local matter_driver_template = { } local matter_driver = MatterDriver("matter-window-covering", matter_driver_template) -matter_driver:run() +matter_driver:run() \ No newline at end of file diff --git a/drivers/SmartThings/matter-window-covering/src/test/test_matter_window_covering.lua b/drivers/SmartThings/matter-window-covering/src/test/test_matter_window_covering.lua index 99aab47403..2ed20e159b 100644 --- a/drivers/SmartThings/matter-window-covering/src/test/test_matter_window_covering.lua +++ b/drivers/SmartThings/matter-window-covering/src/test/test_matter_window_covering.lua @@ -190,6 +190,36 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "WindowCovering OperationalStatus state closed before position 0", function() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.matter:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0), + } + ) + test.socket.matter:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( + mock_device, 10, 10000 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShade.windowShade.closed() + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShadeLevel.shadeLevel(0) + ) + ) + end +) + test.register_coroutine_test( "WindowCovering OperationalStatus state open", function() test.socket.capability:__set_channel_ordering("relaxed") @@ -220,6 +250,36 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "WindowCovering OperationalStatus state open before position event", function() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.matter:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0), + } + ) + test.socket.matter:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( + mock_device, 10, 0 + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShade.windowShade.open() + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShadeLevel.shadeLevel(100) + ) + ) + end +) + test.register_coroutine_test( "WindowCovering OperationalStatus partially open", function() test.socket.capability:__set_channel_ordering("relaxed") @@ -250,6 +310,36 @@ test.register_coroutine_test( end ) +test.register_coroutine_test( + "WindowCovering OperationalStatus partially open before position event", function() + test.socket.capability:__set_channel_ordering("relaxed") + test.socket.matter:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.OperationalStatus:build_test_report_data(mock_device, 10, 0), + } + ) + test.socket.matter:__queue_receive( + { + mock_device.id, + WindowCovering.attributes.CurrentPositionLiftPercent100ths:build_test_report_data( + mock_device, 10, ((100 - 25) *100) + ), + } + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShadeLevel.shadeLevel(25) + ) + ) + test.socket.capability:__expect_send( + mock_device:generate_test_message( + "main", capabilities.windowShade.windowShade.partially_open() + ) + ) + end +) + test.register_coroutine_test("WindowCovering OperationalStatus opening", function() test.socket.capability:__set_channel_ordering("relaxed") test.socket.matter:__queue_receive( From 5fafaed72542f7f453f3c070e1112a132340547a Mon Sep 17 00:00:00 2001 From: lelandblue <79465613+lelandblue@users.noreply.github.com> Date: Thu, 21 Mar 2024 09:04:39 -0400 Subject: [PATCH 21/21] new-devices-shelly-plugs (#1193) --- drivers/SmartThings/zwave-switch/fingerprints.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/SmartThings/zwave-switch/fingerprints.yml b/drivers/SmartThings/zwave-switch/fingerprints.yml index 5106edf4c4..30e6ba5530 100644 --- a/drivers/SmartThings/zwave-switch/fingerprints.yml +++ b/drivers/SmartThings/zwave-switch/fingerprints.yml @@ -898,6 +898,19 @@ zwaveManufacturer: manufacturerId: 0x010F productType: 0x0102 deviceProfileName: fibaro-dimmer-2 +#Shelly/Qubino + - id: 1120/2/137 + deviceLabel: Wave Plug UK + manufacturerId: 0x0460 + productId: 0x0089 + productType: 0x0002 + deviceProfileName: smartplug-switch-power-energy + - id: 1120/2/136 + deviceLabel: Wave Plug US + manufacturerId: 0x0460 + productId: 0x0088 + productType: 0x0002 + deviceProfileName: smartplug-switch-power-energy zwaveGeneric: - id: "GenericSwitch/1" deviceLabel: Switch