Compare commits

..

1 Commits

10 changed files with 258 additions and 92 deletions

View File

@@ -1,11 +1,10 @@
# Zigbee Dimmer
# OSRAM Lightify LED On/Off/Dim
Works with:
* [OSRAM Lightify LED On/Off/Dim](https://shop.smartthings.com/#!/products/osram-led-smart-bulb-on-off-dim)
* [WeMo LED Bulb](https://support.smartthings.com/hc/en-us/articles/204259040-Belkin-WeMo-LED-Bulb-F7C033-)
## Table of contents
@@ -24,16 +23,14 @@ Works with:
## Device Health
A Zigbee dimmer with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
A Category C1 Zigbee dimmer with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
This gives the device twice the amount of time to respond before it is marked as offline.
Enrolls with default periodic reporting until newer 5 min interval is confirmed
It then enrolls the device with updated checkInterval i.e. 12 mins
Check-in interval = 12 mins
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
Pairing needs to be tried again by placing the device closer to the hub.
Other troubleshooting tips are listed as follows:
* [OSRAM Lightify LED On/Off/Dim Troubleshooting:](https://support.smartthings.com/hc/en-us/articles/207191763-OSRAM-LIGHTIFY-LED-Smart-Connected-Light-A19-On-Off-Dim)
* [WeMo LED Bulb Troubleshooting:](https://support.smartthings.com/hc/en-us/articles/204259040-Belkin-WeMo-LED-Bulb-F7C033-)
* [Troubleshooting:](https://support.smartthings.com/hc/en-us/articles/207191763-OSRAM-LIGHTIFY-LED-Smart-Connected-Light-A19-On-Off-Dim)

View File

@@ -1,11 +1,10 @@
# ZigBee White Color Temperature Bulb
# OSRAM Lightify Tunable 60 White
Works with:
* [OSRAM Lightify Tunable 60 White](http://www.osram.com/osram_com/tools-and-services/tools/lightify---smart-connected-light/lightify-for-home---what-is-light-to-you/lightify-products/lightify-classic-a60-tunable-white/index.jsp)
* [OSRAM LIGHTIFY RT5/6 Tunable White](https://www.smartthings.com/works-with-smartthings/light-bulbs/osram-lightify-rt56-tunable-white)
## Table of contents
@@ -25,16 +24,14 @@ Works with:
## Device Health
Zigbee Bulb with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
A Category C1 OSRAM Lightify Tunable 60 White with maxReportTime of 5 mins.
Check-in interval is double the value of maxReportTime.
This gives the device twice the amount of time to respond before it is marked as offline.
Enrolls with default periodic reporting until newer 5 min interval is confirmed
It then enrolls the device with updated checkInterval i.e. 12 mins
Check-in interval = 12 mins
## Troubleshooting
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
Pairing needs to be tried again by placing the device closer to the hub.
Other troubleshooting tips are listed as follows:
* [OSRAM Lightify Tunable 60 White Troubleshooting](https://support.smartthings.com/hc/en-us/articles/204576454-OSRAM-LIGHTIFY-Tunable-White-60-Bulb)
* [OSRAM LIGHTIFY RT5/6 Tunable White Troubleshooting](https://support.smartthings.com/hc/en-us/articles/214191863-How-to-connect-OSRAM-LIGHTIFY-Bulbs)
* [Troubleshooting:](https://support.smartthings.com/hc/en-us/articles/204576454-OSRAM-LIGHTIFY-Tunable-White-60-Bulb)

View File

@@ -33,7 +33,7 @@ metadata {
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300", outClusters: "0019"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04", outClusters: "0019"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY BR Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Flood BR30 Tunable White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT Tunable White", deviceJoinName: "OSRAM LIGHTIFY RT5/6 Tunable White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY RT Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Recessed Kit RT 5/6 Tunable White"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 TW", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "LIGHTIFY A19 Tunable White", deviceJoinName: "OSRAM LIGHTIFY LED Tunable White 60W"
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 0300, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic B40 TW - LIGHTIFY", deviceJoinName: "OSRAM LIGHTIFY Classic B40 Tunable White"

View File

@@ -4,8 +4,7 @@
Works with:
* [Leviton Plug-in Lamp Dimmer Module (DZPD3-1LW)](https://www.smartthings.com/works-with-smartthings/outlets/leviton-plug-in-lamp-dimmer-module)
* [Leviton Universal Dimmer (DZMX1-LZ)](https://www.smartthings.com/works-with-smartthings/switches-and-dimmers/leviton-universal-dimmer)
* [Leviton Plug-in Lamp Dimmer Module (DZPD3-1LW)](http://www.leviton.com/OA_HTML/ProductDetail.jsp?partnumber=DZPD3-1LW)
## Table of contents
@@ -25,7 +24,7 @@ Works with:
## Device Health
Leviton Plug-in Lamp Dimmer Module (DZPA1-1LW) (Z-wave) and Leviton Universal Dimmer (DZMX1-LZ) (Z-Wave) are polled by the hub.
A Category C5 Leviton Plug-in Lamp Dimmer Module (DZPA1-1LW) (Z-Wave) polled by the hub.
As of hubCore version 0.14.38 the hub sends up reports every 15 minutes regardless of whether the state changed.
Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*15 + 2)mins = 32 mins.
Not to mention after going OFFLINE when the device is plugged back in, it might take a considerable amount of time for
@@ -37,5 +36,4 @@ it is not polled for 5 minutes by the hub. This can delay up the process of bein
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
Pairing needs to be tried again by placing the device closer to the hub.
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
* [Leviton Plug-in Lamp Dimmer Module (DZPD3-1LW) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices)
* [Leviton Universal Dimmer (DZMX1-LZ) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices)
* [Leviton Plug-in Lamp Dimmer Module (DZPD3-1LW) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices)

View File

@@ -23,7 +23,6 @@ metadata {
fingerprint inClusters: "0x26", deviceJoinName: "Z-Wave Dimmer"
fingerprint mfr:"001D", prod:"1902", deviceJoinName: "Z-Wave Dimmer"
fingerprint mfr:"001D", prod:"1B03", model:"0334", deviceJoinName: "Leviton Universal Dimmer"
}
simulator {

View File

@@ -4,10 +4,8 @@
Works with:
* [Leviton Appliance Module (DZPA1-1LW)](https://www.smartthings.com/works-with-smartthings/outlets/leviton-appliance-module)
* [GE Plug-In Outdoor Smart Switch (GE 12720) (Z-Wave)](https://www.smartthings.com/works-with-smartthings/outlets/ge-plug-in-outdoor-smart-switch)
* [Leviton Outlet (DZR15-1LZ)](https://www.smartthings.com/works-with-smartthings/outlets/leviton-outlet)
* [Leviton Switch (DZS15-1LZ)](https://www.smartthings.com/works-with-smartthings/switches-and-dimmers/leviton-switch)
* [Leviton Appliance Module (DZPA1-1LW)](https://support.smartthings.com/hc/en-us/articles/205881176-Leviton-Appliance-Module-DZPA1-1LW-)
* [GE Plug-In Outdoor Smart Switch (GE 12720) (Z-Wave)](https://support.smartthings.com/hc/en-us/articles/200903080-GE-Plug-In-Outdoor-Smart-Switch-GE-12720-Z-Wave-)
## Table of contents
@@ -25,7 +23,7 @@ Works with:
## Device Health
Leviton Appliance Module (DZPA1-1LW), GE Plug-In Outdoor Smart Switch (GE 12720), Leviton Outlet (DZR15-1LZ) and Leviton Switch (DZS15-1LZ) (Z-Wave) are polled by the hub.
A Category C5 Leviton Appliance Module (DZPA1-1LW) and GE Plug-In Outdoor Smart Switch (GE 12720) (Z-Wave) polled by the hub.
As of hubCore version 0.14.38 the hub sends up reports every 15 minutes regardless of whether the state changed.
Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*15 + 2)mins = 32 mins.
Not to mention after going OFFLINE when the device is plugged back in, it might take a considerable amount of time for
@@ -37,7 +35,5 @@ it is not polled for 5 minutes by the hub. This can delay up the process of bein
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
Pairing needs to be tried again by placing the device closer to the hub.
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
* [Leviton Appliance Module (DZPA1-1LW) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices)
* [GE Plug-In Outdoor Smart Switch (GE 12720) (Z-Wave) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/200903080-GE-Plug-In-Outdoor-Smart-Switch-GE-12720-Z-Wave-)
* [Leviton Outlet (DZR15-1LZ) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices)
* [Leviton Switch (DZS15-1LZ) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206171053-How-to-connect-Leviton-Z-Wave-devices)
* [Leviton Appliance Module (DZPA1-1LW) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205881176-Leviton-Appliance-Module-DZPA1-1LW-)
* [GE Plug-In Outdoor Smart Switch (GE 12720) (Z-Wave) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/200903080-GE-Plug-In-Outdoor-Smart-Switch-GE-12720-Z-Wave-)

View File

@@ -23,8 +23,6 @@ metadata {
fingerprint inClusters: "0x25", deviceJoinName: "Z-Wave Switch"
fingerprint mfr:"001D", prod:"1A02", model:"0334", deviceJoinName: "Leviton Appliance Module"
fingerprint mfr:"0063", prod:"4F50", model:"3031", deviceJoinName: "GE Plug-in Outdoor Switch"
fingerprint mfr:"001D", prod:"1D04", model:"0334", deviceJoinName: "Leviton Outlet"
fingerprint mfr:"001D", prod:"1C02", model:"0334", deviceJoinName: "Leviton Switch"
}
// simulator metadata

View File

@@ -0,0 +1,238 @@
/**
* Switch Timer
*
* Copyright 2016 Joe Saporito
*
* 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.
*
*/
definition(
name: "Switch Timer",
namespace: "js",
author: "Joe Saporito",
description: "Turns on switches that are not already on for a determined amount of time after an event. ",
category: "Convenience",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")
preferences {
page(name: "settings")
page(name: "certainTime")
page(name: "renameLabel")
}
def settings() {
dynamicPage(name: "settings", title: "Turn switches off after some minutes, unless already on", uninstall: true, install: true) {
section("Choose one or more, when..."){
input "people", "capability.presenceSensor", title: "Arrival Of", required: false, multiple: true
input "doors", "capability.contactSensor", title: "Doors", required: false, multiple: true
input "motion", "capability.motionSensor", title: "Motion Here", required: false, multiple: true
}
section("Turn on switches"){
input "switches", "capability.switch", multiple: true, required: true
}
section("Turn off after") {
input "waitTime", "decimal", title: "Minutes", required: true
}
section(title: "Additional Options") {
def timeLabel = timeIntervalLabel()
href "certainTime", title: "Only during a certain time", description: timeLabel ?: "Tap to set", state: timeLabel ? "complete" : null
def appLabel = getDefaultLabel()
href "renameLabel", title: "Rename '" + appLabel + "'", description: ""
}
}
}
def certainTime() {
dynamicPage(name:"certainTime",title: "Only during a certain time", uninstall: false) {
section() {
input "startEnum", "enum", title: "Starting at", options: ["A specific time", "Sunrise", "Sunset"], defaultValue: "A specific time", submitOnChange: true
if(startEnum in [null, "A specific time"]) input "startTime", "time", title: "Start time", required: false
else {
if(startEnum == "Sunrise") input "startSunriseOffset", "number", range: "*..*", title: "Offset in minutes (+/-)", required: false
else if(startEnum == "Sunset") input "startSunsetOffset", "number", range: "*..*", title: "Offset in minutes (+/-)", required: false
}
}
section() {
input "endEnum", "enum", title: "Ending at", options: ["A specific time", "Sunrise", "Sunset"], defaultValue: "A specific time", submitOnChange: true
if(endEnum in [null, "A specific time"]) input "endTime", "time", title: "End time", required: false
else {
if(endEnum == "Sunrise") input "endSunriseOffset", "number", range: "*..*", title: "Offset in minutes (+/-)", required: false
else if(endEnum == "Sunset") input "endSunsetOffset", "number", range: "*..*", title: "Offset in minutes (+/-)", required: false
}
}
}
}
def renameLabel() {
dynamicPage(name:"renameLabel",title: "Rename", uninstall: false) {
section() {
input "appLabelText", "text", title: "Rename to", defaultValue: (appLabelText ? appLabelText : getDefaultLabel()), required: false
}
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
updateLabel()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
updateLabel()
}
def initialize() {
subscribeToEvents()
}
def subscribeToEvents() {
subscribe(doors, "contact.open", activateHandler)
subscribe(people, "presence.present", activateHandler)
subscribe(motion, "motion.active", activateHandler)
}
// TODO: implement event handlers
def activateHandler(evt) {
if (checkTime()) {
log.trace("$evt.name triggered")
turnOn()
}
}
def turnOn() {
if(state.offSwitches) {
runIn(waitTime * 60, turnOff)
return
}
def offSwitches = getOffSwitches()
for (s in offSwitches) {
s.on()
}
if (offSwitches.size() > 0) {
state.offSwitches = offSwitches.displayName
runIn(waitTime * 60, turnOff)
}
}
def turnOff() {
if (state.offSwitches) {
def offSwitches = switches.findAll {
it.displayName in state.offSwitches
}
for (s in offSwitches) {
s.off()
}
state.offSwitches = null
}
}
def updateLabel() {
if (appLabelText) {
if (appLabelText != app.label) {
log.trace("Renamed to $appLabelText")
app.updateLabel(appLabelText)
}
}
else {
def label = getDefaultLabel()
log.trace("Renamed to $label")
app.updateLabel(label)
}
}
private getDefaultLabel() {
def label = "Switch Timer"
if (switches) {
label = "Timer for " + switches[0].displayName
}
return label
}
private getOffSwitches() {
def offSwitches = switches.findAll {
it.currentSwitch == "off" ||
it.currentSwitch == null
}
return offSwitches
}
private checkTime() {
def result = true
if ((startTime && endTime) ||
(startTime && endEnum in ["Sunrise", "Sunset"]) ||
(startEnum in ["Sunrise", "Sunset"] && endTime) ||
(startEnum in ["Sunrise", "Sunset"] && endEnum in ["Sunrise", "Sunset"])) {
def currentTime = now()
def start = null
def stop = null
def sun = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: startSunriseOffset, sunsetOffset: startSunsetOffset)
if (startEnum == "Sunrise")
start = sun.sunrise.time
else if (startEnum == "Sunset")
start = sun.sunset.time
else if (startTime)
start = timeToday(startTime,location.timeZone).time
sun = getSunriseAndSunset(zipCode: zipCode, sunriseOffset: endSunriseOffset, sunsetOffset: endSunsetOffset)
if (endEnum == "Sunrise")
stop = sun.sunrise.time
else if (endEnum == "Sunset")
stop = sun.sunset.time
else if (endTime)
stop = timeToday(endTime,location.timeZone).time
result = start < stop ? currentTime >= start && currentTime <= stop : currentTime <= stop || currentTime >= start
}
return result
}
private hideOptionsSection() {
(startTime || endTime || startEnum || endEnum) ? false : true
}
private offset(value) {
def result = value ? ((value > 0 ? "+" : "") + value + " min") : ""
}
private hhmm(time, fmt = "h:mm a") {
def t = timeToday(time, location.timeZone)
def f = new java.text.SimpleDateFormat(fmt)
f.setTimeZone(location.timeZone ?: timeZone(time))
f.format(t)
}
private timeIntervalLabel() {
def result = ""
if (startEnum == "Sunrise" && endEnum == "Sunrise") result = "Sunrise" + offset(startSunriseOffset) + " to " + "Sunrise" + offset(endSunriseOffset)
else if (startEnum == "Sunrise" && endEnum == "Sunset") result = "Sunrise" + offset(startSunriseOffset) + " to " + "Sunset" + offset(endSunsetOffset)
else if (startEnum == "Sunset" && endEnum == "Sunrise") result = "Sunset" + offset(startSunsetOffset) + " to " + "Sunrise" + offset(endSunriseOffset)
else if (startEnum == "Sunset" && endEnum == "Sunset") result = "Sunset" + offset(startSunsetOffset) + " to " + "Sunset" + offset(endSunsetOffset)
else if (startEnum == "Sunrise" && endTime) result = "Sunrise" + offset(startSunriseOffset) + " to " + hhmm(endTime, "h:mm a z")
else if (startEnum == "Sunset" && endTime) result = "Sunset" + offset(startSunsetOffset) + " to " + hhmm(endTime, "h:mm a z")
else if (startTime && endEnum == "Sunrise") result = hhmm(startTime) + " to " + "Sunrise" + offset(endSunriseOffset)
else if (startTime && endEnum == "Sunset") result = hhmm(startTime) + " to " + "Sunset" + offset(endSunsetOffset)
else if (startTime && endTime) result = hhmm(startTime) + " to " + hhmm(endTime, "h:mm a z")
}

View File

@@ -1,50 +0,0 @@
/**
* name
*
* Copyright 2016 Jo&atilde;o Borges
*
* 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.
*
*/
definition(
name: "name",
namespace: "namespace",
author: "Jo&atilde;o Borges",
description: "description",
category: "SmartThings Labs",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")
preferences {
section("Title") {
// TODO: put inputs here
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize()
}
def initialize() {
// TODO: subscribe to attributes, devices, locations, etc.
}
// TODO: implement event handlers

View File

@@ -24,12 +24,6 @@
* switches | switch | on, off | on, off
* motionSensors | motion | | active, inactive
* contactSensors | contact | | open, closed
* thermostat | thermostat | setHeatingSetpoint, | temperature, heatingSetpoint
* | | setCoolingSetpoint(number) | coolingSetpoint, thermostatSetpoint
* | | off, heat, emergencyHeat | thermostatMode — ["emergency heat", "auto", "cool", "off", "heat"]
* | | cool, setThermostatMode | thermostatFanMode — ["auto", "on", "circulate"]
* | | fanOn, fanAuto, fanCirculate| thermostatOperatingState — ["cooling", "heating", "pending heat",
* | | setThermostatFanMode, auto | "fan only", "vent economizer", "pending cool", "idle"]
* presenceSensors | presence | | present, 'not present'
* temperatureSensors | temperature | | <numeric, F or C according to unit>
* accelerationSensors | acceleration | | active, inactive
@@ -64,7 +58,6 @@ preferences(oauthPage: "deviceAuthorization") {
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
input "motionSensors", "capability.motionSensor", title: "Which Motion Sensors?", multiple: true, required: false
input "contactSensors", "capability.contactSensor", title: "Which Contact Sensors?", multiple: true, required: false
input "thermostats", "capability.thermostat", title: "Which Thermostats?", multiple: true, required: false
input "presenceSensors", "capability.presenceSensor", title: "Which Presence Sensors?", multiple: true, required: false
input "temperatureSensors", "capability.temperatureMeasurement", title: "Which Temperature Sensors?", multiple: true, required: false
input "accelerationSensors", "capability.accelerationSensor", title: "Which Vibration Sensors?", multiple: true, required: false
@@ -943,7 +936,7 @@ def deleteHarmony() {
}
private getAllDevices() {
([] + switches + motionSensors + contactSensors + thermostats + presenceSensors + temperatureSensors + accelerationSensors + waterSensors + lightSensors + humiditySensors + alarms + locks)?.findAll()?.unique { it.id }
([] + switches + motionSensors + contactSensors + presenceSensors + temperatureSensors + accelerationSensors + waterSensors + lightSensors + humiditySensors + alarms + locks)?.findAll()?.unique { it.id }
}
private deviceItem(device) {