├── AeotecMultiSensor6.groovy
├── README.md
├── docs
├── AeotecMultiSensor6.json
├── version2.json
└── versions.json
└── v1_7
└── AeotecMultiSensor6.groovy
/AeotecMultiSensor6.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * IMPORT URL: https://raw.githubusercontent.com/HubitatCommunity/AeotecMultiSensor6/master/AeotecMultisensor6.groovy
3 | *
4 | * Copyright 2015 SmartThings
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
7 | * in compliance with the License. You may obtain a copy of the License at:
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
12 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
13 | * for the specific language governing permissions and limitations under the License.
14 | *
15 | *
16 | * v2.1.0 Added in optional Auto No Motion timer.
17 | * Remove UpdateCheck, rely on HPM to check for a new version.
18 | * Refactor methods using createEvent to use sendEvent.
19 | *
20 | * v2.0.2 Extended Motion Sensitivity to allow 0 (zero) which might be "off" depending on firmware.
21 | * v2.0.1 Added in optional Param Set / Get commands.
22 | * Added Fingerprint for this device type.
23 | * Added Limit Check on Temp, Illuminance, Humitity and UV values for wildly out of range cases.
24 | * Integrated portions of basicZWaveTool (2020-08-14 maxwell & Copyright 2016 -> 2020 Hubitat Inc. All Rights Reserved)
25 | * v2.0.0 Converted to Hubitat Security method [zwaveSecureEncap()] for S2+S0 This device still only offers unsecure or S0.
26 | *
27 | * v1.7.1 removed Preference hiding (settingEnable)
28 | * LED Options reflect firmware v1.3 specs. (On or Off only)
29 | * Mostion Sensitivity changed to Enum
30 | * v1.7 refactored using inputValidationCheck
31 | * added de-duplication code for Motion Events (motion Events can occur in <1ms)
32 | * | Motion reports (unsolicited) are duplicated: a NotificationReport (7) plus either:
33 | * | (1) for basicSet; (2) for sensorBinary set via Param 5.
34 | * | Motion Detection via NotificationReport can be considered a duplicate.
35 | * | This version removes motion event processing from NotificationReport relying
36 | * | on basicSet or SensorBinary.
37 | * | Added detail to Motion is... to identify the path.
38 | * removed duplicate call to config()
39 | * lots of cosmetic formatting cleanup, cosmetic reordering of modules
40 | *
41 | * v1.6.13 version report NPE (thanks Christi999)
42 | * tempOffset to "as Int"
43 | * v1.6.12 corrected NPE from malformed Packet. (thanks Mike Maxwell)
44 | * v1.6.11 corrected ledOption scaling. (thanks LJP-hubitat)
45 | * BAB v1.6.10a Changed to HALF_ROUND_UP, standardized DescriptionText
46 | * v1.6.10 Swapped to latest updateCheck() code.
47 | * v1.6.9 Added Initialize to preset ledOptions to prevent an NPE when sending the option value to the device.
48 | * revised updateCheck to use version2.json's format.
49 | * v1.6.8 revised Contact Sensor per @Wounded suggestion, restoring tamperAlert
50 | * v1.6.7 replaced updateCheck with asynchttp version -- removed setVersion, etc.
51 | * added descTextEnable as option to reduce log.info
52 | * v1.6.6 corrected limitation on Humidity Offset
53 | * v1.6.5 alternate description for settingEnabled input
54 | * v1.6.4 corrected temp offset -128 to 127 in configure()
55 | * v1.6.3 added Preference hiding (settingEnable)
56 | * v1.6.2 removed mapping to ledOption(s)
57 | * v1.6.1 added degree symbol to temp scale
58 | * v1.6 deleted isStateChange throughout
59 | * v1.5 Added LED Options
60 | * v1.4 Added selectiveReport, enabled humidChangeAmount, luxChangeAmount
61 | * v1.3 Restored if (debugOutput) log.debug as default logging is too much. cSteele
62 | * v1.2 Merged Chuckles updates
63 | *
64 | * Chuckles V1.2 of multisensor 6
65 | * IMPORTANT NOTE: Assumes device has firmware version 1.10 or later. A warning will be logged if this is not the case.
66 | * Changes:
67 | 1. Implement standard "Power Source" capability to track whether USB power ("dc") or battery is in use
68 | - Remove custom "powerSupply" and "batteryStatus" attributes it replaces.
69 | 2. Use hub's timezone rather than setting a timezone for each device individually
70 | 3. Use hub's temperature scale setting rather than setting each device individually
71 | 4. Add event handler for ManufacturerSpecificReport (V1)
72 | 5. Corrected configuration parameter number (4) for PIR sensitivity
73 | 6. Corrected getTimeOptionValueMap
74 | 7. Disabled selective reporting on threshold
75 | 8. Device only supports V1 of COMMAND_CLASS_SENSOR_BINARY (0x30) - not V2
76 | 9. Device only supports V1 of COMMAND_CLASS_CONFIGURATION (0x70) - not V2
77 | 10. Motion detection in NotificationReportV3 is event 8, not event 7
78 | 11. Numerous minor bug fixes (e.g. motionDelayTime and reportInterval should always hold enumerated values, not number of seconds)
79 | 12. Numerous small changes for internationalisation (e.g. region agnostic date formats)
80 |
81 | * v1.1d Added remote version checking ** Cobra (CobraVmax) for his original version check code
82 | * csteele v1.1c converted to Hubitat.
83 | *
84 | * LGK V1 of multisensor 6 with customizeable settings , changed some timeouts, also changed tamper to vibration so we can
85 | * use that as well (based on stock device type and also some changes copied from Robert Vandervoort device type.
86 | * Changes
87 | 1. changes colors of temp slightly, add colors to humidity, add color to battery level
88 | 2. remove tamper and instead use feature as contact and acceleration ie vibration sensor
89 | 3. slightly change reporting interval times. (also fix issue where 18 hours was wrong time)
90 | 4. add last update time.. sets when configured and whenever temp is reported.
91 | This is used so at a glance you see the last update time tile to know the device is still reporting easily without looking
92 | at the logs.
93 | 5. add a temp and humidity offset -12 to 12 to enable tweaking the settings if they appear off.
94 | 6. added power status tile to display, currently was here but not displayed.
95 | 7. added configure and refresh tiles.
96 | 8. also added refresh capability so refresh can be forced from other smartapps like pollster. (refresh not currently working all the time for some reason)
97 | 9. changed the sensitivity to have more values than min max etc, now is integer 0 - 127.
98 | 10. fix uv setting which in one release was value of 2 now it is 16.
99 | 11. added icons for temp and humidity
100 | 12. also change the default wakeup time to be the same as the report interval,
101 | otherwise when on battery it disregarded the report interval. (only if less than the default 5 minutes).
102 | 13. added a config option for the min change needed to report temp changes and set it in parameter 41.
103 | 14. incresed range and colors for lux values, as when mine is in direct sun outside it goes as high as 1900
104 | 15. support for celsius added. set in input options.
105 | */
106 |
107 | public static String version() { return "v2.1.0" }
108 | import groovy.transform.Field
109 |
110 | metadata {
111 | definition (name: "AeotecMultiSensor6", namespace: "cSteele", author: "cSteele", importUrl: "https://raw.githubusercontent.com/HubitatCommunity/AeotecMultiSensor6/master/AeotecMultisensor6.groovy") {
112 | capability "Motion Sensor"
113 | capability "Temperature Measurement"
114 | capability "Relative Humidity Measurement"
115 | capability "Illuminance Measurement"
116 | capability "Ultraviolet Index"
117 | capability "Configuration"
118 | capability "Sensor"
119 | capability "Battery"
120 | capability "Power Source"
121 | capability "Acceleration Sensor"
122 | capability "TamperAlert"
123 |
124 | command "refresh"
125 | command "getParameterReport", [[name:"parameterNumber",type:"NUMBER", description:"Parameter Number (omit for a complete listing of parameters that have been set)", constraints:["NUMBER"]]]
126 | command "setParameter",[[name:"parameterNumber",type:"NUMBER", description:"Parameter Number", constraints:["NUMBER"]],[name:"size",type:"NUMBER", description:"Parameter Size", constraints:["NUMBER"]],[name:"value",type:"NUMBER", description:"Parameter Value", constraints:["NUMBER"]]]
127 |
128 | attribute "firmware", "decimal"
129 |
130 | fingerprint deviceId: "0x2101", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x7A", outClusters: "0x5A"
131 | fingerprint mfr:"0086", prod:"0102", deviceId:"0064", inClusters:"0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x7A,0x5A"
132 | }
133 |
134 |
135 | preferences {
136 | // Note: Hubitat doesn't appear to honour 'sections' in device handler preferences just now, but hopefully one day...
137 | section("Motion sensor settings") {
138 | input "motionDelayTime", "enum", title: "Motion Sensor Delay Time?",
139 | options: ["20 seconds", "30 seconds", "1 minute", "2 minutes", "3 minutes", "4 minutes"], defaultValue: "1 minute", displayDuringSetup: true
140 | input "motionSensitivity", "enum", title: "Motion Sensor Sensitivity?", options: [5:"Very High", 4:"High", 3:"Medium High", 2:"Medium", 1:"Low", 0:"Off"], defaultValue: 5, displayDuringSetup: true
141 | }
142 |
143 | section("Automatic report settings") {
144 | input "reportInterval", "enum", title: "Sensors Report Interval?",
145 | options: ["20 seconds", "30 seconds", "1 minute", "2 minutes", "3 minutes", "4 minutes", "5 minutes", "10 minutes", "15 minutes", "30 minutes", "1 hour", "6 hours", "12 hours", "18 hours", "24 hours"], defaultValue: "5 minutes", displayDuringSetup: true
146 | input "tempChangeAmount", "number", title: "Temperature Change Amount (Tenths of a degree)?", range: "1..70", description: "
The tenths of degrees the temperature must change to induce an automatic report.
", defaultValue: 2, required: false
147 | input "humidChangeAmount", "number", title: "Humidity Change Amount (%)?", range: "1..100", description: "
The percentage the humidity must change to induce an automatic report.
", defaultValue: 10, required: false
148 | input "luxChangeAmount", "number", title: "Luminance Change Amount (LUX)?", range: "-1000..1000", description: "
The amount of LUX the luminance must change to induce an automatic report.
", defaultValue: 100, required: false
149 | }
150 |
151 | section("Calibration settings") {
152 | input "tempOffset", "number", title: "Temperature Offset?", range: "-127..128", description: "
-128 to +127 (Tenths of a degree)
If your temperature is inaccurate this will offset/adjust it by this many tenths of a degree.
", defaultValue: 0, required: false, displayDuringSetup: true
153 | input "humidOffset", "number", title: "Humidity Offset/Adjustment -50 to +50 in percent?", range: "-50..50", description: "
If your humidity is inaccurate this will offset/adjust it by this percent.
", defaultValue: 0, required: false, displayDuringSetup: true
154 | input "luxOffset", "number", title: "Luminance Offset/Adjustment -10 to +10 in LUX?", range: "-10..10", description: "
If your luminance is inaccurate this will offset/adjust it by this percent.
", defaultValue: 0, required: false, displayDuringSetup: true
155 | }
156 |
157 | input "ledOptions", "enum", title: "LED Options",
158 | options: [0:"Fully Enabled", 1:"Fully Disabled", 2:"Disable When Motion (Aeon v1.10 only)"], defaultValue: "0", displayDuringSetup: true
159 | input name: "selectiveReporting", type: "bool", title: "Enable Selective Reporting?", defaultValue: false
160 | input "autoInactive", "enum", title: "Auto Inactive", description: "
Choose to automatically turn off Motion (inactive) after a period of time.
",
161 | options: [0:"Disable", 2:"2 minutes", 5:"5 minutes", 10:"10 minutes", 20:"20 minutes", 40:"40 minutes", 60: "1 hour"], defaultValue: "0", displayDuringSetup: true
162 |
163 | input name: "debugOutput", type: "bool", title: "Enable debug logging?", description: "
", defaultValue: true
164 | input name: "descTextEnable", type: "bool", title: "Enable descriptionText logging?", defaultValue: true
165 | }
166 | }
167 |
168 |
169 | /*
170 | updated
171 |
172 | When "Save Preferences" gets clicked...
173 | */
174 | def updated() {
175 | if (debugOutput) log.debug "In Updated with settings: ${settings}"
176 | if (debugOutput) log.debug "${device.displayName} is now on ${device.latestValue("powerSource")} power"
177 | unschedule()
178 | initialize()
179 | dbCleanUp() // remove antique db entries created in older versions and no longer used.
180 | if (debugOutput) runIn(1800,logsOff)
181 |
182 | // Check for any null settings and change them to default values
183 | inputValidationCheck()
184 |
185 | if (device.latestValue("powerSource") == "dc") { //case1: USB powered
186 | // response(configure(2))
187 | } else if (device.latestValue("powerSource") == "battery") { //case2: battery powered
188 | // setConfigured("false") is used by WakeUpNotification
189 | setConfigured("false") //wait until the next time device wakeup to send configure command after user change preference
190 | selectiveReport = 0 // battery, selective reporting is not supported
191 | } else { //case3: power source is not identified, ask user to properly pair the sensor again
192 | log.warn "power source is not identified, check that sensor is powered by USB, if so > configure()"
193 | def request = []
194 | request << zwave.configurationV1.configurationGet(parameterNumber: 9) // Retrieve current power mode
195 | response(commands(request))
196 | }
197 | return(configure(1))
198 | }
199 |
200 |
201 | /*
202 | parse
203 |
204 | Respond to received zwave commands.
205 | */
206 | def parse(String description) {
207 | // log.debug "In parse() for description: $description"
208 | def result = null
209 | if (description.startsWith("Err 106")) {
210 | log.warn "parse() >> Err 106"
211 | result = sendEvent( name: "secureInclusion", value: "failed",
212 | descriptionText: "This sensor (${device.displayName}) failed to complete the network security key exchange. If you are unable to control it via Hubitat, you must remove it from your network and add it again.")
213 | } else if (description != "updated") {
214 | // log.debug "About to zwave.parse($description)"
215 | def cmd = zwave.parse(description, [0x31: 5, 0x30: 1, 0x70: 1, 0x72: 1, 0x84: 1])
216 | if (cmd) {
217 | // log.debug "About to call handler for ${cmd.toString()}"
218 | result = zwaveEvent(cmd)
219 | }
220 | }
221 | //log.debug "After zwaveEvent(cmd) >> Parsed '${description}' to ${result.inspect()}"
222 | return result
223 | }
224 |
225 |
226 | /*
227 | installed
228 |
229 | Doesn't do much other than call initialize().
230 | */
231 | void installed()
232 | {
233 | initialize()
234 | }
235 |
236 |
237 | /*
238 | initialize
239 |
240 | Doesn't do much.
241 | */
242 | def initialize() {
243 | if (settings.ledOptions == null) settings.ledOptions = 0 // default to Full
244 | state.firmware = state.firmware ?: 0.0d
245 | state.Copyright = "${thisCopyright} -- ${version()}"
246 | }
247 |
248 |
249 | /*
250 | Beginning of Z-Wave Commands
251 | */
252 |
253 | //this notification will be sent only when device is battery powered
254 | def zwaveEvent(hubitat.zwave.commands.wakeupv1.WakeUpNotification cmd) {
255 | def result = [sendEvent(descriptionText: "${device.displayName} woke up")]
256 | def cmds = []
257 | if (!isConfigured()) {
258 | if (debugOutput) log.debug("late configure")
259 | result << response(configure(3))
260 | } else {
261 | //if (debugOutput) log.debug("Device has been configured sending >> wakeUpNoMoreInformation()")
262 | cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format()
263 | result << response(cmds)
264 | }
265 | result
266 | }
267 |
268 |
269 | def zwaveEvent(hubitat.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
270 | def encapsulatedCommand = cmd.encapsulatedCommand([0x31: 5, 0x30: 1, 0x70: 1, 0x72: 1, 0x84: 1])
271 | state.sec = 1
272 | if (debugOutput) log.debug "encapsulated: ${encapsulatedCommand}"
273 | if (encapsulatedCommand) {
274 | zwaveEvent(encapsulatedCommand)
275 | } else {
276 | log.warn "Unable to extract encapsulated cmd from $cmd"
277 | sendEvent(descriptionText: cmd.toString())
278 | }
279 | }
280 |
281 |
282 | def zwaveEvent(hubitat.zwave.commands.manufacturerspecificv1.ManufacturerSpecificReport cmd) {
283 | if (debugOutput) log.debug "ManufacturerSpecificReport cmd = $cmd"
284 | if (debugOutput) log.debug "manufacturerId: ${cmd.manufacturerId}"
285 | if (debugOutput) log.debug "manufacturerName: ${cmd.manufacturerName}"
286 | if (debugOutput) log.debug "productId: ${cmd.productId}"
287 | def model = "" // We'll decode the specific model for the log, but we don't currently use this info
288 | switch(cmd.productTypeId >> 8) {
289 | case 0: model = "EU"
290 | break
291 | case 1: model = "US"
292 | break
293 | case 2: model = "AU"
294 | break
295 | case 10: model = "JP"
296 | break
297 | case 29: model = "CN"
298 | break
299 | default: model = "unknown"
300 | }
301 | if (debugOutput) log.debug "model: ${model}"
302 | if (debugOutput) log.debug "productTypeId: ${cmd.productTypeId}"
303 | def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
304 | updateDataValue("MSR", msr)
305 | }
306 |
307 |
308 | def zwaveEvent(hubitat.zwave.commands.batteryv1.BatteryReport cmd) {
309 | if (debugOutput) log.debug "In BatteryReport $cmd"
310 | if (cmd.batteryLevel == 0xFF) {
311 | sendEvent(name: "battery", unit: "%", value: 1, descriptionText: "${device.displayName} battery is low")
312 | } else {
313 | sendEvent(name: "battery", unit: "%", value: cmd.batteryLevel.toInteger(), descriptionText: "${device.displayName} battery is ${cmd.batteryLevel}%")
314 | }
315 | }
316 |
317 |
318 | @Field static Map LIMIT_VALUES = [ tempC: [upper: 100, lower: -40], tempF: [upper: 212, lower: -40], lux: [upper: 30000, lower: 0], rh: [upper: 100, lower: 0], uv: [upper: 11, lower: 1]]
319 |
320 | def zwaveEvent(hubitat.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd){
321 | if (debugOutput) log.debug "In multi level report cmd = $cmd"
322 |
323 | if (cmd.scaledSensorValue == null) return
324 | def map = [:]
325 | switch (cmd.sensorType) {
326 | case 1:
327 | if (debugOutput) log.debug "raw temp = $cmd.scaledSensorValue"
328 | if (cmd.scale == 1) { if ((cmd.scaledSensorValue > LIMIT_VALUES.tempF.upper) || (cmd.scaledSensorValue < LIMIT_VALUES.tempF.lower)) { return } }
329 | if (cmd.scale == 0) { if ((cmd.scaledSensorValue > LIMIT_VALUES.tempC.upper) || (cmd.scaledSensorValue < LIMIT_VALUES.tempC.lower)) { return } }
330 | // Convert temperature (if needed) to the system's configured temperature scale
331 | map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmd.scale == 1 ? "F" : "C", cmd.precision)
332 | if (debugOutput) log.debug "finalval = $map.value"
333 |
334 | map.unit = "\u00b0" + getTemperatureScale()
335 | map.name = "temperature"
336 | map.descriptionText = "${device.displayName} temperature is ${map.value}${map.unit}"
337 | if (descTextEnable) log.info "Temperature is ${map.value}${map.unit}"
338 | break
339 | case 3:
340 | if (debugOutput) log.debug "raw illuminance = $cmd.scaledSensorValue"
341 | if (cmd.scaledSensorValue >= LIMIT_VALUES.lux.upper || cmd.scaledSensorValue < LIMIT_VALUES.lux.lower) return
342 | map.name = "illuminance"
343 | map.value = cmd.scaledSensorValue.toInteger() // roundIt((cmd.scaledSensorValue / 2.0),0) as Integer // .toInteger()
344 | map.unit = "lux"
345 | map.descriptionText = "${device.displayName} illuminance is ${map.value} Lux"
346 | if (descTextEnable) log.info "Illuminance is ${map.value} Lux"
347 | break
348 | case 5:
349 | if (debugOutput) log.debug "raw humidity = $cmd.scaledSensorValue"
350 | if (cmd.scaledSensorValue >= LIMIT_VALUES.rh.upper || cmd.scaledSensorValue < LIMIT_VALUES.rh.lower) return
351 | map.value = roundIt(cmd.scaledSensorValue, 0) as Integer // .toInteger()
352 | map.unit = "%"
353 | map.name = "humidity"
354 | map.descriptionText = "${device.displayName} humidity is ${map.value}%"
355 | if (descTextEnable) log.info "Humidity is ${map.value}%"
356 | break
357 | case 27:
358 | if (debugOutput) log.debug "raw uv index = $cmd.scaledSensorValue"
359 | if (cmd.scaledSensorValue >= LIMIT_VALUES.uv.upper || cmd.scaledSensorValue < LIMIT_VALUES.uv.lower) return
360 | map.name = "ultravioletIndex"
361 | map.value = roundIt(cmd.scaledSensorValue, 0) as Integer // .toInteger()
362 | map.descriptionText = "${device.displayName} ultraviolet index is ${map.value}"
363 | if (descTextEnable) log.info "Ultraviolet index is ${map.value}"
364 | break
365 | default:
366 | map.descriptionText = cmd.toString()
367 | }
368 | sendEvent(map)
369 | }
370 |
371 |
372 | def zwaveEvent(hubitat.zwave.commands.sensorbinaryv1.SensorBinaryReport cmd) {
373 | motionEvent(cmd.sensorValue, "SensorBinaryReport")
374 | }
375 |
376 |
377 | def zwaveEvent(hubitat.zwave.commands.basicv1.BasicSet cmd) {
378 | // Sensor sends value 0xFF on motion, 0x00 on no motion (after expiry interval)
379 | motionEvent(cmd.value, "BasicSet")
380 | }
381 |
382 |
383 | def zwaveEvent(hubitat.zwave.commands.notificationv3.NotificationReport cmd) {
384 | if (debugOutput) log.debug "NotificationReport: ${cmd}"
385 | def result = []
386 | if (cmd.notificationType == 7) {
387 | // spec says type 7 is 'Home Security'
388 | switch (cmd.event) {
389 | case 0:
390 | // spec says this is 'clear previous alert'
391 | // sendEvent(name: "contact", value: "closed", descriptionText: "$device.displayName is closed", displayed: true)
392 | // result << motionEvent(0, "NotificationReport") // see Change Note for v1.7
393 | result << sendEvent(name: "tamper", value: "clear", descriptionText: "${device.displayName} tamper cleared", displayed: true)
394 | if (descTextEnable) log.info "Tamper cleared by NotificationReport"
395 | result << sendEvent(name: "acceleration", value: "inactive", descriptionText: "${device.displayName} acceleration is inactive", displayed: true)
396 | if (descTextEnable) log.info "Acceleration is inactive by NotificationReport"
397 | break
398 | case 3:
399 | // spec says this is 'tamper'
400 | //sendEvent(name: "contact", value: "open", descriptionText: "$device.displayName is open", displayed: true)
401 | result << sendEvent(name: "tamper", value: "detected", descriptionText: "${device.displayName} tamper detected", displayed: true)
402 | if (descTextEnable) log.info "Tamper detected"
403 | result << sendEvent(name: "acceleration", value: "active", descriptionText: "${device.displayName} acceleration is active", displayed: true)
404 | if (descTextEnable) log.info "Acceleration is active"
405 | break
406 | case 8:
407 | // spec says this is 'unknown motion detection'
408 | // result << motionEvent(1, "NotificationReport") // see Change Note for v1.7
409 | break
410 | }
411 | } else {
412 | log.warn "Need to handle this cmd.notificationType: ${cmd.notificationType}"
413 | result << sendEvent(descriptionText: cmd.toString())
414 | }
415 | result
416 | }
417 |
418 |
419 | def zwaveEvent(hubitat.zwave.commands.configurationv1.ConfigurationReport cmd) {
420 | if (debugOutput) log.debug "---CONFIGURATION REPORT V1--- ${device.displayName} parameter ${cmd.parameterNumber} with a byte size of ${cmd.size} is set to ${cmd.configurationValue}"
421 | def result = []
422 | def value
423 | if (cmd.parameterNumber == 9 && cmd.configurationValue[0] == 0) {
424 | value = "dc"
425 | if (!isConfigured()) {
426 | if (debugOutput) log.debug("Configuration Report: configuring device")
427 | result << response(configure(4))
428 | }
429 | result << sendEvent(name: "powerSource", value: value, descriptionText: "${device.displayName} power source is dc (mains)", displayed: false)
430 | if (descTextEnable) log.info "Power source is DC (mains)"
431 | }
432 | else if (cmd.parameterNumber == 9 && cmd.configurationValue[0] == 1) {
433 | value = "battery"
434 | result << sendEvent(name: "powerSource", value: value, descriptionText: "${device.displayName} power source is battery", displayed: false)
435 | if (descTextEnable) log.info "Power source is battery"
436 | }
437 | else if (cmd.parameterNumber == 101){
438 | result << response(configure(5))
439 | }
440 | result
441 | }
442 |
443 |
444 | def zwaveEvent(hubitat.zwave.Command cmd) {
445 | if (debugOutput) log.debug "General zwaveEvent cmd: ${cmd}"
446 | sendEvent(descriptionText: cmd.toString(), isStateChange: false)
447 | }
448 |
449 |
450 | def zwaveEvent(hubitat.zwave.commands.versionv1.VersionCommandClassReport cmd) {
451 | if (debugOutput) log.debug "in version command class report"
452 | if (debugOutput) log.debug "---VERSION COMMAND CLASS REPORT V1--- ${device.displayName} has version: ${cmd.commandClassVersion} for command class ${cmd.requestedCommandClass} - payload: ${cmd.payload}"
453 | }
454 |
455 |
456 | def zwaveEvent(hubitat.zwave.commands.versionv1.VersionReport cmd) {
457 | if (debugOutput) log.debug "in version report"
458 | // SubVersion is in 1/100ths so that 1.01 < 1.08 < 1.10, etc.// state.firmware = 0.0d
459 | if (cmd.firmware0Version) {
460 | BigDecimal fw = cmd.firmware0Version + (cmd.firmware0SubVersion/100)
461 | state.firmware = fw
462 | }
463 | if (debugOutput) log.debug "---VERSION REPORT V1--- ${device.displayName} is running firmware version: ${String.format("%1.2f",state.firmware)}, Z-Wave version: ${cmd.zWaveProtocolVersion}.${cmd.zWaveProtocolSubVersion}"
464 | if(state.firmware < 1.10)
465 | log.warn "--- WARNING: Device handler expects devices to have firmware 1.10 or later"
466 | }
467 |
468 |
469 | def zwaveEvent(hubitat.zwave.commands.firmwareupdatemdv2.FirmwareMdReport cmd) {
470 | // NOTE: This command class is not yet implemented by Hubitat...
471 | if (debugOutput) log.debug "---FIRMWARE METADATA REPORT V2 ${device.displayName} manufacturerId: ${cmd.manufacturerId} firmwareId: ${cmd.firmwareId}"
472 | }
473 |
474 |
475 | private command(hubitat.zwave.Command cmd) {
476 | if (debugOutput) log.debug "Sending Z-wave command: ${cmd.toString()}"
477 | return zwaveSecureEncap(cmd.format())
478 | }
479 |
480 |
481 | private commands(commands, delay=1000) {
482 | //if (descTextEnable) log.info "sending commands: ${commands}"
483 | return delayBetween(commands.collect{ command(it) }, delay)
484 | }
485 |
486 |
487 | String secureCmd(cmd) {
488 | if (getDataValue("zwaveSecurePairingComplete") == "true" && getDataValue("S2") == null) {
489 | return zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
490 | } else {
491 | return secure(cmd)
492 | }
493 | }
494 |
495 |
496 | String secure(String cmd) { return zwaveSecureEncap(cmd) }
497 | String secure(hubitat.zwave.Command cmd) { return zwaveSecureEncap(cmd) }
498 |
499 |
500 | /*
501 | End of Z-Wave Commands
502 |
503 | Beginning of Driver Commands
504 |
505 | */
506 | def configure(ccc) {
507 | if (debugOutput) log.debug "ccc: $ccc"
508 | // This sensor joins as a secure device if you double-click the button to include it
509 | if (descTextEnable) log.info "${device.displayName} is configuring its settings"
510 |
511 | if (device.currentValue('tamper') == null) {
512 | sendEvent(name: 'tamper', value: 'clear', descriptionText: '${device.displayName} tamper cleared')
513 | sendEvent(name: 'acceleration', value: 'inactive', descriptionText: "$device.displayName} acceleration is inactive")
514 | }
515 |
516 | // inputValidationCheck()
517 |
518 | if (debugOutput) {
519 | log.debug "Report Interval = $reportInterval"
520 | log.debug "Motion Delay Time = $motionDelayTime"
521 | log.debug "Motion Sensitivity = $motionSensitivity"
522 | log.debug "Temperature adjust = $tempOffset (${tempOffset/10}°)"
523 | log.debug "Humidity adjust = $humidOffset"
524 | log.debug "Min Temp change for reporting = $tempChangeAmount"
525 | log.debug "Min Humidity change for reporting = $humidChangeAmount"
526 | log.debug "Min Lux change for reporting = $luxChangeAmount"
527 | log.debug "LED Option = $ledOptions"
528 | }
529 |
530 | def now = new Date()
531 | def tf = new java.text.SimpleDateFormat("dd-MMM-yyyy h:mm a")
532 | tf.setTimeZone(location.getTimeZone())
533 | def newtime = "${tf.format(now)}" as String
534 | sendEvent(name: "lastUpdate", value: newtime, descriptionText: "${device.displayName} configured at ${newtime}")
535 |
536 | setConfigured("true")
537 | def waketime
538 |
539 | if (timeOptionValueMap[settings.reportInterval] < 300)
540 | waketime = timeOptionValueMap[settings.reportInterval]
541 | else waketime = 300
542 |
543 | if (debugOutput) log.debug "wake time reset to $waketime"
544 | if (debugOutput) log.debug "Current firmware: ${sprintf ("%1.2f", state.firmware)}"
545 |
546 | // Retrieve local temperature scale: "C" = Celsius, "F" = Fahrenheit
547 | // Convert to a value of 1 or 2 as used by the device to select the scale
548 | if (debugOutput) log.debug "Location temperature scale: ${location.getTemperatureScale()}"
549 | byte tempScaleByte = (location.getTemperatureScale() == "C" ? 1 : 2)
550 | selectiveReport = selectiveReporting ? 1 : 0
551 |
552 | def request = [
553 | // set wakeup interval to report time otherwise it doesnt report in time
554 | zwave.wakeUpV1.wakeUpIntervalSet(seconds:waketime, nodeid:zwaveHubNodeId),
555 |
556 | zwave.versionV1.versionGet(),
557 | zwave.manufacturerSpecificV1.manufacturerSpecificGet(),
558 |
559 | // Hubitat have not yet implemented the firmwareUpdateMdV2 class
560 | //zwave.firmwareUpdateMdV2.firmwareMdGet(),
561 |
562 | //1. set association groups for hub
563 | zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId),
564 | zwave.associationV1.associationSet(groupingIdentifier:2, nodeId:zwaveHubNodeId),
565 |
566 | //2. automatic report flags
567 | // params 101-103 [4 bytes] 128: light sensor, 64 humidity, 32 temperature sensor, 16 ultraviolet sensor, 1 battery sensor -> send command 241 to get all reports
568 | zwave.configurationV1.configurationSet(parameterNumber: 101, size: 4, scaledConfigurationValue: 241), //association group 1 - all reports
569 | zwave.configurationV1.configurationSet(parameterNumber: 102, size: 4, scaledConfigurationValue: 1), //association group 2 - battery report
570 |
571 | //3. no-motion report x seconds after motion stops (default 60 secs)
572 | zwave.configurationV1.configurationSet(parameterNumber: 3, size: 2, scaledConfigurationValue: timeOptionValueMap[motionDelayTime] ?: 60),
573 |
574 | //4. motion sensitivity: 0 (least sensitive) - 5 (most sensitive)
575 | zwave.configurationV1.configurationSet(parameterNumber: 4, size: 1, scaledConfigurationValue: motionSensitivity as int),
576 | //5. report every x minutes (threshold reports don't work on battery power, default 8 mins)
577 | zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: timeOptionValueMap[reportInterval]), //association group 1
578 | // battery report time.. too long at every 6 hours change to 2 hours.
579 | zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: 2*60*60), //association group 2
580 | //6. enable/disable selective reporting only on thresholds
581 | zwave.configurationV1.configurationSet(parameterNumber: 40, size: 1, scaledConfigurationValue: selectiveReport),
582 | // Set the temperature scale for automatic reports
583 | // US units default to reporting in Fahrenheit, whilst all others default to reporting in Celsius, but we can configure the preferred scale with this setting
584 | zwave.configurationV1.configurationSet(parameterNumber: 64, size: 1, configurationValue: [tempScaleByte]),
585 | // Automatically generate a report when temp changes by specified amount
586 | zwave.configurationV1.configurationSet(parameterNumber: 41, size: 4, configurationValue: [0, tempChangeAmount, tempScaleByte, 0]),
587 | // Automatically generate a report when humidity changes by specified amount
588 | zwave.configurationV1.configurationSet(parameterNumber: 42, size: 1, scaledConfigurationValue: humidChangeAmount),
589 | // Automatically generate a report when lux changes by specified amount
590 | zwave.configurationV1.configurationSet(parameterNumber: 43, size: 2, scaledConfigurationValue: luxChangeAmount),
591 | // send (1) BasicSet or (2) SensorBinary report for motion
592 | zwave.configurationV1.configurationSet(parameterNumber: 0x05, size: 1, scaledConfigurationValue: 2),
593 | // Set temperature calibration offset
594 | zwave.configurationV1.configurationSet(parameterNumber: 201, size: 2, configurationValue: [tempOffset as int, tempScaleByte]),
595 | // Set humidity calibration offset
596 | zwave.configurationV1.configurationSet(parameterNumber: 202, size: 1, scaledConfigurationValue: humidOffset),
597 | // Set luminance calibration offset
598 | zwave.configurationV1.configurationSet(parameterNumber: 203, size: 2, scaledConfigurationValue: luxOffset),
599 | // Set LED Option value
600 | zwave.configurationV1.configurationSet(parameterNumber: 81, size: 1, configurationValue: [ledOptions as int]),
601 | //7. query sensor data
602 | // zwave.configurationV1.configurationGet(parameterNumber: 9), // Retrieve current power mode
603 | zwave.batteryV1.batteryGet(),
604 | zwave.sensorBinaryV1.sensorBinaryGet(), //motion
605 | zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1), //temperature
606 | zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 3), //illuminance
607 | zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 5), //humidity
608 | zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 27) //ultravioletIndex
609 | ]
610 | return commands(request) + ["delay 20000", zwave.wakeUpV1.wakeUpNoMoreInformation().format()]
611 | }
612 |
613 |
614 | def refresh() {
615 | if (debugOutput) log.debug "in refresh"
616 |
617 | return commands([
618 | zwave.versionV1.versionGet(), // Retrieve version info (includes firmware version)
619 | // zwave.firmwareUpdateMdV2.firmwareMdGet(), // Command class not implemented by Hubitat yet
620 | zwave.configurationV1.configurationGet(parameterNumber: 9), // Retrieve current power mode
621 | zwave.batteryV1.batteryGet(),
622 | zwave.sensorBinaryV1.sensorBinaryGet(), //motion
623 | zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1), //temperature
624 | zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 3), //illuminance
625 | zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 5), //humidity
626 | zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 27) //ultravioletIndex
627 | ])
628 | }
629 |
630 | /*
631 | Begin support methods
632 | */
633 |
634 | def motionEvent(value, by) {
635 | if (value) {
636 | if (descTextEnable) log.info "Motion is active by $by"
637 | sendEvent(name: "motion", value: "active", descriptionText: "${device.displayName} motion is active by $by", isStateChange:true)
638 | if (autoInactive > "0") {
639 | def delay = autoInactive.toInteger() * 60
640 | runIn(delay, motionInactivate)
641 | }
642 | } else {
643 | if (descTextEnable) log.info "Motion is inactive by $by"
644 | sendEvent(name: "motion", value: "inactive", descriptionText: "${device.displayName} motion is inactive by $by", isStateChange:true)
645 | unschedule(motionInactivate)
646 | }
647 | }
648 |
649 |
650 | /*
651 | Auto reset motion to inactive after timeout period.
652 | */
653 | def motionInactivate() {
654 | if (descTextEnable) log.info "Motion is inactive by Time Out"
655 | sendEvent(name: "motion", value: "inactive", descriptionText: "${device.displayName} motion is inactive by Time Out")
656 | }
657 |
658 |
659 | def getTimeOptionValueMap() { [
660 | "20 seconds" : 20,
661 | "30 seconds" : 30,
662 | "1 minute" : 60,
663 | "2 minutes" : 2*60,
664 | "3 minutes" : 3*60,
665 | "4 minutes" : 4*60,
666 | "5 minutes" : 5*60,
667 | "10 minutes" : 10*60,
668 | "15 minutes" : 15*60,
669 | "30 minutes" : 30*60,
670 | "1 hour" : 1*60*60,
671 | "6 hours" : 6*60*60,
672 | "12 hours" : 12*60*60,
673 | "18 hours" : 18*60*60,
674 | "24 hours" : 24*60*60,
675 | ]}
676 |
677 |
678 | // Check for any null settings and change them to default values
679 | void inputValidationCheck () {
680 |
681 | motionSensitivity = motionSensitivity ?: 0
682 | motionDelayTime = motionDelayTime ?: "1 minute"
683 | reportInterval = reportInterval ?: "5 minute"
684 | tempChangeAmount = tempChangeAmount ?: 2
685 | humidChangeAmount = humidChangeAmount ?: 10
686 | luxChangeAmount = luxChangeAmount ?: 100
687 | tempOffset = tempOffset ?: 0
688 | ledOptions = ledOptions ?: 0
689 |
690 | // Validate Input Ranges
691 | def motionRange = 0..5
692 | def tempRange = 0..128
693 | def humidRange = 0..50
694 | def luxRange = 0..1000
695 |
696 | if ( !motionRange.contains(motionSensitivity as int) ) { motionSensitivity = 3 ; log.warn "Selection out of Range: Motion Sensitivity set to 3"; }
697 | if ( !tempRange.contains( tempOffset.abs() as int ) ) { tempOffset = 0 ; log.warn "Selection out of Range: Temperature Offset set to 0"; }
698 | if ( !humidRange.contains( humidOffset.abs() as int ) ) { humidOffset = 0 ; log.warn "Selection out of Range: Humidity Offset set to 0"; }
699 | if ( !luxRange.contains( luxOffset.abs() as int ) ) { luxOffset = 0 ; log.warn "Selection out of Range: Luminance Offset set to 0"; }
700 | }
701 |
702 |
703 | private setConfigured(configure) {
704 | updateDataValue("configured", configure)
705 | }
706 |
707 |
708 | private isConfigured() {
709 | getDataValue("configured") == "true"
710 | }
711 |
712 |
713 | def roundIt( value, decimals=0 ) {
714 | return (value == null) ? null : value.toBigDecimal().setScale(decimals, BigDecimal.ROUND_HALF_UP)
715 | }
716 |
717 |
718 | def roundIt( BigDecimal value, decimals=0) {
719 | return (value == null) ? null : value.setScale(decimals, BigDecimal.ROUND_HALF_UP)
720 | }
721 |
722 |
723 | def logsOff(){
724 | log.warn "debug logging disabled..."
725 | device.updateSetting("debugOutput",[value:"false",type:"bool"])
726 | }
727 |
728 |
729 | private dbCleanUp() {
730 | // clean up state variables that are obsolete
731 | // state.remove("tempOffset")
732 | // state.remove("version")
733 | // state.remove("Version")
734 | // state.remove("sensorTemp")
735 | // state.remove("author")
736 | // state.remove("Copyright")
737 | state.remove("verUpdate")
738 | state.remove("verStatus")
739 | state.remove("Type")
740 | state.remove("Status")
741 | state.remove("InternalName")
742 | state.remove("UpdateInfo")
743 | }
744 |
745 | List setParameter(parameterNumber = null, size = null, value = null){
746 | if (parameterNumber == null || size == null || value == null) {
747 | log.warn "incomplete parameter list supplied..."
748 | log.info "syntax: setParameter(parameterNumber,size,value)"
749 | } else {
750 | return delayBetween([
751 | secureCmd(zwave.configurationV1.configurationSet(scaledConfigurationValue: value, parameterNumber: parameterNumber, size: size)),
752 | secureCmd(zwave.configurationV1.configurationGet(parameterNumber: parameterNumber))
753 | ],500)
754 | }
755 | }
756 |
757 | List getParameterReport(param = null){
758 | if (!debugOutput) {
759 | log.warn "debug logging auto-enabled..."
760 | device.updateSetting("debugOutput",[value:"true",type:"bool"])
761 | runIn(1800,logsOff)
762 | }
763 |
764 | List cmds = []
765 | if (param != null) {
766 | cmds = [secureCmd(zwave.configurationV1.configurationGet(parameterNumber: param))]
767 | } else {
768 | 0.upto(255, {
769 | cmds.add(secureCmd(zwave.configurationV1.configurationGet(parameterNumber: it)))
770 | })
771 | }
772 | log.trace "configurationGet command(s) ($param) sent..."
773 | return delayBetween(cmds,500)
774 | }
775 |
776 |
777 | String getThisCopyright(){"© 2019 C Steele "}
778 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AeotecMultiSensor6
2 | Aeon Labs or Aeotec's MultiSensor6 Hubitat Driver
3 |
4 | There are far too many "AeonMultiSensor6 V1" drivers out there and no clear way to distinguish.
5 |
6 | This driver has been through many coders with the most recent being Chuckles.
7 |
8 | Thanks to all who have contributed.
9 |
10 | Hubitat introduced a common Security Method in Platform v2.2.3 that encompasses S0 and S2. However, that means using the method requires the platform, first.
11 |
12 | Aeotec MultiSensor 6 v2 therefore needs a minimum of Hubitat Platform v2.2.3.
13 |
14 | Aeotec MultiSensor 6 v1.x will work on Hubitat Platform v2.2.3 and earlier.
15 |
--------------------------------------------------------------------------------
/docs/AeotecMultiSensor6.json:
--------------------------------------------------------------------------------
1 | {
2 | "packageName": "AeotecMultiSensor6",
3 | "releaseNotes": "v2.1.0 Added in optional Auto No Motion timer\n Remove UpdateCheck\nv2.0.2 Extended Motion Sensitivity to allow 0 (zero) which might be 'off' depending on firmware.",
4 | "documentationLink": "https://github.com/HubitatCommunity/AeotecMultiSensor6/blob/master/README.md",
5 | "communityLink": "https://community.hubitat.com/t/aeotec-multisensor-6/1204/20",
6 | "author": "CSteele",
7 | "version": "2.1.0",
8 | "minimumHEVersion": "2.0.1",
9 | "dateReleased": "2023-04-28",
10 | "category": "Control",
11 | "tags": [
12 | "ZWave",
13 | "Lights & Switches",
14 | "Multi Sensors"
15 | ],
16 | "drivers": [
17 | {
18 | "id": "41a8de23-9da5-4204-9048-d8038fe21dc4",
19 | "name": "AeotecMultiSensor6",
20 | "namespace": "cSteele",
21 | "location": "https://raw.githubusercontent.com/HubitatCommunity/AeotecMultiSensor6/master/AeotecMultiSensor6.groovy",
22 | "required": true,
23 | "oauth": false,
24 | "description": "A more preference filled alternative to the Built-In driver."
25 | }
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/docs/version2.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "C Steele",
3 | "comment": "",
4 | "copyright": " Ⓒ 2019 cSteele",
5 | "application": {
6 | },
7 | "driver": {
8 | "AeotecMultiSensor6": {"ver": "1.7.1", "updated": "08/16/2020"},
9 | "AeotecMultiSensor6_2": {"ver": "2.0.2", "updated": "07/21/2021"}
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/docs/versions.json:
--------------------------------------------------------------------------------
1 | {
2 | "Comment": "",
3 | "copyright": " Ⓒ 2018 cSteele",
4 | "author":"cSteele",
5 | "versions": {
6 | "Application": {
7 | },
8 | "Driver": {
9 | "AeotecMultiSensor6": "1.6.13"
10 | },
11 |
12 | "UpdateInfo": {
13 | "Application": {
14 | },
15 | "Driver": {
16 | "AeotecMultiSensor6": "Updated: 05/28/2020"
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/v1_7/AeotecMultiSensor6.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * IMPORT URL: https://raw.githubusercontent.com/HubitatCommunity/AeotecMultiSensor6/master/v1_7/AeotecMultisensor6.groovy
3 | *
4 | * Copyright 2015 SmartThings
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
7 | * in compliance with the License. You may obtain a copy of the License at:
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
12 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
13 | * for the specific language governing permissions and limitations under the License.
14 | *
15 | *
16 | * v1.7.1 removed Preference hiding (settingEnable)
17 | * LED Options reflect firmware v1.3 specs. (On or Off only)
18 | * Mostion Sensitivity changed to Enum
19 | * v1.7 refactored using inputValidationCheck
20 | * added de-duplication code for Motion Events (motion Events can occur in <1ms)
21 | * | Motion reports (unsolicited) are duplicated: a NotificationReport (7) plus either:
22 | * | (1) for basicSet; (2) for sensorBinary set via Param 5.
23 | * | Motion Detection via NotificationReport can be considered a duplicate.
24 | * | This version removes motion event processing from NotificationReport relying
25 | * | on basicSet or SensorBinary.
26 | * | Added detail to Motion is... to identify the path.
27 | * removed duplicate call to config()
28 | * lots of cosmetic formatting cleanup, cosmetic reordering of modules
29 | *
30 | * v1.6.13 version report NPE (thanks Christi999)
31 | * tempOffset to "as Int"
32 | * v1.6.12 corrected NPE from malformed Packet. (thanks Mike Maxwell)
33 | * v1.6.11 corrected ledOption scaling. (thanks LJP-hubitat)
34 | * BAB v1.6.10a Changed to HALF_ROUND_UP, standardized DescriptionText
35 | * v1.6.10 Swapped to latest updateCheck() code.
36 | * v1.6.9 Added Initialize to preset ledOptions to prevent an NPE when sending the option value to the device.
37 | * revised updateCheck to use version2.json's format.
38 | * v1.6.8 revised Contact Sensor per @Wounded suggestion, restoring tamperAlert
39 | * v1.6.7 replaced updateCheck with asynchttp version -- removed setVersion, etc.
40 | * added descTextEnable as option to reduce log.info
41 | * v1.6.6 corrected limitation on Humidity Offset
42 | * v1.6.5 alternate description for settingEnabled input
43 | * v1.6.4 corrected temp offset -128 to 127 in configure()
44 | * v1.6.3 added Preference hiding (settingEnable)
45 | * v1.6.2 removed mapping to ledOption(s)
46 | * v1.6.1 added degree symbol to temp scale
47 | * v1.6 deleted isStateChange throughout
48 | * v1.5 Added LED Options
49 | * v1.4 Added selectiveReport, enabled humidChangeAmount, luxChangeAmount
50 | * v1.3 Restored if (debugOutput) log.debug as default logging is too much. cSteele
51 | * v1.2 Merged Chuckles updates
52 | *
53 | * Chuckles V1.2 of multisensor 6
54 | * IMPORTANT NOTE: Assumes device has firmware version 1.10 or later. A warning will be logged if this is not the case.
55 | * Changes:
56 | 1. Implement standard "Power Source" capability to track whether USB power ("dc") or battery is in use
57 | - Remove custom "powerSupply" and "batteryStatus" attributes it replaces.
58 | 2. Use hub's timezone rather than setting a timezone for each device individually
59 | 3. Use hub's temperature scale setting rather than setting each device individually
60 | 4. Add event handler for ManufacturerSpecificReport (V1)
61 | 5. Corrected configuration parameter number (4) for PIR sensitivity
62 | 6. Corrected getTimeOptionValueMap
63 | 7. Disabled selective reporting on threshold
64 | 8. Device only supports V1 of COMMAND_CLASS_SENSOR_BINARY (0x30) - not V2
65 | 9. Device only supports V1 of COMMAND_CLASS_CONFIGURATION (0x70) - not V2
66 | 10. Motion detection in NotificationReportV3 is event 8, not event 7
67 | 11. Numerous minor bug fixes (e.g. motionDelayTime and reportInterval should always hold enumerated values, not number of seconds)
68 | 12. Numerous small changes for internationalisation (e.g. region agnostic date formats)
69 |
70 | * v1.1d Added remote version checking ** Cobra (CobraVmax) for his original version check code
71 | * csteele v1.1c converted to Hubitat.
72 | *
73 | * LGK V1 of multisensor 6 with customizeable settings , changed some timeouts, also changed tamper to vibration so we can
74 | * use that as well (based on stock device type and also some changes copied from Robert Vandervoort device type.
75 | * Changes
76 | 1. changes colors of temp slightly, add colors to humidity, add color to battery level
77 | 2. remove tamper and instead use feature as contact and acceleration ie vibration sensor
78 | 3. slightly change reporting interval times. (also fix issue where 18 hours was wrong time)
79 | 4. add last update time.. sets when configured and whenever temp is reported.
80 | This is used so at a glance you see the last update time tile to know the device is still reporting easily without looking
81 | at the logs.
82 | 5. add a temp and humidity offset -12 to 12 to enable tweaking the settings if they appear off.
83 | 6. added power status tile to display, currently was here but not displayed.
84 | 7. added configure and refresh tiles.
85 | 8. also added refresh capability so refresh can be forced from other smartapps like pollster. (refresh not currently working all the time for some reason)
86 | 9. changed the sensitivity to have more values than min max etc, now is integer 0 - 127.
87 | 10. fix uv setting which in one release was value of 2 now it is 16.
88 | 11. added icons for temp and humidity
89 | 12. also change the default wakeup time to be the same as the report interval,
90 | otherwise when on battery it disregarded the report interval. (only if less than the default 5 minutes).
91 | 13. added a config option for the min change needed to report temp changes and set it in parameter 41.
92 | 14. incresed range and colors for lux values, as when mine is in direct sun outside it goes as high as 1900
93 | 15. support for celsius added. set in input options.
94 | */
95 |
96 | public static String version() { return "v1.7.1" }
97 |
98 | metadata {
99 | definition (name: "AeotecMultiSensor6", namespace: "cSteele", author: "cSteele", importUrl: "https://raw.githubusercontent.com/HubitatCommunity/AeotecMultiSensor6/master/AeotecMultisensor6.groovy") {
100 | capability "Motion Sensor"
101 | capability "Temperature Measurement"
102 | capability "Relative Humidity Measurement"
103 | capability "Illuminance Measurement"
104 | capability "Ultraviolet Index"
105 | capability "Configuration"
106 | capability "Sensor"
107 | capability "Battery"
108 | capability "Power Source"
109 | capability "Acceleration Sensor"
110 | capability "TamperAlert"
111 |
112 | command "refresh"
113 | // command "updateCheck" // **---** delete for Release
114 |
115 | attribute "firmware", "decimal"
116 |
117 | fingerprint deviceId: "0x2101", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x7A", outClusters: "0x5A"
118 | }
119 |
120 |
121 | preferences {
122 | // Note: Hubitat doesn't appear to honour 'sections' in device handler preferences just now, but hopefully one day...
123 | section("Motion sensor settings") {
124 | input "motionDelayTime", "enum", title: "Motion Sensor Delay Time?",
125 | options: ["20 seconds", "30 seconds", "1 minute", "2 minutes", "3 minutes", "4 minutes"], defaultValue: "1 minute", displayDuringSetup: true
126 | input "motionSensitivity", "enum", title: "Motion Sensor Sensitivity?", options: [5:"Very High", 4:"High", 3:"Medium High", 2:"Medium", 1:"Low"], defaultValue: 5, displayDuringSetup: true
127 | }
128 |
129 | section("Automatic report settings") {
130 | input "reportInterval", "enum", title: "Sensors Report Interval?",
131 | options: ["20 seconds", "30 seconds", "1 minute", "2 minutes", "3 minutes", "4 minutes", "5 minutes", "10 minutes", "15 minutes", "30 minutes", "1 hour", "6 hours", "12 hours", "18 hours", "24 hours"], defaultValue: "5 minutes", displayDuringSetup: true
132 | input "tempChangeAmount", "number", title: "Temperature Change Amount (Tenths of a degree)?", range: "1..70", description: "
The tenths of degrees the temperature must change to induce an automatic report.
", defaultValue: 2, required: false
133 | input "humidChangeAmount", "number", title: "Humidity Change Amount (%)?", range: "1..100", description: "
The percentage the humidity must change to induce an automatic report.
", defaultValue: 10, required: false
134 | input "luxChangeAmount", "number", title: "Luminance Change Amount (LUX)?", range: "-1000..1000", description: "
The amount of LUX the luminance must change to induce an automatic report.
", defaultValue: 100, required: false
135 | }
136 |
137 | section("Calibration settings") {
138 | input "tempOffset", "number", title: "Temperature Offset?", range: "-127..128", description: "
-128 to +127 (Tenths of a degree)
If your temperature is inaccurate this will offset/adjust it by this many tenths of a degree.
", defaultValue: 0, required: false, displayDuringSetup: true
139 | input "humidOffset", "number", title: "Humidity Offset/Adjustment -50 to +50 in percent?", range: "-50..50", description: "
If your humidity is inaccurate this will offset/adjust it by this percent.
", defaultValue: 0, required: false, displayDuringSetup: true
140 | input "luxOffset", "number", title: "Luminance Offset/Adjustment -10 to +10 in LUX?", range: "-10..10", description: "
If your luminance is inaccurate this will offset/adjust it by this percent.
", defaultValue: 0, required: false, displayDuringSetup: true
141 | }
142 |
143 | input "ledOptions", "enum", title: "LED Options",
144 | options: [0:"Fully Enabled", 1:"Fully Disabled", 2:"Disable When Motion (Aeon v1.10 only)"], defaultValue: "0", displayDuringSetup: true
145 | input name: "selectiveReporting", type: "bool", title: "Enable Selective Reporting?", defaultValue: false
146 |
147 | input name: "debugOutput", type: "bool", title: "Enable debug logging?", description: "
", defaultValue: true
148 | input name: "descTextEnable", type: "bool", title: "Enable descriptionText logging?", defaultValue: true
149 | }
150 | }
151 |
152 |
153 | /*
154 | updated
155 |
156 | When "Save Preferences" gets clicked...
157 | */
158 | def updated() {
159 | if (debugOutput) log.debug "In Updated with settings: ${settings}"
160 | if (debugOutput) log.debug "${device.displayName} is now on ${device.latestValue("powerSource")} power"
161 | unschedule()
162 | initialize()
163 | dbCleanUp() // remove antique db entries created in older versions and no longer used.
164 | schedule("0 0 8 ? * FRI *", updateCheck)
165 | if (debugOutput) runIn(1800,logsOff)
166 | runIn(20, updateCheck)
167 |
168 | // Check for any null settings and change them to default values
169 | inputValidationCheck()
170 |
171 | if (device.latestValue("powerSource") == "dc") { //case1: USB powered
172 | // response(configure(2))
173 | } else if (device.latestValue("powerSource") == "battery") { //case2: battery powered
174 | // setConfigured("false") is used by WakeUpNotification
175 | setConfigured("false") //wait until the next time device wakeup to send configure command after user change preference
176 | selectiveReport = 0 // battery, selective reporting is not supported
177 | } else { //case3: power source is not identified, ask user to properly pair the sensor again
178 | log.warn "power source is not identified, check that sensor is powered by USB, if so > configure()"
179 | def request = []
180 | request << zwave.configurationV1.configurationGet(parameterNumber: 101)
181 | response(commands(request))
182 | }
183 | return(configure(1))
184 | }
185 |
186 |
187 | /*
188 | parse
189 |
190 | Respond to received zwave commands.
191 | */
192 | def parse(String description) {
193 | // log.debug "In parse() for description: $description"
194 | def result = null
195 | if (description.startsWith("Err 106")) {
196 | log.warn "parse() >> Err 106"
197 | result = createEvent( name: "secureInclusion", value: "failed",
198 | descriptionText: "This sensor (${device.displayName}) failed to complete the network security key exchange. If you are unable to control it via Hubitat, you must remove it from your network and add it again.")
199 | } else if (description != "updated") {
200 | // log.debug "About to zwave.parse($description)"
201 | def cmd = zwave.parse(description, [0x31: 5, 0x30: 1, 0x70: 1, 0x72: 1, 0x84: 1])
202 | if (cmd) {
203 | // log.debug "About to call handler for ${cmd.toString()}"
204 | result = zwaveEvent(cmd)
205 | }
206 | }
207 | //log.debug "After zwaveEvent(cmd) >> Parsed '${description}' to ${result.inspect()}"
208 | return result
209 | }
210 |
211 |
212 | /*
213 | installed
214 |
215 | Doesn't do much other than call initialize().
216 | */
217 | void installed()
218 | {
219 | initialize()
220 | }
221 |
222 |
223 |
224 | /*
225 | initialize
226 |
227 | Doesn't do much.
228 | */
229 | def initialize() {
230 | if (settings.ledOptions == null) settings.ledOptions = 0 // default to Full
231 | state.firmware = state.firmware ?: 0.0d
232 | }
233 |
234 |
235 | /*
236 | Beginning of Z-Wave Commands
237 | */
238 |
239 | //this notification will be sent only when device is battery powered
240 | def zwaveEvent(hubitat.zwave.commands.wakeupv1.WakeUpNotification cmd) {
241 | def result = [createEvent(descriptionText: "${device.displayName} woke up")]
242 | def cmds = []
243 | if (!isConfigured()) {
244 | if (debugOutput) log.debug("late configure")
245 | result << response(configure(3))
246 | } else {
247 | //if (debugOutput) log.debug("Device has been configured sending >> wakeUpNoMoreInformation()")
248 | cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format()
249 | result << response(cmds)
250 | }
251 | result
252 | }
253 |
254 |
255 | def zwaveEvent(hubitat.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
256 | def encapsulatedCommand = cmd.encapsulatedCommand([0x31: 5, 0x30: 1, 0x70: 1, 0x72: 1, 0x84: 1])
257 | state.sec = 1
258 | if (debugOutput) log.debug "encapsulated: ${encapsulatedCommand}"
259 | if (encapsulatedCommand) {
260 | zwaveEvent(encapsulatedCommand)
261 | } else {
262 | log.warn "Unable to extract encapsulated cmd from $cmd"
263 | createEvent(descriptionText: cmd.toString())
264 | }
265 | }
266 |
267 |
268 | def zwaveEvent(hubitat.zwave.commands.securityv1.SecurityCommandsSupportedReport cmd) {
269 | if (descTextEnable) log.info "Executing zwaveEvent 98 (SecurityV1): 03 (SecurityCommandsSupportedReport) with cmd: $cmd"
270 | state.sec = 1
271 | }
272 |
273 |
274 | def zwaveEvent(hubitat.zwave.commands.securityv1.NetworkKeyVerify cmd) {
275 | state.sec = 1
276 | if (descTextEnable) log.info "Executing zwaveEvent 98 (SecurityV1): 07 (NetworkKeyVerify) with cmd: $cmd (node is securely included)"
277 | def result = [createEvent(name:"secureInclusion", value:"success", descriptionText:"${device.displayName} - Secure inclusion was successful")]
278 | result
279 | }
280 |
281 |
282 | def zwaveEvent(hubitat.zwave.commands.manufacturerspecificv1.ManufacturerSpecificReport cmd) {
283 | if (debugOutput) log.debug "ManufacturerSpecificReport cmd = $cmd"
284 | if (debugOutput) log.debug "manufacturerId: ${cmd.manufacturerId}"
285 | if (debugOutput) log.debug "manufacturerName: ${cmd.manufacturerName}"
286 | if (debugOutput) log.debug "productId: ${cmd.productId}"
287 | def model = "" // We'll decode the specific model for the log, but we don't currently use this info
288 | switch(cmd.productTypeId >> 8) {
289 | case 0: model = "EU"
290 | break
291 | case 1: model = "US"
292 | break
293 | case 2: model = "AU"
294 | break
295 | case 10: model = "JP"
296 | break
297 | case 29: model = "CN"
298 | break
299 | default: model = "unknown"
300 | }
301 | if (debugOutput) log.debug "model: ${model}"
302 | if (debugOutput) log.debug "productTypeId: ${cmd.productTypeId}"
303 | def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
304 | updateDataValue("MSR", msr)
305 | }
306 |
307 |
308 | def zwaveEvent(hubitat.zwave.commands.batteryv1.BatteryReport cmd) {
309 | if (debugOutput) log.debug "In BatteryReport"
310 | def result = []
311 | def map = [ name: "battery", unit: "%" ]
312 | if (cmd.batteryLevel == 0xFF) {
313 | map.value = 1
314 | map.descriptionText = "${device.displayName} battery is low"
315 | } else {
316 | map.value = cmd.batteryLevel
317 | map.descriptionText = "${device.displayName} battery is ${map.value}%"
318 | }
319 | createEvent(map)
320 | }
321 |
322 |
323 | def zwaveEvent(hubitat.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd){
324 | if (debugOutput) log.debug "In multi level report cmd = $cmd"
325 |
326 | if (cmd.scaledSensorValue == null) return
327 | def map = [:]
328 | switch (cmd.sensorType) {
329 | case 1:
330 | if (debugOutput) log.debug "raw temp = $cmd.scaledSensorValue"
331 | // Convert temperature (if needed) to the system's configured temperature scale
332 | def finalval = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmd.scale == 1 ? "F" : "C", cmd.precision)
333 |
334 | if (debugOutput) log.debug "finalval = $finalval"
335 |
336 | map.value = finalval
337 | map.unit = "\u00b0" + getTemperatureScale()
338 | map.name = "temperature"
339 | map.descriptionText = "${device.displayName} temperature is ${map.value}${map.unit}"
340 | if (descTextEnable) log.info "Temperature is ${map.value}${map.unit}"
341 | break
342 | case 3:
343 | if (debugOutput) log.debug "raw illuminance = $cmd.scaledSensorValue"
344 | map.name = "illuminance"
345 | map.value = cmd.scaledSensorValue.toInteger() // roundIt((cmd.scaledSensorValue / 2.0),0) as Integer // .toInteger()
346 | map.unit = "lux"
347 | map.descriptionText = "${device.displayName} illuminance is ${map.value} Lux"
348 | if (descTextEnable) log.info "Illuminance is ${map.value} Lux"
349 | break
350 | case 5:
351 | if (debugOutput) log.debug "raw humidity = $cmd.scaledSensorValue"
352 | map.value = roundIt(cmd.scaledSensorValue, 0) as Integer // .toInteger()
353 | map.unit = "%"
354 | map.name = "humidity"
355 | map.descriptionText = "${device.displayName} humidity is ${map.value}%"
356 | if (descTextEnable) log.info "Humidity is ${map.value}%"
357 | break
358 | case 27:
359 | if (debugOutput) log.debug "raw uv index = $cmd.scaledSensorValue"
360 | map.name = "ultravioletIndex"
361 | map.value = roundIt(cmd.scaledSensorValue, 0) as Integer // .toInteger()
362 | map.descriptionText = "${device.displayName} ultraviolet index is ${map.value}"
363 | if (descTextEnable) log.info "Ultraviolet index is ${map.value}"
364 | break
365 | default:
366 | map.descriptionText = cmd.toString()
367 | }
368 | createEvent(map)
369 | }
370 |
371 |
372 | def zwaveEvent(hubitat.zwave.commands.sensorbinaryv1.SensorBinaryReport cmd) {
373 | motionEvent(cmd.sensorValue, "SensorBinaryReport")
374 | }
375 |
376 |
377 | def zwaveEvent(hubitat.zwave.commands.basicv1.BasicSet cmd) {
378 | // Sensor sends value 0xFF on motion, 0x00 on no motion (after expiry interval)
379 | motionEvent(cmd.value, "BasicSet")
380 | }
381 |
382 |
383 | def zwaveEvent(hubitat.zwave.commands.notificationv3.NotificationReport cmd) {
384 | if (debugOutput) log.debug "NotificationReport: ${cmd}"
385 | def result = []
386 | if (cmd.notificationType == 7) {
387 | // spec says type 7 is 'Home Security'
388 | switch (cmd.event) {
389 | case 0:
390 | // spec says this is 'clear previous alert'
391 | //sendEvent(name: "contact", value: "closed", descriptionText: "$device.displayName is closed", displayed: true)
392 | // result << motionEvent(0, "NotificationReport") // see Change Note for v1.7
393 | result << createEvent(name: "tamper", value: "clear", descriptionText: "${device.displayName} tamper cleared", displayed: true)
394 | if (descTextEnable) log.info "Tamper cleared by NotificationReport"
395 | result << createEvent(name: "acceleration", value: "inactive", descriptionText: "${device.displayName} acceleration is inactive", displayed: true)
396 | if (descTextEnable) log.info "Acceleration is inactive by NotificationReport"
397 | break
398 | case 3:
399 | // spec says this is 'tamper'
400 | //sendEvent(name: "contact", value: "open", descriptionText: "$device.displayName is open", displayed: true)
401 | result << createEvent(name: "tamper", value: "detected", descriptionText: "${device.displayName} tamper detected", displayed: true)
402 | if (descTextEnable) log.info "Tamper detected"
403 | result << createEvent(name: "acceleration", value: "active", descriptionText: "${device.displayName} acceleration is active", displayed: true)
404 | if (descTextEnable) log.info "Acceleration is active"
405 | break
406 | case 8:
407 | // spec says this is 'unknown motion detection'
408 | // result << motionEvent(1, "NotificationReport") // see Change Note for v1.7
409 | break
410 | }
411 | } else {
412 | log.warn "Need to handle this cmd.notificationType: ${cmd.notificationType}"
413 | result << createEvent(descriptionText: cmd.toString())
414 | }
415 | result
416 | }
417 |
418 |
419 | def zwaveEvent(hubitat.zwave.commands.configurationv1.ConfigurationReport cmd) {
420 | if (debugOutput) log.debug "---CONFIGURATION REPORT V1--- ${device.displayName} parameter ${cmd.parameterNumber} with a byte size of ${cmd.size} is set to ${cmd.configurationValue}"
421 | def result = []
422 | def value
423 | if (cmd.parameterNumber == 9 && cmd.configurationValue[0] == 0) {
424 | value = "dc"
425 | if (!isConfigured()) {
426 | if (debugOutput) log.debug("Configuration Report: configuring device")
427 | result << response(configure(4))
428 | }
429 | result << createEvent(name: "powerSource", value: value, descriptionText: "${device.displayName} power source is dc (mains)", displayed: false)
430 | if (descTextEnable) log.info "Power source is DC (mains)"
431 | }
432 | else if (cmd.parameterNumber == 9 && cmd.configurationValue[0] == 1) {
433 | value = "battery"
434 | result << createEvent(name: "powerSource", value: value, descriptionText: "${device.displayName} power source is battery", displayed: false)
435 | if (descTextEnable) log.info "Power source is battery"
436 | }
437 | else if (cmd.parameterNumber == 101){
438 | result << response(configure(5))
439 | }
440 | result
441 | }
442 |
443 |
444 | def zwaveEvent(hubitat.zwave.Command cmd) {
445 | if (debugOutput) log.debug "General zwaveEvent cmd: ${cmd}"
446 | createEvent(descriptionText: cmd.toString(), isStateChange: false)
447 | }
448 |
449 |
450 | def zwaveEvent(hubitat.zwave.commands.versionv1.VersionCommandClassReport cmd) {
451 | if (debugOutput) log.debug "in version command class report"
452 | if (debugOutput) log.debug "---VERSION COMMAND CLASS REPORT V1--- ${device.displayName} has version: ${cmd.commandClassVersion} for command class ${cmd.requestedCommandClass} - payload: ${cmd.payload}"
453 | }
454 |
455 |
456 | def zwaveEvent(hubitat.zwave.commands.versionv1.VersionReport cmd) {
457 | if (debugOutput) log.debug "in version report"
458 | // SubVersion is in 1/100ths so that 1.01 < 1.08 < 1.10, etc.// state.firmware = 0.0d
459 | if (cmd.firmware0Version) {
460 | BigDecimal fw = cmd.firmware0Version + (cmd.firmware0SubVersion/100)
461 | state.firmware = fw
462 | }
463 | if (debugOutput) log.debug "---VERSION REPORT V1--- ${device.displayName} is running firmware version: ${String.format("%1.2f",state.firmware)}, Z-Wave version: ${cmd.zWaveProtocolVersion}.${cmd.zWaveProtocolSubVersion}"
464 | if(state.firmware < 1.10)
465 | log.warn "--- WARNING: Device handler expects devices to have firmware 1.10 or later"
466 | }
467 |
468 |
469 | def zwaveEvent(hubitat.zwave.commands.firmwareupdatemdv2.FirmwareMdReport cmd) {
470 | // NOTE: This command class is not yet implemented by Hubitat...
471 | if (debugOutput) log.debug "---FIRMWARE METADATA REPORT V2 ${device.displayName} manufacturerId: ${cmd.manufacturerId} firmwareId: ${cmd.firmwareId}"
472 | }
473 |
474 |
475 | private command(hubitat.zwave.Command cmd) {
476 | if (state.sec) {
477 | if (debugOutput) log.debug "Sending secure Z-wave command: ${cmd.toString()}"
478 | return zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
479 | } else {
480 | if (debugOutput) log.debug "Sending Z-wave command: ${cmd.toString()}"
481 | return cmd.format()
482 | }
483 | }
484 |
485 |
486 | private commands(commands, delay=1000) {
487 | //if (descTextEnable) log.info "sending commands: ${commands}"
488 | return delayBetween(commands.collect{ command(it) }, delay)
489 | }
490 | /*
491 | End of Z-Wave Commands
492 |
493 | Beginning of Driver Commands
494 |
495 | */
496 | def configure(ccc) {
497 | if (debugOutput) log.debug "ccc: $ccc"
498 | // This sensor joins as a secure device if you double-click the button to include it
499 | if (descTextEnable) log.info "${device.displayName} is configuring its settings"
500 |
501 | if (device.currentValue('tamper') == null) {
502 | sendEvent(name: 'tamper', value: 'clear', descriptionText: '${device.displayName} tamper cleared')
503 | sendEvent(name: 'acceleration', value: 'inactive', descriptionText: "$device.displayName} acceleration is inactive")
504 | }
505 |
506 | // inputValidationCheck()
507 |
508 | if (debugOutput) {
509 | log.debug "Report Interval = $reportInterval"
510 | log.debug "Motion Delay Time = $motionDelayTime"
511 | log.debug "Motion Sensitivity = $motionSensitivity"
512 | log.debug "Temperature adjust = $tempOffset (${tempOffset/10}°)"
513 | log.debug "Humidity adjust = $humidOffset"
514 | log.debug "Min Temp change for reporting = $tempChangeAmount"
515 | log.debug "Min Humidity change for reporting = $humidChangeAmount"
516 | log.debug "Min Lux change for reporting = $luxChangeAmount"
517 | log.debug "LED Option = $ledOptions"
518 | }
519 |
520 | def now = new Date()
521 | def tf = new java.text.SimpleDateFormat("dd-MMM-yyyy h:mm a")
522 | tf.setTimeZone(location.getTimeZone())
523 | def newtime = "${tf.format(now)}" as String
524 | sendEvent(name: "lastUpdate", value: newtime, descriptionText: "${device.displayName} configured at ${newtime}")
525 |
526 | setConfigured("true")
527 | def waketime
528 |
529 | if (timeOptionValueMap[settings.reportInterval] < 300)
530 | waketime = timeOptionValueMap[settings.reportInterval]
531 | else waketime = 300
532 |
533 | if (debugOutput) log.debug "wake time reset to $waketime"
534 | if (debugOutput) log.debug "Current firmware: ${sprintf ("%1.2f", state.firmware)}"
535 |
536 | // Retrieve local temperature scale: "C" = Celsius, "F" = Fahrenheit
537 | // Convert to a value of 1 or 2 as used by the device to select the scale
538 | if (debugOutput) log.debug "Location temperature scale: ${location.getTemperatureScale()}"
539 | byte tempScaleByte = (location.getTemperatureScale() == "C" ? 1 : 2)
540 | selectiveReport = selectiveReporting ? 1 : 0
541 |
542 | def request = [
543 | // set wakeup interval to report time otherwise it doesnt report in time
544 | zwave.wakeUpV1.wakeUpIntervalSet(seconds:waketime, nodeid:zwaveHubNodeId),
545 |
546 | zwave.versionV1.versionGet(),
547 | zwave.manufacturerSpecificV1.manufacturerSpecificGet(),
548 |
549 | // Hubitat have not yet implemented the firmwareUpdateMdV2 class
550 | //zwave.firmwareUpdateMdV2.firmwareMdGet(),
551 |
552 | //1. set association groups for hub
553 | zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId),
554 | zwave.associationV1.associationSet(groupingIdentifier:2, nodeId:zwaveHubNodeId),
555 |
556 | //2. automatic report flags
557 | // params 101-103 [4 bytes] 128: light sensor, 64 humidity, 32 temperature sensor, 16 ultraviolet sensor, 1 battery sensor -> send command 241 to get all reports
558 | zwave.configurationV1.configurationSet(parameterNumber: 101, size: 4, scaledConfigurationValue: 241), //association group 1 - all reports
559 | zwave.configurationV1.configurationSet(parameterNumber: 102, size: 4, scaledConfigurationValue: 1), //association group 2 - battery report
560 |
561 | //3. no-motion report x seconds after motion stops (default 60 secs)
562 | zwave.configurationV1.configurationSet(parameterNumber: 3, size: 2, scaledConfigurationValue: timeOptionValueMap[motionDelayTime] ?: 60),
563 |
564 | //4. motion sensitivity: 0 (least sensitive) - 5 (most sensitive)
565 | zwave.configurationV1.configurationSet(parameterNumber: 4, size: 1, scaledConfigurationValue: motionSensitivity as int),
566 | //5. report every x minutes (threshold reports don't work on battery power, default 8 mins)
567 | zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: timeOptionValueMap[reportInterval]), //association group 1
568 | // battery report time.. too long at every 6 hours change to 2 hours.
569 | zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: 2*60*60), //association group 2
570 | //6. enable/disable selective reporting only on thresholds
571 | zwave.configurationV1.configurationSet(parameterNumber: 40, size: 1, scaledConfigurationValue: selectiveReport),
572 | // Set the temperature scale for automatic reports
573 | // US units default to reporting in Fahrenheit, whilst all others default to reporting in Celsius, but we can configure the preferred scale with this setting
574 | zwave.configurationV1.configurationSet(parameterNumber: 64, size: 1, configurationValue: [tempScaleByte]),
575 | // Automatically generate a report when temp changes by specified amount
576 | zwave.configurationV1.configurationSet(parameterNumber: 41, size: 4, configurationValue: [0, tempChangeAmount, tempScaleByte, 0]),
577 | // Automatically generate a report when humidity changes by specified amount
578 | zwave.configurationV1.configurationSet(parameterNumber: 42, size: 1, scaledConfigurationValue: humidChangeAmount),
579 | // Automatically generate a report when lux changes by specified amount
580 | zwave.configurationV1.configurationSet(parameterNumber: 43, size: 2, scaledConfigurationValue: luxChangeAmount),
581 | // send (1) BasicSet or (2) SensorBinary report for motion
582 | zwave.configurationV1.configurationSet(parameterNumber: 0x05, size: 1, scaledConfigurationValue: 2),
583 | // Set temperature calibration offset
584 | zwave.configurationV1.configurationSet(parameterNumber: 201, size: 2, configurationValue: [tempOffset as int, tempScaleByte]),
585 | // Set humidity calibration offset
586 | zwave.configurationV1.configurationSet(parameterNumber: 202, size: 1, scaledConfigurationValue: humidOffset),
587 | // Set luminance calibration offset
588 | zwave.configurationV1.configurationSet(parameterNumber: 203, size: 2, scaledConfigurationValue: luxOffset),
589 | // Set LED Option value
590 | zwave.configurationV1.configurationSet(parameterNumber: 81, size: 1, configurationValue: [ledOptions as int]),
591 | //7. query sensor data
592 | zwave.batteryV1.batteryGet(),
593 | zwave.sensorBinaryV1.sensorBinaryGet(), //motion
594 | zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1), //temperature
595 | zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 3), //illuminance
596 | zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 5), //humidity
597 | zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 27) //ultravioletIndex
598 | ]
599 | return commands(request) + ["delay 20000", zwave.wakeUpV1.wakeUpNoMoreInformation().format()]
600 | }
601 |
602 |
603 | def refresh() {
604 | if (debugOutput) log.debug "in refresh"
605 |
606 | return commands([
607 | zwave.versionV1.versionGet(), // Retrieve version info (includes firmware version)
608 | // zwave.firmwareUpdateMdV2.firmwareMdGet(), // Command class not implemented by Hubitat yet
609 | zwave.configurationV1.configurationGet(parameterNumber: 9), // Retrieve current power mode
610 | zwave.batteryV1.batteryGet(),
611 | zwave.sensorBinaryV1.sensorBinaryGet(), //motion
612 | zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1), //temperature
613 | zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 3), //illuminance
614 | zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 5), //humidity
615 | zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 27) //ultravioletIndex
616 | ])
617 | }
618 |
619 | /*
620 | Begin support methods
621 | */
622 |
623 | def motionEvent(value, by) {
624 | def map = [name: "motion"]
625 | if (value) {
626 | if (descTextEnable) log.info "Motion is active by $by"
627 | map.value = "active"
628 | map.descriptionText = "${device.displayName} motion is active by $by"
629 | } else {
630 | if (descTextEnable) log.info "Motion is inactive by $by"
631 | map.value = "inactive"
632 | map.descriptionText = "${device.displayName} motion is inactive by $by"
633 | }
634 | createEvent(map)
635 | }
636 |
637 |
638 | def getTimeOptionValueMap() { [
639 | "20 seconds" : 20,
640 | "30 seconds" : 30,
641 | "1 minute" : 60,
642 | "2 minutes" : 2*60,
643 | "3 minutes" : 3*60,
644 | "4 minutes" : 4*60,
645 | "5 minutes" : 5*60,
646 | "10 minutes" : 10*60,
647 | "15 minutes" : 15*60,
648 | "30 minutes" : 30*60,
649 | "1 hour" : 1*60*60,
650 | "6 hours" : 6*60*60,
651 | "12 hours" : 12*60*60,
652 | "18 hours" : 18*60*60,
653 | "24 hours" : 24*60*60,
654 | ]}
655 |
656 |
657 | // Check for any null settings and change them to default values
658 | void inputValidationCheck () {
659 |
660 | motionSensitivity = motionSensitivity ?: 0
661 | motionDelayTime = motionDelayTime ?: "1 minute"
662 | reportInterval = reportInterval ?: "5 minute"
663 | tempChangeAmount = tempChangeAmount ?: 2
664 | humidChangeAmount = humidChangeAmount ?: 10
665 | luxChangeAmount = luxChangeAmount ?: 100
666 | tempOffset = tempOffset ?: 0
667 | ledOptions = ledOptions ?: 0
668 |
669 | // Validate Input Ranges
670 | def motionRange = 0..5
671 | def tempRange = 0..128
672 | def humidRange = 0..50
673 | def luxRange = 0..1000
674 |
675 | if ( !motionRange.contains(motionSensitivity as int) ) { motionSensitivity = 3 ; log.warn "Selection out of Range: Motion Sensitivity set to 3"; }
676 | if ( !tempRange.contains( tempOffset.abs() as int ) ) { tempOffset = 0 ; log.warn "Selection out of Range: Temperature Offset set to 0"; }
677 | if ( !humidRange.contains( humidOffset.abs() as int ) ) { humidOffset = 0 ; log.warn "Selection out of Range: Humidity Offset set to 0"; }
678 | if ( !luxRange.contains( luxOffset.abs() as int ) ) { luxOffset = 0 ; log.warn "Selection out of Range: Luminance Offset set to 0"; }
679 | }
680 |
681 |
682 | private setConfigured(configure) {
683 | updateDataValue("configured", configure)
684 | }
685 |
686 |
687 | private isConfigured() {
688 | getDataValue("configured") == "true"
689 | }
690 |
691 |
692 | def roundIt( value, decimals=0 ) {
693 | return (value == null) ? null : value.toBigDecimal().setScale(decimals, BigDecimal.ROUND_HALF_UP)
694 | }
695 |
696 |
697 | def roundIt( BigDecimal value, decimals=0) {
698 | return (value == null) ? null : value.setScale(decimals, BigDecimal.ROUND_HALF_UP)
699 | }
700 |
701 |
702 | def logsOff(){
703 | log.warn "debug logging disabled..."
704 | device.updateSetting("debugOutput",[value:"false",type:"bool"])
705 | }
706 |
707 |
708 | private dbCleanUp() {
709 | // clean up state variables that are obsolete
710 | // state.remove("tempOffset")
711 | // state.remove("version")
712 | // state.remove("Version")
713 | // state.remove("sensorTemp")
714 | // state.remove("author")
715 | // state.remove("Copyright")
716 | state.remove("verUpdate")
717 | state.remove("verStatus")
718 | state.remove("Type")
719 | }
720 |
721 |
722 | // Check Version ***** with great thanks and acknowlegment to Cobra (CobraVmax) for his original code ****
723 | def updateCheck()
724 | {
725 | def paramsUD = [uri: "https://hubitatcommunity.github.io/AeotecMultiSensor6/version2.json"]
726 | asynchttpGet("updateCheckHandler", paramsUD)
727 | }
728 |
729 |
730 | def updateCheckHandler(resp, data) {
731 |
732 | state.InternalName = "AeotecMultiSensor6"
733 |
734 | if (resp.getStatus() == 200 || resp.getStatus() == 207) {
735 | respUD = parseJson(resp.data)
736 | // log.warn " Version Checking - Response Data: $respUD" // Troubleshooting Debug Code - Uncommenting this line should show the JSON response from your webserver
737 | state.Copyright = "${thisCopyright} -- ${version()}"
738 | // uses reformattted 'version2.json'
739 | def newVer = padVer(respUD.driver.(state.InternalName).ver)
740 | def currentVer = padVer(version())
741 | state.UpdateInfo = (respUD.driver.(state.InternalName).updated)
742 | // log.debug "updateCheck: ${respUD.driver.(state.InternalName).ver}, $state.UpdateInfo, ${respUD.author}"
743 |
744 | switch(newVer) {
745 | case { it == "NLS"}:
746 | state.Status = "** This Driver is no longer supported by ${respUD.author} **"
747 | if (descTextEnable) log.warn "** This Driver is no longer supported by ${respUD.author} **"
748 | break
749 | case { it > currentVer}:
750 | state.Status = "New Version Available (Version: ${respUD.driver.(state.InternalName).ver})"
751 | if (descTextEnable) log.warn "** There is a newer version of this Driver available (Version: ${respUD.driver.(state.InternalName).ver}) **"
752 | if (descTextEnable) log.warn "** $state.UpdateInfo **"
753 | break
754 | case { it < currentVer}:
755 | state.Status = "You are using a Test version of this Driver (Expecting: ${respUD.driver.(state.InternalName).ver})"
756 | if (descTextEnable) log.warn "You are using a Test version of this Driver (Expecting: ${respUD.driver.(state.InternalName).ver})"
757 | break
758 | default:
759 | state.Status = "Current"
760 | if (descTextEnable) log.info "You are using the current version of this driver"
761 | break
762 | }
763 | sendEvent(name: "chkUpdate", value: state.UpdateInfo)
764 | sendEvent(name: "chkStatus", value: state.Status)
765 | }
766 | else
767 | {
768 | log.error "Something went wrong: CHECK THE JSON FILE AND IT'S URI"
769 | }
770 | }
771 |
772 | /*
773 | padVer
774 |
775 | Version progression of 1.4.9 to 1.4.10 would mis-compare unless each duple is padded first.
776 |
777 | */
778 | String padVer(ver) {
779 | def pad = ""
780 | ver.replaceAll( "[vV]", "" ).split( /\./ ).each { pad += it.padLeft( 2, '0' ) }
781 | return pad
782 | }
783 |
784 | String getThisCopyright(){"© 2019 C Steele "}
785 |
--------------------------------------------------------------------------------