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
5 changes: 5 additions & 0 deletions drivers/SmartThings/zigbee-window-treatment/fingerprints.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ zigbeeManufacturer:
manufacturer: Shade Revolution
model: Indoor Shade Motors
deviceProfileName: window-treatment-powerSource
- id: "VIVIDSTORM/VWSDSTUST120H"
deviceLabel: VIVIDSTORM Smart Screen
manufacturer: VIVIDSTORM
model: VWSDSTUST120H
deviceProfileName: projector-screen-VWSDSTUST120H

zigbeeGeneric:
- id: "genericShade"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: projector-screen-VWSDSTUST120H
components:
- label: " "
id: main
capabilities:
- id: windowShade
version: 1
- id: mode
version: 1
- id: firmwareUpdate
version: 1
- id: refresh
categories:
- name: Projector
- label: " "
id: hardwareFault
capabilities:
- id: hardwareFault
version: 1
metadata:
mnmn: SolutionsEngineering
vid: SmartThings-smartthings-VIVIDSTORM_Projector_Screen
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
-- Copyright 2025 SmartThings
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.

local data_types = require "st.zigbee.data_types"

local custom_clusters = {
motor = {
id = 0xFCC9,
mfg_specific_code = 0x1235,
attributes = {
mode_value = {
id = 0x0000,
value_type = data_types.Uint8,
},
hardwareFault = {
id = 0x0001,
value_type = data_types.Uint8,
}
}
}
}

return custom_clusters
187 changes: 187 additions & 0 deletions drivers/SmartThings/zigbee-window-treatment/src/VIVIDSTORM/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
-- Copyright 2025 SmartThings
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.

local zcl_clusters = require "st.zigbee.zcl.clusters"
local capabilities = require "st.capabilities"
local custom_clusters = require "VIVIDSTORM/custom_clusters"
local cluster_base = require "st.zigbee.cluster_base"
local WindowCovering = zcl_clusters.WindowCovering

local MOST_RECENT_SETLEVEL = "windowShade_recent_setlevel"
local TIMER = "liftPercentage_timer"


local ZIGBEE_WINDOW_SHADE_FINGERPRINTS = {
{ mfr = "VIVIDSTORM", model = "VWSDSTUST120H" }
}

local is_zigbee_window_shade = function(opts, driver, device)
for _, fingerprint in ipairs(ZIGBEE_WINDOW_SHADE_FINGERPRINTS) do
if device:get_manufacturer() == fingerprint.mfr and device:get_model() == fingerprint.model then
return true
end
end
return false
end

local function send_read_attr_request(device, cluster, attr)
device:send(
cluster_base.read_manufacturer_specific_attribute(
device,
cluster.id,
attr.id,
cluster.mfg_specific_code
)
)
end

local function mode_attr_handler(driver, device, value, zb_rx)
if value.value == 0 then
device:emit_component_event(device.profile.components.main,capabilities.mode.mode("Delete upper limit"))
elseif value.value == 1 then
device:emit_component_event(device.profile.components.main,capabilities.mode.mode("Set the upper limit"))
elseif value.value == 2 then
device:emit_component_event(device.profile.components.main,capabilities.mode.mode("Delete lower limit"))
elseif value.value == 3 then
device:emit_component_event(device.profile.components.main,capabilities.mode.mode("Set the lower limit"))
end
end


local function liftPercentage_attr_handler(driver, device, value, zb_rx)
local windowShade = capabilities.windowShade.windowShade
local components = device.profile.components.main
local most_recent_setlevel = device:get_field(MOST_RECENT_SETLEVEL)
if value.value and most_recent_setlevel and value.value ~= most_recent_setlevel then
if value.value > most_recent_setlevel then
device:emit_component_event(components,windowShade.opening())
elseif value.value < most_recent_setlevel then
device:emit_component_event(components,windowShade.closing())
end
end
device:set_field(MOST_RECENT_SETLEVEL, value.value)

local timer = device:get_field(TIMER)
if timer ~= nil then driver:cancel_timer(timer) end
timer = device.thread:call_with_delay(5, function(d)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the 5 sec timer set properly? Does this 5 sec interval work well for curtain tracks of different lengths?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The current experimental data is only applicable to this product. For other products, adjustments will be made based on the test results in the future.

if most_recent_setlevel == 0 then
device:emit_component_event(components,windowShade.closed())
elseif most_recent_setlevel == 100 then
device:emit_component_event(components,windowShade.open())
else
device:emit_component_event(components,windowShade.partially_open())
end
end
)
device:set_field(TIMER, timer)
end

local function hardwareFault_attr_handler(driver, device, value, zb_rx)
if value.value == 1 then
device:emit_component_event(device.profile.components.hardwareFault,capabilities.hardwareFault.hardwareFault.detected())
elseif value.value == 0 then
device:emit_component_event(device.profile.components.hardwareFault,capabilities.hardwareFault.hardwareFault.clear())
end
end

local function capabilities_mode_handler(driver, device, command)
if command.args.mode == "Delete upper limit" then
device:send(
cluster_base.write_manufacturer_specific_attribute(
device,
custom_clusters.motor.id,
custom_clusters.motor.attributes.mode_value.id,
custom_clusters.motor.mfg_specific_code,
custom_clusters.motor.attributes.mode_value.value_type,
0
)
)
elseif command.args.mode == "Set the upper limit" then
device:send(
cluster_base.write_manufacturer_specific_attribute(
device,
custom_clusters.motor.id,
custom_clusters.motor.attributes.mode_value.id,
custom_clusters.motor.mfg_specific_code,
custom_clusters.motor.attributes.mode_value.value_type,
1
)
)
elseif command.args.mode == "Delete lower limit" then
device:send(
cluster_base.write_manufacturer_specific_attribute(
device,
custom_clusters.motor.id,
custom_clusters.motor.attributes.mode_value.id,
custom_clusters.motor.mfg_specific_code,
custom_clusters.motor.attributes.mode_value.value_type,
2
)
)
elseif command.args.mode == "Set the lower limit" then
device:send(
cluster_base.write_manufacturer_specific_attribute(
device,
custom_clusters.motor.id,
custom_clusters.motor.attributes.mode_value.id,
custom_clusters.motor.mfg_specific_code,
custom_clusters.motor.attributes.mode_value.value_type,
3
)
)
end
end

local function do_refresh(driver, device)
device:send(WindowCovering.attributes.CurrentPositionLiftPercentage:read(device):to_endpoint(0x01))
send_read_attr_request(device, custom_clusters.motor, custom_clusters.motor.attributes.mode_value)
send_read_attr_request(device, custom_clusters.motor, custom_clusters.motor.attributes.hardwareFault)
end

local function added_handler(self, device)
device:emit_component_event(device.profile.components.hardwareFault,capabilities.hardwareFault.hardwareFault.clear())
do_refresh(self, device)
end

local screen_handler = {
NAME = "VWSDSTUST120H Device Handler",
supported_capabilities = {
capabilities.refresh
},
lifecycle_handlers = {
added = added_handler
},
capability_handlers = {
[capabilities.refresh.ID] = {
[capabilities.refresh.commands.refresh.NAME] = do_refresh
},
[capabilities.mode.ID] = {
[capabilities.mode.commands.setMode.NAME] = capabilities_mode_handler
},
},
zigbee_handlers = {
attr = {
[WindowCovering.ID] = {
[WindowCovering.attributes.CurrentPositionLiftPercentage.ID] = liftPercentage_attr_handler
},
[custom_clusters.motor.id] = {
[custom_clusters.motor.attributes.mode_value.id] = mode_attr_handler,
[custom_clusters.motor.attributes.hardwareFault.id] = hardwareFault_attr_handler
}
}
},
can_handle = is_zigbee_window_shade,
}

return screen_handler
3 changes: 2 additions & 1 deletion drivers/SmartThings/zigbee-window-treatment/src/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ local zigbee_window_treatment_driver_template = {
require("axis"),
require("yoolax"),
require("hanssem"),
require("screen-innovations")},
require("screen-innovations"),
require("VIVIDSTORM")},
lifecycle_handlers = {
added = added_handler
},
Expand Down
Loading
Loading