4 * Author: SmartThings - Ulises Mujica (Ule) - obycode
9 input(name: "customDelay", type: "enum", title: "Delay before msg (seconds)", options: ["0","1","2","3","4","5"])
10 input(name: "actionsDelay", type: "enum", title: "Delay between actions (seconds)", options: ["0","1","2","3"])
13 // Automatically generated. Make future change here.
14 definition (name: "Striim Light", namespace: "obycode", author: "SmartThings-Ulises Mujica") {
17 capability "Switch Level"
18 capability "Color Control"
23 attribute "model", "string"
24 attribute "temperature", "number"
25 attribute "brightness", "number"
26 attribute "lightMode", "string"
28 command "setColorHex" "string"
30 command "setTemperature"
38 standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
39 state "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"off"
40 state "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"on"
42 standardTile("reset", "device.reset", inactiveLabel: false, decoration: "flat") {
43 state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
45 standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
46 state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
48 standardTile("cycleColors", "device.color", inactiveLabel: false, decoration: "flat") {
49 state "default", label:"Colors", action:"cycleColors", icon:"st.unknown.thing.thing-circle"
51 standardTile("dimLevel", "device.level", inactiveLabel: false, decoration: "flat") {
52 state "default", label:"Dimmer Level", icon:"st.switches.light.on"
55 controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) {
56 state "color", action:"color control.setColor"
58 controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, range:"(0..100)") {
59 state "level", label:"Dimmer Level", action:"switch level.setLevel"
61 controlTile("tempSliderControl", "device.temperature", "slider", height: 1, width: 2, inactiveLabel: false, range:"(0..100)") {
62 state "temperature", label:"Temperature", action:"setTemperature"
64 valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
65 state "level", label: 'Level ${currentValue}%'
67 valueTile("lightMode", "device.lightMode", inactiveLabel: false, decoration: "flat") {
68 state "lightMode", label: 'Mode: ${currentValue}', action:"White"
72 details(["switch", "levelSliderControl", "tempSliderControl", "rgbSelector", "refresh", "cycleColors", "level", "lightMode"])
76 def parse(description) {
79 def msg = parseLanMessage(description)
81 def hdr = msg.header.split('\n')[0]
82 if (hdr.size() > 36) {
83 hdr = hdr[0..35] + "..."
87 if (msg.headers["SID"]) {
88 sid = msg.headers["SID"]
100 // log.debug "received $msg"
101 // log.debug "${msg.body}"
103 // Process level status
104 def node = msg.xml.Body.GetLoadLevelTargetResponse
106 return sendEvent(name: "level", value: node, description: "$device.displayName Level is $node")
109 // Process subscription updates
111 node = msg.xml.property.Status
113 def statusString = node == 0 ? "off" : "on"
114 return sendEvent(name: "switch", value: statusString, description: "$device.displayName Switch is $statusString")
118 node = msg.xml.property.LoadLevelStatus
120 return sendEvent(name: "level", value: node, description: "$device.displayName Level is $node")
123 // TODO: Do something with Temperature, Brightness and Mode in the UI
125 node = msg.xml.property.Temperature
127 def temp = node.text().toInteger()
132 result << sendEvent(name: "temperature", value: temp, description: "$device.displayName Temperature is $temp")
133 result << sendEvent(name: "lightMode", value: "White", description: "$device.displayName Mode is White")
138 node = msg.xml.property.Brightness
140 return sendEvent(name: "brightness", value: node, description: "$device.displayName Brightness is $node")
144 // node = msg.xml.property.CurrentMode
145 // if (node.size()) {
150 def bodyXml = parseXml(msg.xml.text())
153 def fields = node.text().split(',')
154 def colorHex = '#' + String.format('%02x%02x%02x', fields[0].toInteger(), fields[1].toInteger(), fields[2].toInteger())
156 result << sendEvent(name: "color", value: colorHex, description:"$device.displayName Color is $colorHex")
157 result << sendEvent(name: "lightMode", value: "Color", description: "$device.displayName Mode is Color")
161 catch (org.xml.sax.SAXParseException e) {
162 // log.debug "${msg.body}"
165 // Not sure what this is for?
167 def bodyHtml = msg.body ? msg.body.replaceAll('(<[a-z,A-Z,0-9,\\-,_,:]+>)','\n$1\n')
168 .replaceAll('(</[a-z,A-Z,0-9,\\-,_,:]+>)','\n$1\n')
169 .replaceAll('\n\n','\n').encodeAsHTML() : ""
170 results << createEvent(
171 name: "mediaRendererMessage",
172 value: "${msg.body.encodeAsMD5()}",
173 description: description,
174 descriptionText: "Body is ${msg.body?.size() ?: 0} bytes",
175 data: "<pre>${msg.headers.collect{it.key + ': ' + it.value}.join('\n')}</pre><br/><pre>${bodyHtml}</pre>",
176 isStateChange: false, displayed: false)
180 def bodyHtml = msg.body ? msg.body.replaceAll('(<[a-z,A-Z,0-9,\\-,_,:]+>)','\n$1\n')
181 .replaceAll('(</[a-z,A-Z,0-9,\\-,_,:]+>)','\n$1\n')
182 .replaceAll('\n\n','\n').encodeAsHTML() : ""
183 results << createEvent(
184 name: "unknownMessage",
185 value: "${msg.body.encodeAsMD5()}",
186 description: description,
187 descriptionText: "Body is ${msg.body?.size() ?: 0} bytes",
188 data: "<pre>${msg.headers.collect{it.key + ': ' + it.value}.join('\n')}</pre><br/><pre>${bodyHtml}</pre>",
189 isStateChange: true, displayed: true)
193 catch (Throwable t) {
194 //results << createEvent(name: "parseError", value: "$t")
195 sendEvent(name: "parseError", value: "$t", description: description)
202 sendEvent(name:"model",value:getDataValue("model"),isStateChange:true)
203 def result = [delayAction(5000)]
209 dimmableLightAction("SetTarget", "SwitchPower", getDataValue("spcurl"), [newTargetValue: 1])
213 dimmableLightAction("SetTarget", "SwitchPower", getDataValue("spcurl"), [newTargetValue: 0])
221 def eventTime = new Date().time
223 if(eventTime > state.secureEventTime ?:0) {
224 if ((state.lastRefreshTime ?: 0) > (state.lastStatusTime ?:0)) {
225 sendEvent(name: "status", value: "no_device_present", data: "no_device_present", displayed: false)
227 state.lastRefreshTime = eventTime
228 log.trace "Refresh()"
230 result << subscribe()
231 result << getCurrentLevel()
235 log.trace "Refresh skipped"
240 dimmableLightAction("SetLoadLevelTarget", "Dimming", getDataValue("dcurl"), [newLoadlevelTarget: val])
243 def setTemperature(val) {
244 // The API only accepts values 2700 - 6000, but it actually wants values
245 // between 0-100, so we need to do this weird offsetting (trial and error)
246 def offsetVal = val.toInteger() + 4016
247 awoxAction("SetTemperature", "X_WhiteLight", getDataValue("xwlcurl"), [Temperature: offsetVal.toString()])
251 def lastTemp = device.currentValue("temperature") + 4016
252 awoxAction("SetTemperature", "X_WhiteLight", getDataValue("xwlcurl"), [Temperature: lastTemp])
255 def setColor(value) {
256 def colorString = value.red.toString() + "," + value.green.toString() + "," + value.blue.toString()
257 awoxAction("SetRGBColor", "X_ColorLight", getDataValue("xclcurl"), [RGBColor: colorString])
260 def setColorHex(hexString) {
261 def colorString = convertHexToInt(hexString[1..2]).toString() + "," +
262 convertHexToInt(hexString[3..4]).toString() + "," +
263 convertHexToInt(hexString[5..6]).toString()
264 awoxAction("SetRGBColor", "X_ColorLight", getDataValue("xclcurl"), [RGBColor: colorString])
269 // This method models the button on the StriimLight remote that cycles through
270 // some pre-defined colors
272 def currentColor = device.currentValue("color")
273 switch(currentColor) {
275 setColorHex("#ffff00")
278 setColorHex("#00ffff")
281 setColorHex("#ff00ff")
284 setColorHex("#ff0000")
287 setColorHex("#00ff00")
290 setColorHex("#0000ff")
293 setColorHex("#0000ff")
299 log.trace "subscribe()"
301 result << subscribeAction(getDataValue("deurl"))
302 result << delayAction(2500)
303 result << subscribeAction(getDataValue("speurl"))
304 result << delayAction(2500)
305 result << subscribeAction(getDataValue("xcleurl"))
306 result << delayAction(2500)
307 result << subscribeAction(getDataValue("xwleurl"))
313 unsubscribeAction(getDataValue("deurl"), device.getDataValue('subscriptionId')),
314 unsubscribeAction(getDataValue("speurl"), device.getDataValue('subscriptionId')),
315 unsubscribeAction(getDataValue("xcleurl"), device.getDataValue('subscriptionId')),
316 unsubscribeAction(getDataValue("xwleurl"), device.getDataValue('subscriptionId')),
319 unsubscribeAction(getDataValue("deurl"), device.getDataValue('subscriptionId1')),
320 unsubscribeAction(getDataValue("speurl"), device.getDataValue('subscriptionId1')),
321 unsubscribeAction(getDataValue("xcleurl"), device.getDataValue('subscriptionId1')),
322 unsubscribeAction(getDataValue("xwleurl"), device.getDataValue('subscriptionId1')),
325 updateDataValue("subscriptionId", "")
326 updateDataValue("subscriptionId1", "")
330 def getCurrentLevel()
332 dimmableLightAction("GetLoadLevelTarget", "Dimming", getDataValue("dcurl"))
335 def getSystemString()
337 mediaRendererAction("GetString", "SystemProperties", "/SystemProperties/Control", [VariableName: "UMTracking"])
340 private getCallBackAddress()
342 device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP")
345 private dimmableLightAction(String action, String service, String path, Map body = [:]) {
346 // log.debug "path is $path, service is $service, action is $action, body is $body"
347 def result = new physicalgraph.device.HubSoapAction(
348 path: path ?: "/DimmableLight/$service/Control",
349 urn: "urn:schemas-upnp-org:service:$service:1",
352 headers: [Host:getHostAddress(), CONNECTION: "close"])
356 private awoxAction(String action, String service, String path, Map body = [:]) {
357 // log.debug "path is $path, service is $service, action is $action, body is $body"
358 def result = new physicalgraph.device.HubSoapAction(
359 path: path ?: "/DimmableLight/$service/Control",
360 urn: "urn:schemas-awox-com:service:$service:1",
363 headers: [Host:getHostAddress(), CONNECTION: "close"])
367 private subscribeAction(path, callbackPath="") {
368 def address = getCallBackAddress()
369 def ip = getHostAddress()
370 def result = new physicalgraph.device.HubAction(
375 CALLBACK: "<http://${address}/notify$callbackPath>",
377 TIMEOUT: "Second-600"])
381 private unsubscribeAction(path, sid) {
382 def ip = getHostAddress()
383 def result = new physicalgraph.device.HubAction(
384 method: "UNSUBSCRIBE",
392 private delayAction(long time) {
393 new physicalgraph.device.HubAction("delay $time")
396 private Integer convertHexToInt(hex) {
397 Integer.parseInt(hex,16)
400 private String convertHexToIP(hex) {
401 [convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
404 private getHostAddress() {
405 def parts = getDataValue("dni")?.split(":")
406 def ip = convertHexToIP(parts[0])
407 def port = convertHexToInt(parts[1])
408 return ip + ":" + port
411 private updateSid(sid) {
413 def sid0 = device.getDataValue('subscriptionId')
414 def sid1 = device.getDataValue('subscriptionId1')
415 def sidNumber = device.getDataValue('sidNumber') ?: "0"
416 if (sidNumber == "0") {
418 updateDataValue("subscriptionId", sid)
419 updateDataValue("sidNumber", "1")
424 updateDataValue("subscriptionId1", sid)
425 updateDataValue("sidNumber", "0")
431 private dniFromUri(uri) {
432 def segs = uri.replaceAll(/http:\/\/([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)\/.+/,'$1').split(":")
433 def nums = segs[0].split("\\.")
434 (nums.collect{hex(it.toInteger())}.join('') + ':' + hex(segs[-1].toInteger(),4)).toUpperCase()
437 private hex(value, width=2) {
438 def s = new BigInteger(Math.round(value).toString()).toString(16)
439 while (s.size() < width) {