Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Aqara] Cube T1 Pro #930

Merged
merged 10 commits into from
Dec 1, 2023
29 changes: 29 additions & 0 deletions drivers/Aqara/aqara-cube/capabilities/cubeAction.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
id: stse.cubeAction
version: 1
status: proposed
name: Cube Action
ephemeral: false
attributes:
cubeAction:
schema:
type: object
properties:
value:
title: ActionType
type: string
enum:
- noAction
- shake
- rotate
- pickUpAndHold
- flipToSide1
- flipToSide2
- flipToSide3
- flipToSide4
- flipToSide5
- flipToSide6
additionalProperties: false
required:
- value
enumCommands: []
commands: {}
25 changes: 25 additions & 0 deletions drivers/Aqara/aqara-cube/capabilities/cubeFace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
id: stse.cubeFace
version: 1
status: proposed
name: Cube Face
ephemeral: false
attributes:
cubeFace:
schema:
type: object
properties:
value:
title: CubeFace
type: string
enum:
- face1Up
- face2Up
- face3Up
- face4Up
- face5Up
- face6Up
additionalProperties: false
required:
- value
enumCommands: []
commands: {}
6 changes: 6 additions & 0 deletions drivers/Aqara/aqara-cube/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name: 'Aqara Cube'
packageKey: 'aqara-cube'
permissions:
zigbee: {}
description: "SmartThings driver for Aqara Cube devices"
vendorSupportInformation: "https://www.aqara.com/en/support/"
6 changes: 6 additions & 0 deletions drivers/Aqara/aqara-cube/fingerprints.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
zigbeeManufacturer:
- id: "Lumi/lumi.remote.cagl02"
deviceLabel: Aqara Cube T1 Pro
manufacturer: LUMI
model: lumi.remote.cagl02
deviceProfileName: cube-t1-pro
17 changes: 17 additions & 0 deletions drivers/Aqara/aqara-cube/profiles/cube-t1-pro.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: cube-t1-pro
components:
- id: main
capabilities:
- id: stse.cubeAction
version: 1
- id: stse.cubeFace
version: 1
- id: battery
version: 1
- id: firmwareUpdate
version: 1
categories:
- name: RemoteController
metadata:
mnmn: SolutionsEngineering
vid: SmartThings-smartthings-Aqara_CubeT1Pro
127 changes: 127 additions & 0 deletions drivers/Aqara/aqara-cube/src/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
local ZigbeeDriver = require "st.zigbee"
local capabilities = require "st.capabilities"
local data_types = require "st.zigbee.data_types"
local cluster_base = require "st.zigbee.cluster_base"
local battery_defaults = require "st.zigbee.defaults.battery_defaults"
local clusters = require "st.zigbee.zcl.clusters"
local PowerConfiguration = clusters.PowerConfiguration

local PRI_CLU = 0xFCC0
local PRI_ATTR = 0x0009
local MFG_CODE = 0x115F

local ROTATE_CLU = 0x000C
local EVENT_CLU = 0x0012
local FACE_ATTR = 0x0149
local ACTION_ATTR = 0x0055
local CUBE_MODE = 0x0148

local cubeAction = capabilities["stse.cubeAction"]
local cubeFace = capabilities["stse.cubeFace"]
local cubeFaceVal = { "face1Up", "face2Up", "face3Up", "face4Up", "face5Up", "face6Up" }
local cubeFlipToSideVal = { "flipToSide1", "flipToSide2", "flipToSide3", "flipToSide4", "flipToSide5", "flipToSide6" }

local CUBEACTION_TIMER = "cubeAction_timer"
local CUBEACTION_TIME = 3

local FINGERPRINTS = { mfr = "LUMI", model = "lumi.remote.cagl02" }

local is_aqara_products = function(opts, driver, device, ...)
return device:get_manufacturer() == FINGERPRINTS.mfr and device:get_model() == FINGERPRINTS.model
end

local callback_timer = function(device)
return function()
device:emit_event(cubeAction.cubeAction("noAction"))
end
end

local function reset_thread(device)
local timer = device:get_field(CUBEACTION_TIMER)
if timer then
device.thread:cancel_timer(timer)
device:set_field(CUBEACTION_TIMER, nil)
end
device:set_field(CUBEACTION_TIMER, device.thread:call_with_delay(CUBEACTION_TIME, callback_timer(device)))
end

local function data_handler(driver, device, value, zb_rx)
local val = value.value
if val == 0x0000 then -- Shake
reset_thread(device)
device:emit_event(cubeAction.cubeAction("shake"))
elseif val == 0x0004 then -- hold
reset_thread(device)
device:emit_event(cubeAction.cubeAction("pickUpAndHold"))
elseif val & 0x0400 == 0x0400 then -- Flip to side
local faceNum = val & 0x0007
reset_thread(device)
device:emit_event(cubeAction.cubeAction(cubeFlipToSideVal[faceNum + 0x1]))
end
end

local function rotate_handler(driver, device, value, zb_rx)
-- Rotation
reset_thread(device)
device:emit_event(cubeAction.cubeAction("rotate"))
end

local function face_handler(driver, device, value, zb_rx)
local faceNum = value.value
device:emit_event(cubeFace.cubeFace(cubeFaceVal[faceNum + 1]))
end

local function device_init(driver, device)
local power_configuration = {
cluster = PowerConfiguration.ID,
attribute = PowerConfiguration.attributes.BatteryVoltage.ID,
minimum_interval = 30,
maximum_interval = 3600,
data_type = PowerConfiguration.attributes.BatteryVoltage.base_type,
reportable_change = 1
}

battery_defaults.build_linear_voltage_init(2.6, 3.0)(driver, device)

device:add_configured_attribute(power_configuration)
device:add_monitored_attribute(power_configuration)
end

local function device_added(self, device)
-- Set private attribute
device:send(cluster_base.write_manufacturer_specific_attribute(device,
PRI_CLU, PRI_ATTR, MFG_CODE, data_types.Uint8, 1))

device:send(cluster_base.write_manufacturer_specific_attribute(device,
PRI_CLU, CUBE_MODE, MFG_CODE, data_types.Uint8, 1))
device:emit_event(cubeAction.cubeAction("noAction"))
device:emit_event(cubeFace.cubeFace("face1Up"))
device:emit_event(capabilities.battery.battery(100))
end

-- [[ register ]]
local aqara_cube_t1_pro_handler = {
NAME = "Aqara Cube T1 Pro",
zigbee_handlers = {
attr = {
[EVENT_CLU] = {
[ACTION_ATTR] = data_handler
},
[ROTATE_CLU] = {
[ACTION_ATTR] = rotate_handler,
},
[PRI_CLU] = {
[FACE_ATTR] = face_handler
}
}
},
lifecycle_handlers = {
init = device_init,
added = device_added
},
can_handle = is_aqara_products
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as a base driver, you do not need to supply a can_handle function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was removed can_handle.

}

local aqara_cube_t1_pro_driver = ZigbeeDriver("aqara_cube_t1_pro", aqara_cube_t1_pro_handler)
aqara_cube_t1_pro_driver:run()

159 changes: 159 additions & 0 deletions drivers/Aqara/aqara-cube/src/test/test_aqara_cube_t1_pro.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
local test = require "integration_test"
local cluster_base = require "st.zigbee.cluster_base"
local t_utils = require "integration_test.utils"
local zigbee_test_utils = require "integration_test.zigbee_test_utils"
local capabilities = require "st.capabilities"
local data_types = require "st.zigbee.data_types"

local PRI_CLU = 0xFCC0
local PRI_ATTR = 0x0009
local MFG_CODE = 0x115F

local ROTATE_CLU = 0x000C
local EVENT_CLU = 0x0012
local FACE_ATTR = 0x0149
local ACTION_ATTR = 0x0055
local CUBE_MODE = 0x0148

local cubeAction = capabilities["stse.cubeAction"]
local cubeFace = capabilities["stse.cubeFace"]
test.add_package_capability("cubeAction.yaml")
test.add_package_capability("cubeFace.yaml")

local CUBEACTION_TIME = 3


local mock_device = test.mock_device.build_test_zigbee_device(
{
profile = t_utils.get_profile_definition("cube-t1-pro.yml"),
zigbee_endpoints = {
[1] = {
id = 1,
manufacturer = "LUMI",
model = "lumi.remote.cagl02",
server_clusters = { PRI_CLU }
}
}
}
)

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(
"lifecycle - added test",
function()
test.socket.zigbee:__set_channel_ordering("relaxed")
test.socket.capability:__set_channel_ordering("relaxed")
test.socket.device_lifecycle:__queue_receive({ mock_device.id, "added" })
-- private protocol enable
test.socket.zigbee:__expect_send({ mock_device.id,
cluster_base.write_manufacturer_specific_attribute(mock_device, PRI_CLU, PRI_ATTR, MFG_CODE,
data_types.Uint8, 1) })
-- init
test.socket.zigbee:__expect_send({ mock_device.id,
cluster_base.write_manufacturer_specific_attribute(mock_device, PRI_CLU, CUBE_MODE, MFG_CODE,
data_types.Uint8, 1) })
test.socket.capability:__expect_send(mock_device:generate_test_message("main", cubeAction.cubeAction("noAction")))
test.socket.capability:__expect_send(mock_device:generate_test_message("main", cubeFace.cubeFace("face1Up")))
test.socket.capability:__expect_send(mock_device:generate_test_message("main", capabilities.battery.battery(100)))
end
)

test.register_coroutine_test(
"data_handler test - shake",
function()
local attr_report_data = {
{ ACTION_ATTR, data_types.Uint16.ID, 0x0000 }
}
test.socket.zigbee:__queue_receive({
mock_device.id,
zigbee_test_utils.build_attribute_report(mock_device, EVENT_CLU, attr_report_data, MFG_CODE)
})
test.socket.capability:__expect_send(mock_device:generate_test_message("main",
cubeAction.cubeAction("shake")))
test.timer.__create_and_queue_test_time_advance_timer(CUBEACTION_TIME, "oneshot")
test.mock_time.advance_time(CUBEACTION_TIME)
test.socket.capability:__expect_send(mock_device:generate_test_message("main",
cubeAction.cubeAction("noAction")))
end
)

test.register_coroutine_test(
"data_handler test - pick up and hold",
function()
local attr_report_data = {
{ ACTION_ATTR, data_types.Uint16.ID, 0x0004 }
}
test.socket.zigbee:__queue_receive({
mock_device.id,
zigbee_test_utils.build_attribute_report(mock_device, EVENT_CLU, attr_report_data, MFG_CODE)
})
test.socket.capability:__expect_send(mock_device:generate_test_message("main",
cubeAction.cubeAction("pickUpAndHold")))
test.timer.__create_and_queue_test_time_advance_timer(CUBEACTION_TIME, "oneshot")
test.mock_time.advance_time(CUBEACTION_TIME)
test.socket.capability:__expect_send(mock_device:generate_test_message("main",
cubeAction.cubeAction("noAction")))
end
)

test.register_coroutine_test(
"data_handler test - flip to side 6",
function()
local attr_report_data = {
{ ACTION_ATTR, data_types.Uint16.ID, 0x0405 }
}
test.socket.zigbee:__queue_receive({
mock_device.id,
zigbee_test_utils.build_attribute_report(mock_device, EVENT_CLU, attr_report_data, MFG_CODE)
})
test.socket.capability:__expect_send(mock_device:generate_test_message("main",
cubeAction.cubeAction("flipToSide6")))
test.timer.__create_and_queue_test_time_advance_timer(CUBEACTION_TIME, "oneshot")
test.mock_time.advance_time(CUBEACTION_TIME)
test.socket.capability:__expect_send(mock_device:generate_test_message("main",
cubeAction.cubeAction("noAction")))
end
)

test.register_coroutine_test(
"rotate_handler test - rotation",
function()
local attr_report_data = {
{ ACTION_ATTR, data_types.Uint16.ID, 0x0000 }
}
test.socket.zigbee:__queue_receive({
mock_device.id,
zigbee_test_utils.build_attribute_report(mock_device, ROTATE_CLU, attr_report_data, MFG_CODE)
})
test.socket.capability:__expect_send(mock_device:generate_test_message("main",
cubeAction.cubeAction("rotate")))
test.timer.__create_and_queue_test_time_advance_timer(CUBEACTION_TIME, "oneshot")
test.mock_time.advance_time(CUBEACTION_TIME)
test.socket.capability:__expect_send(mock_device:generate_test_message("main",
cubeAction.cubeAction("noAction")))
end
)

test.register_coroutine_test(
"face_handler test - face up 1",
function()
local attr_report_data = {
{ FACE_ATTR, data_types.Uint8.ID, 0x00 }
}
test.socket.zigbee:__queue_receive({
mock_device.id,
zigbee_test_utils.build_attribute_report(mock_device, PRI_CLU, attr_report_data, MFG_CODE)
})
test.socket.capability:__expect_send(mock_device:generate_test_message("main",
cubeFace.cubeFace("face1Up")))
end
)

test.run_registered_tests()
Loading