Advertisement
erocm123

mydlink™ Home Door/Window Sensor

Dec 26th, 2018
126
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 14.97 KB | None | 0 0
  1. /**
  2. * Copyright (c) 2016 Tibor Jakab-Barthi
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
  5. * in compliance with the License. You may obtain a copy of the License at:
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
  10. * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
  11. * for the specific language governing permissions and limitations under the License.
  12. *
  13. * mydlink™ Home Door/Window Sensor DCH-Z110
  14. *
  15. * Author: Tibor Jakab-Barthi
  16. */
  17.  
  18. metadata {
  19. definition (name: "mydlink™ Home Door/Window Sensor", namespace: "jbt", author: "Tibor Jakab-Barthi") {
  20. capability "Battery"
  21. capability "Configuration"
  22. capability "Contact Sensor"
  23. capability "Illuminance Measurement"
  24. capability "Sensor"
  25. capability "Tamper Alert"
  26. capability "Temperature Measurement"
  27.  
  28. fingerprint deviceId: "0x0701", inClusters: "0x59,0x5A,0x5E,0x72,0x73,0x7A,0x86,0x8F,0x98", outClusters:"0x20", manufacturer: "0108", model: "000E"
  29.  
  30. attribute "sensorStateChangedDate", "string"
  31. }
  32.  
  33. preferences {
  34. input "configParam22", "number", title: "Illumination Differential Report\nThe illumination differential to report. 0 means turn off this function. The unit is percentage. Enable this function the device will detect every minutes.\nRange: 0 to 99", description: "Tap to set", required: false, range: "0..99", defaultValue: "0"
  35. }
  36.  
  37. tiles(scale: 2) {
  38. multiAttributeTile(name: "contact", type: "generic", width: 6, height: 4) {
  39. tileAttribute("device.contact", key: "PRIMARY_CONTROL") {
  40. attributeState("open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#ffa81e")
  41. attributeState("closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#79b821")
  42. }
  43. tileAttribute("device.sensorStateChangedDate", key: "SECONDARY_CONTROL") {
  44. attributeState("default", label:'At ${currentValue}')
  45. }
  46. }
  47. valueTile("temperature", "device.temperature", width: 3, height: 2) {
  48. state("temperature", label: '${currentValue}°',
  49. backgroundColors:[
  50. // Celsius
  51. [value: 0, color: "#153591"],
  52. [value: 7, color: "#1e9cbb"],
  53. [value: 15, color: "#90d2a7"],
  54. [value: 23, color: "#44b621"],
  55. [value: 28, color: "#f1d801"],
  56. [value: 35, color: "#d04e00"],
  57. [value: 37, color: "#bc2323"],
  58. // Fahrenheit
  59. [value: 40, color: "#153591"],
  60. [value: 44, color: "#1e9cbb"],
  61. [value: 59, color: "#90d2a7"],
  62. [value: 74, color: "#44b621"],
  63. [value: 84, color: "#f1d801"],
  64. [value: 95, color: "#d04e00"],
  65. [value: 96, color: "#bc2323"]
  66. ]
  67. )
  68. }
  69. valueTile("illuminance", "device.illuminance", width: 3, height: 2) {
  70. state("illuminance", label:'${currentValue}%',
  71. backgroundColors:[
  72. [value: 0, color: "#000000"],
  73. [value: 10, color: "#444400"],
  74. [value: 20, color: "#777700"],
  75. [value: 30, color: "#999900"],
  76. [value: 40, color: "#AAAA00"],
  77. [value: 50, color: "#BBBB00"],
  78. [value: 60, color: "#CCCC00"],
  79. [value: 70, color: "#DDDD00"],
  80. [value: 80, color: "#EEEE00"],
  81. [value: 90, color: "#FFFF00"]
  82. ]
  83. )
  84. }
  85. valueTile("temperatureMain", "device.temperature", width: 1, height: 1) {
  86. state("temperature", label: '${currentValue}°', unit: "C", icon: "st.Weather.weather2", backgroundColor:"#EC6E05")
  87. }
  88. valueTile("battery", "device.battery", width: 6, height: 1) {
  89. state("battery", label:'${currentValue}% battery', icon: "st.Transportation.transportation6",
  90. backgroundColors:[
  91. [value: 10, color: "#FF0000"],
  92. [value: 15, color: "#FF7F00"],
  93. [value: 50, color: "#FF7F00"],
  94. [value: 55, color: "#79B821"],
  95. [value: 90, color: "#79B821"]
  96. ]
  97. )
  98. }
  99.  
  100. main("contact")
  101. details(["contact", "temperature", "illuminance", "battery"])
  102. }
  103. }
  104.  
  105. def parse(String description) {
  106. log.debug "parse raw '$description'"
  107.  
  108. def result = []
  109. if (description.startsWith("Err 106")) {
  110. if (state.sec) {
  111. log.debug description
  112. } else {
  113. result << createEvent(
  114. descriptionText: "This sensor failed to complete the network security key exchange. If you are unable to control it via SmartThings, you must remove it from your network and add it again.",
  115. eventType: "ALERT",
  116. name: "secureInclusion",
  117. value: "failed",
  118. isStateChange: true,
  119. )
  120. }
  121. } else if (description != "updated") {
  122. def cmd = zwave.parse(description)
  123.  
  124. log.debug "zwave.parse returned '$cmd'"
  125.  
  126. if (cmd instanceof physicalgraph.zwave.commands.multicmdv1.MultiCmdEncap) {
  127. // HACK: Until SmartThings implements MultiCmdEncap.
  128. log.debug "parse found MultiCmdEncap"
  129. result += parseMultiCmdEncapString(description)
  130. } else if (cmd) {
  131. result += zwaveEvent(cmd)
  132. }
  133. }
  134.  
  135. log.debug "parsed '$description' to ${result.inspect()}"
  136.  
  137. return result
  138. }
  139.  
  140. def parseMultiCmdEncapString(description) {
  141. log.debug "parseMultiCmdEncapString($description)"
  142.  
  143. def payload = []
  144.  
  145. def payloadStart = description.indexOf("payload: ") + 9
  146.  
  147. description.substring(payloadStart).tokenize(" ").eachWithIndex { it, index ->
  148. if (index > 2) {
  149. payload << Integer.parseInt("$it", 16)
  150. }
  151. }
  152.  
  153. def events = parseMultiCmdEncapBytes(payload)
  154.  
  155. return events
  156. }
  157.  
  158. def parseMultiCmdEncapBytes(bytes) {
  159. log.debug "parseMultiCmdEncapBytes($bytes)"
  160.  
  161. def results = []
  162.  
  163. def zwDeviceId = "MultiCmdEncap" // Irrelevant for parse so can be anything.
  164. def bytesLastIndex = bytes.size() - 1
  165. def offset = 0
  166.  
  167. def commandCount = getMessagePayloadByte(bytes, offset)
  168.  
  169. offset++
  170.  
  171. for (int commandIndex = 0; commandIndex < commandCount; commandIndex++) {
  172. def messageLength = getMessagePayloadByte(bytes, offset)
  173.  
  174. def commandClassCode = getMessagePayloadByteAsHexString(bytes, offset + 1)
  175. def commandTypeCode = getMessagePayloadByteAsHexString(bytes, offset + 2)
  176.  
  177. def payloadStartIndex = offset + 3
  178. def payloadEndIndex = offset + messageLength < bytes.size() - 1 ? offset + messageLength : bytes.size() - 1
  179. def payload = ""
  180.  
  181. if (payloadStartIndex <= bytesLastIndex && payloadEndIndex <= bytesLastIndex) {
  182. for (int payloadIndex = payloadStartIndex; payloadIndex <= payloadEndIndex; payloadIndex++) {
  183. payload += getMessagePayloadByteAsHexString(bytes, payloadIndex) + " "
  184. }
  185. }
  186.  
  187. def singleCommandDescription = "zw device: $zwDeviceId, command: $commandClassCode$commandTypeCode, payload: $payload"
  188.  
  189. results += parse(singleCommandDescription)
  190.  
  191. offset += messageLength + 1
  192. }
  193.  
  194. return results
  195. }
  196.  
  197. String getMessagePayloadByteAsHexString(payload, index) {
  198. return getMessagePayloadByte(payload, index).encodeAsHex().toUpperCase()
  199. }
  200.  
  201. int getMessagePayloadByte(payload, index) {
  202. return payload[index] & 0xFF;
  203. }
  204.  
  205. def updated() {
  206. def cmds = []
  207. if (!state.MSR) {
  208. cmds = [
  209. zwave.manufacturerSpecificV2.manufacturerSpecificGet().format(),
  210. "delay 1200",
  211. zwave.wakeUpV1.wakeUpNoMoreInformation().format()
  212. ]
  213. } else if (!state.lastbat) {
  214. cmds = []
  215. } else {
  216. cmds = [zwave.wakeUpV1.wakeUpNoMoreInformation().format()]
  217. }
  218.  
  219. cmds << configure()
  220.  
  221. response(cmds)
  222. }
  223.  
  224. def configure() {
  225. log.debug "configure()"
  226. delayBetween([
  227. zwave.manufacturerSpecificV2.manufacturerSpecificGet().format(),
  228. batteryGetCommand()
  229. ], 6000)
  230. }
  231.  
  232. def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
  233. log.debug "zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport $cmd)"
  234.  
  235. def results = []
  236.  
  237. def map = [ name: "battery", unit: "%" ]
  238. if (cmd.batteryLevel == 0xFF) {
  239. map.value = 1
  240. map.descriptionText = "${device.displayName} has a low battery"
  241. map.isStateChange = true
  242. } else {
  243. map.value = cmd.batteryLevel
  244. }
  245.  
  246. state.lastbat = now()
  247.  
  248. results << createEvent(map)
  249.  
  250. results << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
  251.  
  252. return results
  253. }
  254.  
  255. def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
  256. log.debug "zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport $cmd)"
  257.  
  258. def results = []
  259.  
  260. def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
  261. log.debug "msr: $msr"
  262. updateDataValue("MSR", msr)
  263.  
  264. retypeBasedOnMSR()
  265.  
  266. results << createEvent(descriptionText: "$device.displayName MSR: $msr", isStateChange: false)
  267.  
  268. results << response(batteryGetCommand())
  269.  
  270. return results
  271. }
  272.  
  273. def zwaveEvent(physicalgraph.zwave.commands.multicmdv1.MultiCmdEncap cmd) {
  274. log.debug "zwaveEvent(physicalgraph.zwave.commands.multicmdv1.MultiCmdEncap $cmd)"
  275. }
  276.  
  277. def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
  278. log.debug "zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport $cmd)"
  279.  
  280. def events = []
  281. def openCloseChanged = false
  282.  
  283. if (cmd.notificationType == 0x06) {
  284. def currentValue = device.currentValue("contact")
  285.  
  286. if (cmd.event == 0x16) {
  287. log.debug "open"
  288. events << createEvent(name: "contact", value: "open", descriptionText: "$device.displayName is open.", translatable: true)
  289. openCloseChanged = currentValue != "open"
  290. } else if (cmd.event == 0x17) {
  291. log.debug "closed"
  292. events << createEvent(name: "contact", value: "closed", descriptionText: "$device.displayName is closed.", translatable: true)
  293. openCloseChanged = currentValue != "closed"
  294. } else {
  295. log.debug "Unknown contact event '${cmd.event}'."
  296. }
  297.  
  298. if (openCloseChanged) {
  299. def dateTime = new Date()
  300. def sensorStateChangedDate = dateTime.format("yyyy-MM-dd HH:mm:ss", location.timeZone)
  301. events << createEvent(name: "sensorStateChangedDate", value: sensorStateChangedDate, descriptionText: "$device.displayName open/close state changed at $sensorStateChangedDate.", translatable: true)
  302. }
  303. } else if (cmd.notificationType == 0x07) {
  304. if (cmd.event == 0x03) {
  305. log.debug "tamper"
  306. events << createEvent(name: "tamper", value: "detected", descriptionText: "$device.displayName tampered.", translatable: true)
  307. } else {
  308. log.debug "Unknown tamper event '${cmd.event}'."
  309. }
  310. } else {
  311. log.debug "Unknown notification type '${cmd.notificationType}'."
  312. }
  313.  
  314. return events
  315. }
  316.  
  317. def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
  318. log.debug "zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation $cmd)"
  319.  
  320. def events = []
  321.  
  322. def encapsulatedCommand = cmd.encapsulatedCommand()
  323.  
  324. if (encapsulatedCommand) {
  325. log.debug "encapsulatedCommand $encapsulatedCommand"
  326.  
  327. state.sec = 1
  328.  
  329. if (encapsulatedCommand instanceof physicalgraph.zwave.commands.multicmdv1.MultiCmdEncap) {
  330. events += parseMultiCmdEncapBytes(cmd.commandByte)
  331. } else {
  332. events += zwaveEvent(encapsulatedCommand)
  333. }
  334. }
  335.  
  336. return events
  337. }
  338.  
  339. def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv1.SensorBinaryReport cmd) {
  340. log.debug "zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv1.SensorBinaryReport $cmd)"
  341.  
  342. def events = []
  343.  
  344. if (cmd.sensorType == 0x08) {
  345. if (cmd.sensorValue == 0xFF) {
  346. log.debug "tamper"
  347. events << createEvent(name: "tamper", value: "detected", descriptionText: "$device.displayName tampered.", translatable: true)
  348. } else {
  349. log.debug "Unknown tamper event '${cmd.sensorValue}'."
  350. }
  351. }
  352. else if (cmd.sensorType == 0x0A) {
  353. if (cmd.sensorValue == 0xFF) {
  354. log.debug "open"
  355. events << createEvent(name: "contact", value: "open", descriptionText: "$device.displayName is open.", translatable: true)
  356. } else if (cmd.sensorValue == 0x00) {
  357. log.debug "close"
  358. events << createEvent(name: "contact", value: "closed", descriptionText: "$device.displayName is closed.", translatable: true)
  359. } else {
  360. log.debug "Unknown contact event '${cmd.sensorValue}'."
  361. }
  362. } else {
  363. log.debug "Unknown sensor type '${cmd.sensorType}'."
  364. }
  365.  
  366. return events
  367. }
  368.  
  369. def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
  370. log.debug "zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport $cmd)"
  371.  
  372. def events = []
  373.  
  374. if (cmd.sensorType == 0x01) {
  375. def reportedTemperatureValue = cmd.scaledSensorValue
  376. def reportedTemperatureUnit = cmd.scale == 1 ? "F" : "C"
  377.  
  378. def convertedTemperatureValue = convertTemperatureIfNeeded(reportedTemperatureValue, reportedTemperatureUnit, 2)
  379.  
  380. def descriptionText = "$device.displayName temperature was $convertedTemperatureValue°" + getTemperatureScale() + "."
  381.  
  382. events << createEvent(name: "temperature", value: convertedTemperatureValue, descriptionText: descriptionText, translatable: true)
  383. }
  384. else if (cmd.sensorType == 0x03) {
  385. def illuminanceValue = cmd.scaledSensorValue
  386.  
  387. events << createEvent(name: "illuminance", value: illuminanceValue, descriptionText: "$device.displayName illuminance is $illuminanceValue.", translatable: true)
  388. }
  389.  
  390. return events
  391. }
  392.  
  393. def zwaveEvent(physicalgraph.zwave.commands.configurationv2.ConfigurationReport cmd) {
  394. state."configVal${cmd.parameterNumber}" = cmd2Integer(cmd.configurationValue)
  395. log.debug "${device.displayName} parameter '${cmd.parameterNumber}' with a byte size of '${cmd.size}' is set to '${cmd2Integer(cmd.configurationValue)}'"
  396. }
  397.  
  398. def cmd2Integer(array) {
  399. switch(array.size()) {
  400. case 1:
  401. array[0]
  402. break
  403. case 2:
  404. ((array[0] & 0xFF) << 8) | (array[1] & 0xFF)
  405. break
  406. case 4:
  407. ((array[0] & 0xFF) << 24) | ((array[1] & 0xFF) << 16) | ((array[2] & 0xFF) << 8) | (array[3] & 0xFF)
  408. break
  409. }
  410. }
  411.  
  412. def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) {
  413. log.debug "zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification $cmd)"
  414.  
  415. def results = []
  416.  
  417. if ("$state.configVal22" != "${configParam22!=null? configParam22 : 0}") {
  418. results << zwave.configurationV1.configurationSet(parameterNumber: 22, scaledConfigurationValue: configParam22!=null? configParam22.toInteger() : 0).format()
  419. results << zwave.configurationV1.configurationGet(parameterNumber: 22).format()
  420. }
  421.  
  422. if (!state.MSR) {
  423. results << zwave.wakeUpV2.wakeUpIntervalSet(seconds: 4 * 3600, nodeid: zwaveHubNodeId).format()
  424. results << zwave.manufacturerSpecificV2.manufacturerSpecificGet().format()
  425. results << "delay 1200"
  426. }
  427.  
  428. if (!state.lastbat || now() - state.lastbat > 53 * 60 * 60 * 1000) {
  429. results << batteryGetCommand()
  430. }
  431.  
  432. results << zwave.wakeUpV2.wakeUpNoMoreInformation().format()
  433.  
  434. return response(results)
  435. }
  436.  
  437. def batteryGetCommand() {
  438. def cmd = zwave.batteryV1.batteryGet()
  439. if (state.sec) {
  440. cmd = zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd)
  441. }
  442. cmd.format()
  443. }
  444.  
  445. def retypeBasedOnMSR() {
  446. switch (state.MSR) {
  447. case "0108-0002-000E":
  448. log.debug "Changing device type to mydlink™ Home Door/Window Sensor"
  449. setDeviceType("mydlink™ Home Door/Window Sensor")
  450. break
  451. }
  452. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement