Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 35 additions & 10 deletions drivers/SmartThings/zigbee-thermostat/src/popp/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ local turn_switch_on = function(driver, device)
end
end

-- Custom setpoint command handler
local setpoint_cmd_handler = function(driver, device, cmd)
-- custom thermostatMode_handler
local thermostat_mode_handler = function(driver, device, cmd)
local payload
local mode = cmd.args.mode

Expand All @@ -182,7 +182,7 @@ local setpoint_cmd_handler = function(driver, device, cmd)
-- convert setpoint value into bytes e.g. 25.5 -> 2550 -> \x09\xF6 -> \xF6\x09
local p2 = last_setpointTemp & 0xFF
local p3 = last_setpointTemp >> 8
local type = device:get_latest_state("main", ThermostatMode.ID, ThermostatMode.thermostatMode.heat.NAME) or 'eco'
local type = 0x00 -- eco

if mode == ThermostatMode.thermostatMode.heat.NAME then
-- Setpoint type "1": the actuator will make a large movement to minimize reaction time to UI
Expand Down Expand Up @@ -210,11 +210,31 @@ local setpoint_cmd_handler = function(driver, device, cmd)
end

-- temperature setpoint handler
local handle_set_setpoint = function(driver, device, command)
local thermostat_setpoint_handler = function(driver, device, command)
local value = command.args.setpoint
local type = 0x00 -- default eco

-- write new setpoint
device:send(Thermostat.attributes.OccupiedHeatingSetpoint:write(device, value * 100))
local mode = device:get_latest_state("main", ThermostatMode.ID, ThermostatMode.thermostatMode.NAME, 'eco')

if mode == ThermostatMode.thermostatMode.heat.NAME then
-- Setpoint type "1": the actuator will make a large movement to minimize reaction time to UI
type = 0x01
elseif mode == ThermostatMode.thermostatMode.eco.NAME then
-- Setpoint type "0": the behavior will be the same as setting the attribute "Occupied Heating Setpoint" to the same value
type = 0x00
end

-- prepare setpoint for correct 4 char dec format
local setpointTemp = math.floor(value * 100)

-- convert setpoint value into bytes e.g. 25.5 -> 2550 -> \x09\xF6 -> \xF6\x09
local p2 = setpointTemp & 0xFF
local p3 = setpointTemp >> 8

-- send thermostat setpoint command
local payload = string.char(type, p2, p3)
device:send(cluster_base.build_manufacturer_specific_command(device, Thermostat.ID, THERMOSTAT_SETPOINT_CMD_ID,
MFG_CODE, payload))

-- turn thermostat ventile on
turn_switch_on(driver, device)
Expand Down Expand Up @@ -252,13 +272,18 @@ end
-- handle heating setpoint
local thermostat_heating_set_point_attr_handler = function(driver, device, value, zb_rx)
local point_value = value.value
local new_heating_setpoint = point_value / 100
local last_heating_setpoint = device:get_latest_state("main", ThermostatHeatingSetpoint.ID, ThermostatHeatingSetpoint.heatingSetpoint.NAME)

device:emit_event(ThermostatHeatingSetpoint.heatingSetpoint({
value = point_value / 100,
value = new_heating_setpoint,
unit = "C"
}))

-- turn thermostat ventile on
turn_switch_on(driver, device)
if last_heating_setpoint ~= new_heating_setpoint then
turn_switch_on(driver, device)
end
end

-- handle external window open detection
Expand Down Expand Up @@ -377,10 +402,10 @@ local popp_thermostat = {
[capabilities.refresh.commands.refresh.NAME] = do_refresh
},
[ThermostatHeatingSetpoint.ID] = {
[ThermostatHeatingSetpoint.commands.setHeatingSetpoint.NAME] = handle_set_setpoint
[ThermostatHeatingSetpoint.commands.setHeatingSetpoint.NAME] = thermostat_setpoint_handler
},
[ThermostatMode.ID] = {
[ThermostatMode.commands.setThermostatMode.NAME] = setpoint_cmd_handler
[ThermostatMode.commands.setThermostatMode.NAME] = thermostat_mode_handler
},
[Switch.ID] = {
[Switch.commands.on.NAME] = switch_handler_factory('on'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ local ThermostatMode = capabilities.thermostatMode
local MFG_CODE = 0x1246
local ETRV_WINDOW_OPEN_DETECTION_ATTR_ID = 0x4000
local EXTERNAL_WINDOW_OPEN_DETECTION = 0x4003
local THERMOSTAT_SETPOINT_CMD_ID = 0x40

-- utils
local zigbee_test_utils = require "integration_test.zigbee_test_utils"
Expand Down Expand Up @@ -81,7 +82,7 @@ test.register_coroutine_test(
test.socket.zigbee:__expect_send(
{
mock_device.id,
Thermostat.attributes.OccupiedHeatingSetpoint:write(mock_device, 2750)
cluster_base.build_manufacturer_specific_command(mock_device, Thermostat.ID, THERMOSTAT_SETPOINT_CMD_ID, MFG_CODE, string.char(0x00, (math.floor(27.5 * 100) & 0xFF), (math.floor(27.5 * 100) >> 8)))
}
)
test.wait_for_events()
Expand All @@ -96,6 +97,44 @@ test.register_coroutine_test(
end
)

test.register_coroutine_test(
"External window open detection window open",
function()
test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot")
test.socket.capability:__queue_receive(
{
mock_device.id,
{ capability = "switch", component = "main", command = "on", args = {} }
}
)
test.socket.zigbee:__expect_send(
{
mock_device.id,
cluster_base.write_manufacturer_specific_attribute(mock_device, Thermostat.ID, EXTERNAL_WINDOW_OPEN_DETECTION, MFG_CODE, data_types.Boolean, false)
}
)
end
)

test.register_coroutine_test(
"External window open detection window closed",
function()
test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot")
test.socket.capability:__queue_receive(
{
mock_device.id,
{ capability = "switch", component = "main", command = "off", args = {} }
}
)
test.socket.zigbee:__expect_send(
{
mock_device.id,
cluster_base.write_manufacturer_specific_attribute(mock_device, Thermostat.ID, EXTERNAL_WINDOW_OPEN_DETECTION, MFG_CODE, data_types.Boolean, true)
}
)
end
)

test.register_coroutine_test(
"Configure should configure all necessary attributes",
function()
Expand Down Expand Up @@ -256,4 +295,73 @@ test.register_coroutine_test(
end
)

test.register_coroutine_test(
"Setting the thermostat mode to heat should generate the correct zigbee messages",
function()
test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot")
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)
}
)

test.wait_for_events()
test.mock_time.advance_time(2)
test.socket.zigbee:__expect_send(
{
mock_device.id,
Thermostat.attributes.SystemMode:read(mock_device)
}
)
end
)

test.register_coroutine_test(
"Setting the thermostat mode to off should generate the correct zigbee messages",
function()
test.timer.__create_and_queue_test_time_advance_timer(2, "oneshot")
test.socket.capability:__queue_receive(
{
mock_device.id,
{
component = "main",
capability = capabilities.thermostatMode.ID,
command = "off",
args = {}
}
}
)
test.socket.zigbee:__expect_send(
{
mock_device.id,
Thermostat.attributes.SystemMode:write(mock_device,
Thermostat.attributes.SystemMode.OFF)
}
)

test.wait_for_events()
test.mock_time.advance_time(2)
test.socket.zigbee:__expect_send(
{
mock_device.id,
Thermostat.attributes.SystemMode:read(mock_device)
}
)
end
)


test.run_registered_tests()