Another Aeon Multisensor 6 DH

/**
 *  Copyright 2015 SmartThings
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 * LGK V1 of multisensor 6 with customizeable settings , changed some timeouts, also changed tamper to vibration so we can
 * use that as well (based on stock device type and also some changes copied from Robert Vandervoort device type.
 * Changes
   1. changes colors of temp slightly, add colors to humidity, add color to battery level
   2. remove tamper and instead use feature as contact and acceleration ie vibration sensor
   3. slightly change reporting interval times. (also fix issue where 18 hours was wrong time)
   4. add last update time.. sets when configured and whenever temp is reported. 
      This is used so at a glance you see the last update time tile to know the device is still reporting easily without looking
      at the logs.
   5. add a temp and humidity offset -12 to 12 to enable tweaking the settings if they appear off.
   6. added power status tile to display, currently was here but not displayed.
   7. added configure and refresh tiles.
   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)
   9. changed the sensitivity to have more values than min max etc, now is integer 0 - 127. 
   10. fix uv setting which in one release was value of 2 now it is 16.
   11. added icons for temp and humidity
   12. also change the default wakeup time to be the same as the report interval, 
        otherwise when on battery it disregarded the report interval. (only if less than the default 5 minutes).
   13. added a config option for the min change needed to report temp changes and set it in parameter 41.
   14. incresed range and colors for lux values, as when mine is in direct sun outside it goes as high as 1900
   15. support for celsius added. set in input options.
*/

metadata {
	definition (name: "Aeon Multisensor 6 V1", namespace: "lgkapps", author: "lgkapps") {
		capability "Motion Sensor"
		capability "Temperature Measurement"
		capability "Relative Humidity Measurement"
		capability "Illuminance Measurement"
		capability "Ultraviolet Index"
		capability "Configuration"
		capability "Sensor"
		capability "Battery"
   		capability "Acceleration Sensor"
   		capability "Contact Sensor"
//        capability "refresh"

//		attribute "tamper", "enum", ["detected", "clear"]
		attribute "batteryStatus", "string"
		attribute "powerSupply", "enum", ["USB Cable", "Battery"]
      
		fingerprint deviceId: "0x2101", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x7A", outClusters: "0x5A"
	}


	preferences {
		//input description: "Please consult AEOTEC MULTISENSOR 6 operating manual for advanced setting options. You can skip this configuration to use default settings",
		//		title: "Advanced Configuration", displayDuringSetup: true, type: "paragraph", element: "paragraph"
		input "motionDelayTime", "enum", title: "Motion Sensor Delay Time?",
				options: ["20 seconds", "30 seconds", "1 minute", "2 minutes", "3 minutes", "4 minutes"], defaultValue: "1 minute", displayDuringSetup: true
		input "motionSensitivity", "number", title: "Motion Sensor Sensitivity? (0 = off, 1 = min., 3 = normal, 5 = max.)", range: "0..5", defaultValue: 3, displayDuringSetup: true
		input "reportInterval", "enum", title: "Sensors Report Interval?",
				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
 		input("TempOffset", "number", title: "Temperature Offset/Adjustment -10 to +10 in Degrees?",range: "-10..10", description: "If your temperature is innacurate this will offset/adjust it by this many degrees.", defaultValue: 0, required: false, displayDuringSetup: true)
   		input("HumidOffset", "number", title: "Humidity Offset/Adjustment -10 to +10 in percent?",range: "-10..10", description: "If your humidty is innacurate this will offset/adjust it by this percent.", defaultValue: 0, required: false, displayDuringSetup: true)
   		input("tzOffset", "number", title: "Time zone offset +/-x?", required: false, range: "-12..14", defaultValue:  -5, description: "Time Zone Offset ie -5.", displayDuringSetup: true)
		input("tempScale", "enum", title: "Temperature Scale?", options: ["F","C"], defaltValue: "F", description: "What is your temperature scale?", displayDuringSetup: true)
     	input("TempChangeAmount", "number", title: "Temperature Change Amount (1 = .1 degree)?", range: "1..70",description: "The tenths of degrees the temperature must changes before a report is sent?", defaultValue: 2,required: false)
     }
}

def updated() {
	log.debug "In Updated with settings: ${settings}"
	log.debug "${device.displayName} is now ${device.latestValue("powerSupply")}"  
    
if (settings.motionDelayTime == null)
  settings.motionDelayTime = 60
if (settings.reportInterval == null)
  settings.reportInterval = 8*60
if (settings.motionSensitivity == null)
  settings.motionSensitivity = 3
if (settings.TempOffset == null)
  settings.TempOffset = 0
if (settings.HumidOffset == null)
  settings.HumidOffset = 0
if (settings.tzOffset == null)
  settings.tzOffset = -5
if (settings.tempScale == null)
  settings.tempScale = "F"
if (settings.tempChangeAmount == null)
  settings.tempChangeAmount = 2
  
  if (settings.motionSensitivity < 0)
    {
      log.debug "Illegal motion sensitivity ... resetting to 0!"
      settings.motionSensitivity = 0
    }
    
   if (settings.motionSensitivity > 5)
    {
      log.debug "Illegal motion sensitivity ... resetting to 5!"
      settings.motionSensitivity = 5
    }
    
   // fix tz offset
 if (settings.tzOffset < -12)
  {
    settings.tzOffset = -12
    log.debug "Timezone too low... resetting to -12"
    }
    
 if (settings.tzOffset > 14)
  {
    settings.tzOffset = 14
    log.debug "Timezone too high ... resetting to 14"
    }
    
     // fix temp offset
 if (settings.TempOffset < -10)
  {
    settings.TempOffset = -10
    log.debug "Temperature Offset too low... resetting to -10"
    }
    
 if (settings.TempOffset > 10)
  {
    settings.TempOffset = 10
    log.debug "Temperature Adjusment too high ... resetting to 10"
    }

     // fix temp offset
 if (settings.HumidOffset < -10)
  {
    settings.HumidOffset = -10
    log.debug "Humidity Offset too low... resetting to -10"
    }
    
 if (settings.HumidOffset > 10)
  {
    settings.HumidOffset = 10
    log.debug "Humidity Adjusment too high ... resetting to 10"
    }

	if (device.latestValue("powerSupply") == "USB Cable") {  //case1: USB powered
		response(configure())
	} else if (device.latestValue("powerSupply") == "Battery") {  //case2: battery powered
		// setConfigured("false") is used by WakeUpNotification
		setConfigured("false") //wait until the next time device wakeup to send configure command after user change preference
	} else { //case3: power source is not identified, ask user to properly pair the sensor again
		log.warn "power source is not identified, check it sensor is powered by USB, if so > configure()"
		def request = []
		request << zwave.configurationV1.configurationGet(parameterNumber: 101)
		response(commands(request))
	}
    return(configure())
}

def parse(String description) {
	log.debug "parse() >> description: ${description}"
	def result = null
	if (description.startsWith("Err 106")) {
		log.debug "parse() >> Err 106"
		result = createEvent( name: "secureInclusion", value: "failed", isStateChange: true,
				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.")
	} else if (description != "updated") {
		//log.debug "parse3() >> zwave.parse(description)"
		def cmd = zwave.parse(description, [0x31: 5, 0x30: 2, 0x84: 1])
		if (cmd) {
			result = zwaveEvent(cmd)
		}
	}
	//log.debug "After zwaveEvent(cmd) >> Parsed '${description}' to ${result.inspect()}"
	return result
}

//this notification will be sent only when device is battery powered
def zwaveEvent(hubitat.zwave.commands.wakeupv1.WakeUpNotification cmd) {
	def result = [createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)]
	def cmds = []
	if (!isConfigured()) {
		log.debug("late configure")
		result << response(configure())
	} else {
		//log.debug("Device has been configured sending >> wakeUpNoMoreInformation()")
		cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format()
		result << response(cmds)
	}
	result
}

def zwaveEvent(hubitat.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
	def encapsulatedCommand = cmd.encapsulatedCommand([0x31: 5, 0x30: 2, 0x84: 1])
	state.sec = 1
	log.debug "encapsulated: ${encapsulatedCommand}"
	if (encapsulatedCommand) {
		zwaveEvent(encapsulatedCommand)
	} else {
		log.warn "Unable to extract encapsulated cmd from $cmd"
		createEvent(descriptionText: cmd.toString())
	}
}

def zwaveEvent(hubitat.zwave.commands.securityv1.SecurityCommandsSupportedReport cmd) {
	log.info "Executing zwaveEvent 98 (SecurityV1): 03 (SecurityCommandsSupportedReport) with cmd: $cmd"
	state.sec = 1
}

def zwaveEvent(hubitat.zwave.commands.securityv1.NetworkKeyVerify cmd) {
	state.sec = 1
	log.info "Executing zwaveEvent 98 (SecurityV1): 07 (NetworkKeyVerify) with cmd: $cmd (node is securely included)"
	def result = [createEvent(name:"secureInclusion", value:"success", descriptionText:"Secure inclusion was successful", isStateChange: true)]
	result
}

def zwaveEvent(hubitat.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
 log.debug "in manuf specific cmd = $cmd"
 
	log.info "Executing zwaveEvent 72 (ManufacturerSpecificV2) : 05 (ManufacturerSpecificReport) with cmd: $cmd"
	log.debug "manufacturerId:   ${cmd.manufacturerId}"
	log.debug "manufacturerName: ${cmd.manufacturerName}"
	log.debug "productId:        ${cmd.productId}"
	log.debug "productTypeId:    ${cmd.productTypeId}"
	def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
	updateDataValue("MSR", msr)
}

def zwaveEvent(hubitat.zwave.commands.batteryv1.BatteryReport cmd) {
	def result = []
	def map = [ name: "battery", unit: "%" ]
	if (cmd.batteryLevel == 0xFF) {
		map.value = 1
		map.descriptionText = "${device.displayName} battery is low"
		map.isStateChange = true
	} else {
		map.value = cmd.batteryLevel
	}
	state.lastbatt = now()
	result << createEvent(map)
	if (device.latestValue("powerSupply") != "USB Cable"){
		result << createEvent(name: "batteryStatus", value: "${map.value} % battery", displayed: false)
	}
	result
}

def zwaveEvent(hubitat.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd){
log.debug "\nin multi level report cmd = $cmd\n"
 
	def map = [:]
	switch (cmd.sensorType) {
		case 1:
            log.debug "raw temp = $cmd.scaledSensorValue"
            
            if (settings.tzOffset == null)
 				settings.tzOffset = -5
 
            def now = new Date()
            def tf = new java.text.SimpleDateFormat("MM/dd/yyyy h:mm a")
            tf.setTimeZone(TimeZone.getTimeZone("GMT${settings.tzOffset}"))
            def newtime = "${tf.format(now)}" as String   
            sendEvent(name: "lastUpdate", value: newtime, descriptionText: "Last Update: $newtime")
            
            BigDecimal offset = settings.TempOffset
            // now way to change to c in device so do differently
            def startval =convertTemperatureIfNeeded(cmd.scaledSensorValue, cmd.scale == 1 ? "F" : "C", cmd.precision)
            if (settings.tempScale == "C" && cmd.scale == 0)
               startval = cmd.scaledSensorValue
               
            log.debug "scaled scaled sensor value = $cmd.scaledSensorValue scale = $cmd.scale"
            log.debug "offset = $offset"
            log.debug "startval = $startval"
			def thetemp = startval as BigDecimal
            log.debug "the temp = $thetemp"
            offset = offset ?: 0
            def newValue = (Math.round((thetemp * 100) + (offset * 100)) / 100)
            BigDecimal adjval = (thetemp + offset)
            def dispval =  String.format("%5.1f", adjval)
            def finalval = dispval as BigDecimal
            map.value = finalval
            map.unit = getTemperatureScale()
			map.name = "temperature"
			break
		case 3:
            log.debug "raw illuminance = $cmd.scaledSensorValue"
			map.name = "illuminance"
			map.value = cmd.scaledSensorValue.toInteger()
			map.unit = "lux"
			break
		case 5:
            log.debug "raw humidity = $cmd.scaledSensorValue"
            settings.HumidOffset = settings.HumidOffset ?: 0
            map.value = (cmd.scaledSensorValue.toInteger() + settings.HumidOffset)
			map.unit = "%"
			map.name = "humidity"
			break
		case 27:
            log.debug "raw uv index = $cmd.scaledSensorValue"
			map.name = "ultravioletIndex"
			map.value = cmd.scaledSensorValue.toInteger()
			break
		default:
			map.descriptionText = cmd.toString()
	}
	createEvent(map)
}

def motionEvent(value) {
	def map = [name: "motion"]
	if (value) {
        log.debug "motion active"
		map.value = "active"
		map.descriptionText = "$device.displayName detected motion"
	} else {
       log.debug "motion inactive"
		map.value = "inactive"
		map.descriptionText = "$device.displayName motion has stopped"
	}
	createEvent(map)
}

def zwaveEvent(hubitat.zwave.commands.sensorbinaryv2.SensorBinaryReport cmd) {
	motionEvent(cmd.sensorValue)
}

def zwaveEvent(hubitat.zwave.commands.basicv1.BasicSet cmd) {
	motionEvent(cmd.value)
}

def zwaveEvent(hubitat.zwave.commands.notificationv3.NotificationReport cmd) {
	def result = []
	if (cmd.notificationType == 7) {
		switch (cmd.event) {
			case 0:
           		sendEvent(name: "contact", value: "closed", descriptionText: "$device.displayName is closed", displayed: true)
				result << motionEvent(0)
				//result << createEvent(name: "tamper", value: "clear", displayed: false)
               	result << createEvent(name: "acceleration", value: "inactive", descriptionText: "$device.displayName is inactive", displayed: true)
				break
			case 3:
           		sendEvent(name: "contact", value: "open", descriptionText: "$device.displayName is open", displayed: true)
				//result << createEvent(name: "tamper", value: "detected", descriptionText: "$device.displayName was tampered")
                result << createEvent(name: "acceleration", value: "active", descriptionText: "$device.displayName is active", displayed: true)
				break
			case 7:
				result << motionEvent(1)
				break
		}
	} else {
		log.warn "Need to handle this cmd.notificationType: ${cmd.notificationType}"
		result << createEvent(descriptionText: cmd.toString(), isStateChange: false)
	}
	result
}

def zwaveEvent(hubitat.zwave.commands.configurationv2.ConfigurationReport cmd) {
 log.debug "---CONFIGURATION REPORT V2--- ${device.displayName} parameter ${cmd.parameterNumber} with a byte size of ${cmd.size} is set to ${cmd.configurationValue}"

def result = []
	def value
	if (cmd.parameterNumber == 9 && cmd.configurationValue[0] == 0) {
		value = "USB Cable"
		if (!isConfigured()) {
			log.debug("ConfigurationReport: configuring device")
			result << response(configure())
		}
		result << createEvent(name: "batteryStatus", value: value, displayed: false)
		result << createEvent(name: "powerSupply", value: value, displayed: false)
	}else if (cmd.parameterNumber == 9 && cmd.configurationValue[0] == 1) {
		value = "Battery"
		result << createEvent(name: "powerSupply", value: value, displayed: false)
	} else if (cmd.parameterNumber == 101){
		result << response(configure())
	}
	result
}

def zwaveEvent(hubitat.zwave.Command cmd) {
	log.debug "General zwaveEvent cmd: ${cmd}"
	createEvent(descriptionText: cmd.toString(), isStateChange: false)
}

def configure() {
	// This sensor joins as a secure device if you double-click the button to include it
	log.debug "${device.displayName} is configuring its settings"

if (settings.motionDelayTime == null)
  settings.motionDelayTime = 60
if (settings.reportInterval == null)
  settings.reportInterval = 8*60
if (settings.motionSensitivity == null)
  settings.motionSensitivity = 3
if (settings.TempOffset == null)
  settings.TempOffset = 0
if (settings.HumidOffset == null)
  settings.HumidOffset = 0
if (settings.tzOffset == null)
  settings.tzOffset = -5
if (settings.tempScale == null)
  settings.tempScale = "F"
if (settings.TempChangeAmount == null)
  settings.TempChangeAmount = 2
  
  if (settings.motionSensitivity < 0)
    {
      log.debug "Illegal motion sensitivity ... resetting to 0!"
      settings.motionSensitivity = 0
    }
    
   if (settings.motionSensitivity > 5)
    {
      log.debug "Illegal motion sensitivity ... resetting to 5!"
      settings.motionSensitivity = 5
    }
    
   // fix tz offset
 if (settings.tzOffset < -12)
  {
    settings.tzOffset = -12
    log.debug "Timezone too low... resetting to -12"
    }
    
 if (settings.tzOffset > 14)
  {
    settings.tzOffset = 14
    log.debug "Timezone too high ... resetting to 14"
    }
    
     // fix temp offset
 if (settings.TempOffset < -10)
  {
    settings.TempOffset = -10
    log.debug "Temperature Offset too low... resetting to -10"
    }
    
 if (settings.TempOffset > 10)
  {
    settings.TempOffset = 10
    log.debug "Temperature Adjusment too high ... resetting to 10"
    }

     // fix temp offset
 if (settings.HumidOffset < -10)
  {
    settings.HumidOffset = -10
    log.debug "Humidity Offset too low... resetting to -10"
    }
    
 if (settings.HumidOffset > 10)
  {
    settings.HumidOffset = 10
    log.debug "Humidity Adjusment too high ... resetting to 10"
    }

  
log.debug "In configure report interval value = $reportInterval"
log.debug "Motion Delay Time = $motionDelayTime"
log.debug "Motion Sensitivity = $motionSensitivity"
log.debug "Temperature adjust = $TempOffset"
log.debug "Humidity adjust = $HumidOffset"
log.debug "Tz Offset = $settings.tzOffset"
log.debug "temp scale = $tempScale"
log.debug "min temp change for reporting = $TempChangeAmount"

def now = new Date()
def tf = new java.text.SimpleDateFormat("MM/dd/yyyy h:mm a")
tf.setTimeZone(TimeZone.getTimeZone("GMT${settings.tzOffset}"))
def newtime = "${tf.format(now)}" as String   
sendEvent(name: "lastUpdate", value: newtime, descriptionText: "Configured: $newtime")

setConfigured("true")      
def waketime

if (timeOptionValueMap[reportInterval] < 300)
  waketime = timeOptionValueMap[reportInterval] 
 else waketime = 300
 
 log.debug "wake time reset to $waketime"

def request = [
// set wakeup interval to report time otherwise it doesnt report in time

		zwave.wakeUpV1.wakeUpIntervalSet(seconds:waketime, nodeid:zwaveHubNodeId),
		
        zwave.versionV1.versionGet(),
        zwave.firmwareUpdateMdV2.firmwareMdGet(),
 
	//1. set association groups for hub
	zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId),
	zwave.associationV1.associationSet(groupingIdentifier:2, nodeId:zwaveHubNodeId),

	//2. automatic report flags
    // lgk change ultraviolet is 16 not 2 
	// param 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
	zwave.configurationV1.configurationSet(parameterNumber: 101, size: 4, scaledConfigurationValue: 241), //association group 1
	zwave.configurationV1.configurationSet(parameterNumber: 102, size: 4, scaledConfigurationValue: 1), //association group 2

	//3. no-motion report x seconds after motion stops (default 60 secs)
	zwave.configurationV1.configurationSet(parameterNumber: 3, size: 2, scaledConfigurationValue: timeOptionValueMap[motionDelayTime] ?: 60),

	//4. afSensitivity 3 levels: 64-normal (default), 127-maximum, 0-minimum
    zwave.configurationV1.configurationSet(parameterNumber: 6, size: 1, scaledConfigurationValue: motionSensitivity),
     
	//5. report every x minutes (threshold reports don't work on battery power, default 8 mins)
 	zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue:  timeOptionValueMap[reportInterval]), //association group 1
    zwave.configurationV1.configurationGet(parameterNumber: 0x6F),
    	        
    // battery report time.. too long at  every 6 hours change to 2 hours.
	zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: 2*60*60),  //association group 2

	//6. report automatically on threshold change
	zwave.configurationV1.configurationSet(parameterNumber: 40, size: 1, scaledConfigurationValue: 1),

	// min change in temp to report
    zwave.configurationV1.configurationSet(parameterNumber: 41, size: 1, scaledConfigurationValue: TempChangeAmount),


	// send binary sensor report for motion
		zwave.configurationV1.configurationSet(parameterNumber: 0x05, size: 1, scaledConfigurationValue: 2),
	// Enable the function of touch sensor
        zwave.configurationV1.configurationSet(parameterNumber: 0x07, size: 1, scaledConfigurationValue: 1),
	
     // configure temp offset
     // these are done directly in the reporting
		//zwave.configurationV1.configurationSet(parameterNumber: 0xC9, size: 1, scaledConfigurationValue: 1),
		//zwave.configurationV1.configurationGet(parameterNumber: 0xC9),
		
     // configure humidity offset
	//  zwave.configurationV1.configurationSet(parameterNumber: 0xCA, size: 1, scaledConfigurationValue: 01),
	//  zwave.configurationV1.configurationGet(parameterNumber: 0xCA),
	
	//7. query sensor data
    zwave.batteryV1.batteryGet(),
	zwave.sensorBinaryV2.sensorBinaryGet(),
	zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1), //temperature
	zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 3), //illuminance
	zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 5), //humidity
	zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 27) //ultravioletIndex

  ]
	commands(request) + ["delay 20000", zwave.wakeUpV1.wakeUpNoMoreInformation().format()]

}

def refresh() {
// refresh not working for now.
	// This sensor joins as a secure device if you double-click the button to include it
	log.debug "in refresh"

 	delayBetween([
		zwave.versionV1.versionGet(),
        zwave.firmwareUpdateMdV2.firmwareMdGet(),
        zwave.configurationV1.configurationGet(parameterNumber: 6),
        zwave.batteryV1.batteryGet().format(),
        zwave.sensorBinaryV2.sensorBinaryGet(), //motion
		zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1), //temperature
	    zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 3), //illuminance
		zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 5), //humidity
		zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 27) //ultravioletIndex
 	], 1000)
}

private def getTimeOptionValueMap() { [
		"20 seconds" : 20,
		"30 seconds" : 30,
		"1 minute"   : 60,
		"2 minutes"  : 2*60,
		"3 minutes"  : 3*60,
		"4 minutes"  : 4*60,
		"5 minutes"  : 5*60,
		"10 minutes" : 10*60,
		"15 minutes" : 15*60,
		"30 minutes" : 30*60,
		"1 hours"    : 1*60*60,
		"6 hours"    : 6*60*60,
		"12 hours"   : 12*60*60,
		"18 hours"   : 18*60*60,
		"24 hours"   : 24*60*60,
]}

private setConfigured(configure) {
	updateDataValue("configured", configure)
}

private isConfigured() {
	getDataValue("configured") == "true"
}

private command(hubitat.zwave.Command cmd) {
	if (state.sec) {
		zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
//	} else {
//    		logging("Unknown Z-Wave Command: ${cmd.toString()}")
//		cmd.format()
	}
}

private commands(commands, delay=1000) {
	log.info "sending commands: ${commands}"
	delayBetween(commands.collect{ command(it) }, delay)
}


def zwaveEvent(hubitat.zwave.commands.versionv1.VersionCommandClassReport cmd) {
   log.debug "in version class report"
	//if (state.debug) 
    log.debug "---COMMAND CLASS VERSION REPORT V1--- ${device.displayName} has command class version: ${cmd.commandClassVersion} - payload: ${cmd.payload}"
}

def zwaveEvent(hubitat.zwave.commands.versionv1.VersionReport cmd) {
    log.debug "in version report"
	def fw = "${cmd.applicationVersion}.${cmd.applicationSubVersion}"
	updateDataValue("fw", fw)
	//if (state.debug) 
    log.debug "---VERSION REPORT V1--- ${device.displayName} is running firmware version: $fw, Z-Wave version: ${cmd.zWaveProtocolVersion}.${cmd.zWaveProtocolSubVersion}"
}


def zwaveEvent(hubitat.zwave.commands.configurationv1.ConfigurationReport cmd) {
    //if (state.debug) 
    log.debug "---CONFIGURATION REPORT V1--- ${device.displayName} parameter ${cmd.parameterNumber} with a byte size of ${cmd.size} is set to ${cmd.configurationValue}"
}
3 Likes

@csteele Try pasting the code and then select the entire code, click on the </> button once followed by the one that looks like quotes " (to its left).
This should make the whole code come out in a panel of its own, with the pre-formatting respected in the post.

1 Like

Yes, much better. Thanks for taking the time.

Thanks for posting your updated version.

I found one minor problem with the code - it doesn't handle the timezone offset very well.

Issues include:

  • It would not properly handle a positive offset - e.g. here in Melbourne, Australia we're (normally) at UTC+10:00. Unfortunately the code only worked with negative offsets - anything else ended up as GMT (due to a decimal, formatted as a string, not including the "+" by default).
  • It would not properly handle timezones with non-integer hour offsets (e.g. Adelaide, in South Australia, is (normally) UTC+9:30).
  • It would not properly handle daylight savings.

For all of the above reasons, and also because it strikes me as odd that I should need to set a timezone for each individual sensor, I've updated the code to instead use the hub's timezone information, which appears to be correctly implemented.

/**
 *  Copyright 2015 SmartThings
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 * LGK V1 of multisensor 6 with customizeable settings , changed some timeouts, also changed tamper to vibration so we can
 * use that as well (based on stock device type and also some changes copied from Robert Vandervoort device type.
 * Changes
   1. changes colors of temp slightly, add colors to humidity, add color to battery level
   2. remove tamper and instead use feature as contact and acceleration ie vibration sensor
   3. slightly change reporting interval times. (also fix issue where 18 hours was wrong time)
   4. add last update time.. sets when configured and whenever temp is reported. 
      This is used so at a glance you see the last update time tile to know the device is still reporting easily without looking
      at the logs.
   5. add a temp and humidity offset -12 to 12 to enable tweaking the settings if they appear off.
   6. added power status tile to display, currently was here but not displayed.
   7. added configure and refresh tiles.
   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)
   9. changed the sensitivity to have more values than min max etc, now is integer 0 - 127. 
   10. fix uv setting which in one release was value of 2 now it is 16.
   11. added icons for temp and humidity
   12. also change the default wakeup time to be the same as the report interval, 
        otherwise when on battery it disregarded the report interval. (only if less than the default 5 minutes).
   13. added a config option for the min change needed to report temp changes and set it in parameter 41.
   14. incresed range and colors for lux values, as when mine is in direct sun outside it goes as high as 1900
   15. support for celsius added. set in input options.
*/

metadata {
	definition (name: "Aeon Multisensor 6 V1", namespace: "lgkapps", author: "lgkapps") {
		capability "Motion Sensor"
		capability "Temperature Measurement"
		capability "Relative Humidity Measurement"
		capability "Illuminance Measurement"
		capability "Ultraviolet Index"
		capability "Configuration"
		capability "Sensor"
		capability "Battery"
   		capability "Acceleration Sensor"
   		capability "Contact Sensor"
//        capability "refresh"

//		attribute "tamper", "enum", ["detected", "clear"]
		attribute "batteryStatus", "string"
		attribute "powerSupply", "enum", ["USB Cable", "Battery"]
      
		fingerprint deviceId: "0x2101", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x7A", outClusters: "0x5A"
	}


	preferences {
		//input description: "Please consult AEOTEC MULTISENSOR 6 operating manual for advanced setting options. You can skip this configuration to use default settings",
		//		title: "Advanced Configuration", displayDuringSetup: true, type: "paragraph", element: "paragraph"
		input "motionDelayTime", "enum", title: "Motion Sensor Delay Time?",
				options: ["20 seconds", "30 seconds", "1 minute", "2 minutes", "3 minutes", "4 minutes"], defaultValue: "1 minute", displayDuringSetup: true
		input "motionSensitivity", "number", title: "Motion Sensor Sensitivity? (0 = off, 1 = min., 3 = normal, 5 = max.)", range: "0..5", defaultValue: 3, displayDuringSetup: true
		input "reportInterval", "enum", title: "Sensors Report Interval?",
				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
 		input("TempOffset", "number", title: "Temperature Offset/Adjustment -10 to +10 in Degrees?",range: "-10..10", description: "If your temperature is innacurate this will offset/adjust it by this many degrees.", defaultValue: 0, required: false, displayDuringSetup: true)
   		input("HumidOffset", "number", title: "Humidity Offset/Adjustment -10 to +10 in percent?",range: "-10..10", description: "If your humidty is innacurate this will offset/adjust it by this percent.", defaultValue: 0, required: false, displayDuringSetup: true)
		input("tempScale", "enum", title: "Temperature Scale?", options: ["F","C"], defaltValue: "F", description: "What is your temperature scale?", displayDuringSetup: true)
     	input("TempChangeAmount", "number", title: "Temperature Change Amount (1 = .1 degree)?", range: "1..70",description: "The tenths of degrees the temperature must changes before a report is sent?", defaultValue: 2,required: false)
     }
}

def updated() {
	log.debug "In Updated with settings: ${settings}"
	log.debug "${device.displayName} is now ${device.latestValue("powerSupply")}"  
    
if (settings.motionDelayTime == null)
  settings.motionDelayTime = 60
if (settings.reportInterval == null)
  settings.reportInterval = 8*60
if (settings.motionSensitivity == null)
  settings.motionSensitivity = 3
if (settings.TempOffset == null)
  settings.TempOffset = 0
if (settings.HumidOffset == null)
  settings.HumidOffset = 0
if (settings.tempScale == null)
  settings.tempScale = "F"
if (settings.tempChangeAmount == null)
  settings.tempChangeAmount = 2
  
  if (settings.motionSensitivity < 0)
    {
      log.debug "Illegal motion sensitivity ... resetting to 0!"
      settings.motionSensitivity = 0
    }
    
   if (settings.motionSensitivity > 5)
    {
      log.debug "Illegal motion sensitivity ... resetting to 5!"
      settings.motionSensitivity = 5
    }
    
     // fix temp offset
 if (settings.TempOffset < -10)
  {
    settings.TempOffset = -10
    log.debug "Temperature Offset too low... resetting to -10"
    }
    
 if (settings.TempOffset > 10)
  {
    settings.TempOffset = 10
    log.debug "Temperature Adjusment too high ... resetting to 10"
    }

     // fix humidity offset
 if (settings.HumidOffset < -10)
  {
    settings.HumidOffset = -10
    log.debug "Humidity Offset too low... resetting to -10"
    }
    
 if (settings.HumidOffset > 10)
  {
    settings.HumidOffset = 10
    log.debug "Humidity Adjusment too high ... resetting to 10"
    }

	if (device.latestValue("powerSupply") == "USB Cable") {  //case1: USB powered
		response(configure())
	} else if (device.latestValue("powerSupply") == "Battery") {  //case2: battery powered
		// setConfigured("false") is used by WakeUpNotification
		setConfigured("false") //wait until the next time device wakeup to send configure command after user change preference
	} else { //case3: power source is not identified, ask user to properly pair the sensor again
		log.warn "power source is not identified, check it sensor is powered by USB, if so > configure()"
		def request = []
		request << zwave.configurationV1.configurationGet(parameterNumber: 101)
		response(commands(request))
	}
    return(configure())
}

def parse(String description) {
	log.debug "parse() >> description: ${description}"
	def result = null
	if (description.startsWith("Err 106")) {
		log.debug "parse() >> Err 106"
		result = createEvent( name: "secureInclusion", value: "failed", isStateChange: true,
				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.")
	} else if (description != "updated") {
		//log.debug "parse3() >> zwave.parse(description)"
		def cmd = zwave.parse(description, [0x31: 5, 0x30: 2, 0x84: 1])
		if (cmd) {
			result = zwaveEvent(cmd)
		}
	}
	//log.debug "After zwaveEvent(cmd) >> Parsed '${description}' to ${result.inspect()}"
	return result
}

//this notification will be sent only when device is battery powered
def zwaveEvent(hubitat.zwave.commands.wakeupv1.WakeUpNotification cmd) {
	def result = [createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)]
	def cmds = []
	if (!isConfigured()) {
		log.debug("late configure")
		result << response(configure())
	} else {
		//log.debug("Device has been configured sending >> wakeUpNoMoreInformation()")
		cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format()
		result << response(cmds)
	}
	result
}

def zwaveEvent(hubitat.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
	def encapsulatedCommand = cmd.encapsulatedCommand([0x31: 5, 0x30: 2, 0x84: 1])
	state.sec = 1
	log.debug "encapsulated: ${encapsulatedCommand}"
	if (encapsulatedCommand) {
		zwaveEvent(encapsulatedCommand)
	} else {
		log.warn "Unable to extract encapsulated cmd from $cmd"
		createEvent(descriptionText: cmd.toString())
	}
}

def zwaveEvent(hubitat.zwave.commands.securityv1.SecurityCommandsSupportedReport cmd) {
	log.info "Executing zwaveEvent 98 (SecurityV1): 03 (SecurityCommandsSupportedReport) with cmd: $cmd"
	state.sec = 1
}

def zwaveEvent(hubitat.zwave.commands.securityv1.NetworkKeyVerify cmd) {
	state.sec = 1
	log.info "Executing zwaveEvent 98 (SecurityV1): 07 (NetworkKeyVerify) with cmd: $cmd (node is securely included)"
	def result = [createEvent(name:"secureInclusion", value:"success", descriptionText:"Secure inclusion was successful", isStateChange: true)]
	result
}

def zwaveEvent(hubitat.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
 log.debug "in manuf specific cmd = $cmd"
 
	log.info "Executing zwaveEvent 72 (ManufacturerSpecificV2) : 05 (ManufacturerSpecificReport) with cmd: $cmd"
	log.debug "manufacturerId:   ${cmd.manufacturerId}"
	log.debug "manufacturerName: ${cmd.manufacturerName}"
	log.debug "productId:        ${cmd.productId}"
	log.debug "productTypeId:    ${cmd.productTypeId}"
	def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
	updateDataValue("MSR", msr)
}

def zwaveEvent(hubitat.zwave.commands.batteryv1.BatteryReport cmd) {
	def result = []
	def map = [ name: "battery", unit: "%" ]
	if (cmd.batteryLevel == 0xFF) {
		map.value = 1
		map.descriptionText = "${device.displayName} battery is low"
		map.isStateChange = true
	} else {
		map.value = cmd.batteryLevel
	}
	state.lastbatt = now()
	result << createEvent(map)
	if (device.latestValue("powerSupply") != "USB Cable"){
		result << createEvent(name: "batteryStatus", value: "${map.value} % battery", displayed: false)
	}
	result
}

def zwaveEvent(hubitat.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd){
log.debug "\nin multi level report cmd = $cmd\n"
 
	def map = [:]
	switch (cmd.sensorType) {
		case 1:
            log.debug "raw temp = $cmd.scaledSensorValue"
            
            if (settings.tzOffset == null)
 				settings.tzOffset = -5
 
            def now = new Date()
            def tf = new java.text.SimpleDateFormat("MM/dd/yyyy h:mm a")
            tf.setTimeZone(location.getTimeZone())
            def newtime = "${tf.format(now)}" as String   
            sendEvent(name: "lastUpdate", value: newtime, descriptionText: "Last Update: $newtime ${tf.getTimeZone()}")
            
            BigDecimal offset = settings.TempOffset
            // no way to change to c in device so do differently
            def startval =convertTemperatureIfNeeded(cmd.scaledSensorValue, cmd.scale == 1 ? "F" : "C", cmd.precision)
            if (settings.tempScale == "C" && cmd.scale == 0)
               startval = cmd.scaledSensorValue
               
            log.debug "scaled scaled sensor value = $cmd.scaledSensorValue scale = $cmd.scale"
            log.debug "offset = $offset"
            log.debug "startval = $startval"
			def thetemp = startval as BigDecimal
            log.debug "the temp = $thetemp"
            offset = offset ?: 0
            def newValue = (Math.round((thetemp * 100) + (offset * 100)) / 100)
            BigDecimal adjval = (thetemp + offset)
            def dispval =  String.format("%5.1f", adjval)
            def finalval = dispval as BigDecimal
            map.value = finalval
            map.unit = getTemperatureScale()
			map.name = "temperature"
			break
		case 3:
            log.debug "raw illuminance = $cmd.scaledSensorValue"
			map.name = "illuminance"
			map.value = cmd.scaledSensorValue.toInteger()
			map.unit = "lux"
			break
		case 5:
            log.debug "raw humidity = $cmd.scaledSensorValue"
            settings.HumidOffset = settings.HumidOffset ?: 0
            map.value = (cmd.scaledSensorValue.toInteger() + settings.HumidOffset)
			map.unit = "%"
			map.name = "humidity"
			break
		case 27:
            log.debug "raw uv index = $cmd.scaledSensorValue"
			map.name = "ultravioletIndex"
			map.value = cmd.scaledSensorValue.toInteger()
			break
		default:
			map.descriptionText = cmd.toString()
	}
	createEvent(map)
}

def motionEvent(value) {
	def map = [name: "motion"]
	if (value) {
        log.debug "motion active"
		map.value = "active"
		map.descriptionText = "$device.displayName detected motion"
	} else {
       log.debug "motion inactive"
		map.value = "inactive"
		map.descriptionText = "$device.displayName motion has stopped"
	}
	createEvent(map)
}

def zwaveEvent(hubitat.zwave.commands.sensorbinaryv2.SensorBinaryReport cmd) {
	motionEvent(cmd.sensorValue)
}

def zwaveEvent(hubitat.zwave.commands.basicv1.BasicSet cmd) {
	motionEvent(cmd.value)
}

def zwaveEvent(hubitat.zwave.commands.notificationv3.NotificationReport cmd) {
	def result = []
	if (cmd.notificationType == 7) {
		switch (cmd.event) {
			case 0:
           		sendEvent(name: "contact", value: "closed", descriptionText: "$device.displayName is closed", displayed: true)
				result << motionEvent(0)
				//result << createEvent(name: "tamper", value: "clear", displayed: false)
               	result << createEvent(name: "acceleration", value: "inactive", descriptionText: "$device.displayName is inactive", displayed: true)
				break
			case 3:
           		sendEvent(name: "contact", value: "open", descriptionText: "$device.displayName is open", displayed: true)
				//result << createEvent(name: "tamper", value: "detected", descriptionText: "$device.displayName was tampered")
                result << createEvent(name: "acceleration", value: "active", descriptionText: "$device.displayName is active", displayed: true)
				break
			case 7:
				result << motionEvent(1)
				break
		}
	} else {
		log.warn "Need to handle this cmd.notificationType: ${cmd.notificationType}"
		result << createEvent(descriptionText: cmd.toString(), isStateChange: false)
	}
	result
}

def zwaveEvent(hubitat.zwave.commands.configurationv2.ConfigurationReport cmd) {
 log.debug "---CONFIGURATION REPORT V2--- ${device.displayName} parameter ${cmd.parameterNumber} with a byte size of ${cmd.size} is set to ${cmd.configurationValue}"

def result = []
	def value
	if (cmd.parameterNumber == 9 && cmd.configurationValue[0] == 0) {
		value = "USB Cable"
		if (!isConfigured()) {
			log.debug("ConfigurationReport: configuring device")
			result << response(configure())
		}
		result << createEvent(name: "batteryStatus", value: value, displayed: false)
		result << createEvent(name: "powerSupply", value: value, displayed: false)
	}else if (cmd.parameterNumber == 9 && cmd.configurationValue[0] == 1) {
		value = "Battery"
		result << createEvent(name: "powerSupply", value: value, displayed: false)
	} else if (cmd.parameterNumber == 101){
		result << response(configure())
	}
	result
}

def zwaveEvent(hubitat.zwave.Command cmd) {
	log.debug "General zwaveEvent cmd: ${cmd}"
	createEvent(descriptionText: cmd.toString(), isStateChange: false)
}

def configure() {
	// This sensor joins as a secure device if you double-click the button to include it
	log.debug "${device.displayName} is configuring its settings"

if (settings.motionDelayTime == null)
  settings.motionDelayTime = 60
if (settings.reportInterval == null)
  settings.reportInterval = 8*60
if (settings.motionSensitivity == null)
  settings.motionSensitivity = 3
if (settings.TempOffset == null)
  settings.TempOffset = 0
if (settings.HumidOffset == null)
  settings.HumidOffset = 0
if (settings.tempScale == null)
  settings.tempScale = "F"
if (settings.TempChangeAmount == null)
  settings.TempChangeAmount = 2
  
  if (settings.motionSensitivity < 0)
    {
      log.debug "Illegal motion sensitivity ... resetting to 0!"
      settings.motionSensitivity = 0
    }
    
   if (settings.motionSensitivity > 5)
    {
      log.debug "Illegal motion sensitivity ... resetting to 5!"
      settings.motionSensitivity = 5
    }
  
     // fix temp offset
 if (settings.TempOffset < -10)
  {
    settings.TempOffset = -10
    log.debug "Temperature Offset too low... resetting to -10"
    }
    
 if (settings.TempOffset > 10)
  {
    settings.TempOffset = 10
    log.debug "Temperature Adjusment too high ... resetting to 10"
    }

     // fix humidity offset
 if (settings.HumidOffset < -10)
  {
    settings.HumidOffset = -10
    log.debug "Humidity Offset too low... resetting to -10"
    }
    
 if (settings.HumidOffset > 10)
  {
    settings.HumidOffset = 10
    log.debug "Humidity Adjusment too high ... resetting to 10"
    }

  
log.debug "In configure report interval value = $reportInterval"
log.debug "Motion Delay Time = $motionDelayTime"
log.debug "Motion Sensitivity = $motionSensitivity"
log.debug "Temperature adjust = $TempOffset"
log.debug "Humidity adjust = $HumidOffset"
log.debug "temp scale = $tempScale"
log.debug "min temp change for reporting = $TempChangeAmount"

def now = new Date()
def tf = new java.text.SimpleDateFormat("MM/dd/yyyy h:mm a")
tf.setTimeZone(location.getTimeZone())
def newtime = "${tf.format(now)}" as String   
sendEvent(name: "lastUpdate", value: newtime, descriptionText: "Configured: $newtime")

setConfigured("true")      
def waketime

if (timeOptionValueMap[reportInterval] < 300)
  waketime = timeOptionValueMap[reportInterval] 
 else waketime = 300
 
 log.debug "wake time reset to $waketime"

def request = [
// set wakeup interval to report time otherwise it doesnt report in time

		zwave.wakeUpV1.wakeUpIntervalSet(seconds:waketime, nodeid:zwaveHubNodeId),
		
        zwave.versionV1.versionGet(),
        zwave.firmwareUpdateMdV2.firmwareMdGet(),
 
	//1. set association groups for hub
	zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId),
	zwave.associationV1.associationSet(groupingIdentifier:2, nodeId:zwaveHubNodeId),

	//2. automatic report flags
    // lgk change ultraviolet is 16 not 2 
	// param 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
	zwave.configurationV1.configurationSet(parameterNumber: 101, size: 4, scaledConfigurationValue: 241), //association group 1
	zwave.configurationV1.configurationSet(parameterNumber: 102, size: 4, scaledConfigurationValue: 1), //association group 2

	//3. no-motion report x seconds after motion stops (default 60 secs)
	zwave.configurationV1.configurationSet(parameterNumber: 3, size: 2, scaledConfigurationValue: timeOptionValueMap[motionDelayTime] ?: 60),

	//4. afSensitivity 3 levels: 64-normal (default), 127-maximum, 0-minimum
    zwave.configurationV1.configurationSet(parameterNumber: 6, size: 1, scaledConfigurationValue: motionSensitivity),
     
	//5. report every x minutes (threshold reports don't work on battery power, default 8 mins)
 	zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue:  timeOptionValueMap[reportInterval]), //association group 1
    zwave.configurationV1.configurationGet(parameterNumber: 0x6F),
    	        
    // battery report time.. too long at  every 6 hours change to 2 hours.
	zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: 2*60*60),  //association group 2

	//6. report automatically on threshold change
	zwave.configurationV1.configurationSet(parameterNumber: 40, size: 1, scaledConfigurationValue: 1),

	// min change in temp to report
    zwave.configurationV1.configurationSet(parameterNumber: 41, size: 1, scaledConfigurationValue: TempChangeAmount),


	// send binary sensor report for motion
		zwave.configurationV1.configurationSet(parameterNumber: 0x05, size: 1, scaledConfigurationValue: 2),
	// Enable the function of touch sensor
        zwave.configurationV1.configurationSet(parameterNumber: 0x07, size: 1, scaledConfigurationValue: 1),
	
     // configure temp offset
     // these are done directly in the reporting
		//zwave.configurationV1.configurationSet(parameterNumber: 0xC9, size: 1, scaledConfigurationValue: 1),
		//zwave.configurationV1.configurationGet(parameterNumber: 0xC9),
		
     // configure humidity offset
	//  zwave.configurationV1.configurationSet(parameterNumber: 0xCA, size: 1, scaledConfigurationValue: 01),
	//  zwave.configurationV1.configurationGet(parameterNumber: 0xCA),
	
	//7. query sensor data
    zwave.batteryV1.batteryGet(),
	zwave.sensorBinaryV2.sensorBinaryGet(),
	zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1), //temperature
	zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 3), //illuminance
	zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 5), //humidity
	zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 27) //ultravioletIndex

  ]
	commands(request) + ["delay 20000", zwave.wakeUpV1.wakeUpNoMoreInformation().format()]

}

def refresh() {
// refresh not working for now.
	// This sensor joins as a secure device if you double-click the button to include it
	log.debug "in refresh"

 	delayBetween([
		zwave.versionV1.versionGet(),
        zwave.firmwareUpdateMdV2.firmwareMdGet(),
        zwave.configurationV1.configurationGet(parameterNumber: 6),
        zwave.batteryV1.batteryGet().format(),
        zwave.sensorBinaryV2.sensorBinaryGet(), //motion
		zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1), //temperature
	    zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 3), //illuminance
		zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 5), //humidity
		zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 27) //ultravioletIndex
 	], 1000)
}

private def getTimeOptionValueMap() { [
		"20 seconds" : 20,
		"30 seconds" : 30,
		"1 minute"   : 60,
		"2 minutes"  : 2*60,
		"3 minutes"  : 3*60,
		"4 minutes"  : 4*60,
		"5 minutes"  : 5*60,
		"10 minutes" : 10*60,
		"15 minutes" : 15*60,
		"30 minutes" : 30*60,
		"1 hours"    : 1*60*60,
		"6 hours"    : 6*60*60,
		"12 hours"   : 12*60*60,
		"18 hours"   : 18*60*60,
		"24 hours"   : 24*60*60,
]}

private setConfigured(configure) {
	updateDataValue("configured", configure)
}

private isConfigured() {
	getDataValue("configured") == "true"
}

private command(hubitat.zwave.Command cmd) {
	if (state.sec) {
		zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
//	} else {
//    		logging("Unknown Z-Wave Command: ${cmd.toString()}")
//		cmd.format()
	}
}

private commands(commands, delay=1000) {
	log.info "sending commands: ${commands}"
	delayBetween(commands.collect{ command(it) }, delay)
}


def zwaveEvent(hubitat.zwave.commands.versionv1.VersionCommandClassReport cmd) {
   log.debug "in version class report"
	//if (state.debug) 
    log.debug "---COMMAND CLASS VERSION REPORT V1--- ${device.displayName} has command class version: ${cmd.commandClassVersion} - payload: ${cmd.payload}"
}

def zwaveEvent(hubitat.zwave.commands.versionv1.VersionReport cmd) {
    log.debug "in version report"
	def fw = "${cmd.applicationVersion}.${cmd.applicationSubVersion}"
	updateDataValue("fw", fw)
	//if (state.debug) 
    log.debug "---VERSION REPORT V1--- ${device.displayName} is running firmware version: $fw, Z-Wave version: ${cmd.zWaveProtocolVersion}.${cmd.zWaveProtocolSubVersion}"
}


def zwaveEvent(hubitat.zwave.commands.configurationv1.ConfigurationReport cmd) {
    //if (state.debug) 
    log.debug "---CONFIGURATION REPORT V1--- ${device.displayName} parameter ${cmd.parameterNumber} with a byte size of ${cmd.size} is set to ${cmd.configurationValue}"
}
2 Likes

I've applied it to one of my MultiSensor's and will enjoy not having to set a TZ. :slight_smile:

Great!
Thanks @csteele and @Chuckles, my issue has been that the illuminance is reported once per hour. And I want it to report once per min..does not matter since it is connected to power via USB :smile:

Edit: Blast....The illumination/temperature etc is still only reported once per hour. :frowning:
Is there a way to have illumination sent every minute or 5 mins? Or was this all only for motion? Thanks!

edit 2 after really looking at the code I see it was only for motion detection. So my question now is...can it be modified to send illumination every 5 mins? I would really appreciate such DH version for it IF technically possible. Thanks again!

Agree, this iteration is battery centric. On battery, the Aeon ignores Report Time values less than 4 mins, and thus option to set values don't exist in this battery focused version.

@ericm has his version ported from ST and it's quite amazing in terms of options. I only have one USB powered MultiSensor6 and it has a busted illuminance. Always returns: illuminance : -32768 :frowning:

I attached Eric's driver to my USB powered MultiSensor6 and I do get reports, but I can't tell if they are correct.

1 Like

I've made some more extensive changes...

  • IMPORTANT NOTE: This version assumes device has firmware version 1.10 or later installed. A warning will be logged if this is not the case.

Key changes:

  • Implement standard "Power Source" capability to track whether USB power ("dc") or battery is in use

    • Remove custom "powerSupply" and "batteryStatus" attributes it replaces.
  • Use hub's temperature scale setting rather than setting each device individually

  • Add event handler for ManufacturerSpecificReport (V1)

  • Corrected configuration parameter number (4) for PIR sensitivity

  • Disabled selective reporting on threshold

  • Numerous minor bug fixes (e.g. motionDelayTime and reportInterval should always hold enumerated values, not number of seconds)

  • Numerous small changes for internationalisation (e.g. region agnostic date formats)

I have one of my test sensors happily reporting on a one minute interval (USB powered, of course; this reporting rate is not supported when the device is running on batteries).

Other changes and fixes are detailed in the comments within the code.

/**
 *  Copyright 2015 SmartThings
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 * LGK V1 of multisensor 6 with customizeable settings , changed some timeouts, also changed tamper to vibration so we can
 * use that as well (based on stock device type and also some changes copied from Robert Vandervoort device type.
 * Changes
 1. changes colors of temp slightly, add colors to humidity, add color to battery level
 2. remove tamper and instead use feature as contact and acceleration ie vibration sensor
 3. slightly change reporting interval times. (also fix issue where 18 hours was wrong time)
 4. add last update time.. sets when configured and whenever temp is reported.
 This is used so at a glance you see the last update time tile to know the device is still reporting easily without looking
 at the logs.
 5. add a temp and humidity offset -12 to 12 to enable tweaking the settings if they appear off.
 6. added power status tile to display, currently was here but not displayed.
 7. added configure and refresh tiles.
 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)
 9. changed the sensitivity to have more values than min max etc, now is integer 0 - 127.
 10. fix uv setting which in one release was value of 2 now it is 16.
 11. added icons for temp and humidity
 12. also change the default wakeup time to be the same as the report interval,
 otherwise when on battery it disregarded the report interval. (only if less than the default 5 minutes).
 13. added a config option for the min change needed to report temp changes and set it in parameter 41.
 14. increased range and colors for lux values, as when mine is in direct sun outside it goes as high as 1900
 15. support for celsius added. set in input options.

 * Chuckles V1 of multisensor 6
 * IMPORTANT NOTE: Assumes device has firmware version 1.10 or later. A warning will be logged if this is not the case.
 * Changes:
  1. Implement standard "Power Source" capability to track whether USB power ("dc") or battery is in use
        - Remove custom "powerSupply" and "batteryStatus" attributes it replaces.
  2. Use hub's timezone rather than setting a timezone for each device individually
  3. Use hub's temperature scale setting rather than setting each device individually
  4. Add event handler for ManufacturerSpecificReport (V1)
  5. Corrected configuration parameter number (4) for PIR sensitivity
  6. Corrected getTimeOptionValueMap
  7. Disabled selective reporting on threshold
  8. Device only supports V1 of COMMAND_CLASS_SENSOR_BINARY (0x30) - not V2
  9. Device only supports V1 of COMMAND_CLASS_CONFIGURATION (0x70) - not V2
 10. Motion detection in NotificationReportV3 is event 8, not event 7
 11. Numerous minor bug fixes (e.g. motionDelayTime and reportInterval should always hold enumerated values, not number of seconds)
 12. Numerous small changes for internationalisation (e.g. region agnostic date formats)

 */


metadata {
    definition (name: "Aeon Multisensor 6 V1", namespace: "chuckles", author: "Chuckles") {
        capability "Motion Sensor"
        capability "Temperature Measurement"
        capability "Relative Humidity Measurement"
        capability "Illuminance Measurement"
        capability "Ultraviolet Index"
        capability "Configuration"
        capability "Sensor"
        capability "Battery"
        capability "Power Source"
        capability "Acceleration Sensor"
        capability "Contact Sensor"
//        capability "refresh"

        command    "refresh"

        attribute  "firmware", "decimal"

//		attribute "tamper", "enum", ["detected", "clear"]
//        attribute "batteryStatus", "string"
//        attribute "powerSupply", "enum", ["USB Cable", "Battery"]

        fingerprint deviceId: "0x2101", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x7A", outClusters: "0x5A"
    }

    preferences {

        // Note: Hubitat doesn't appear to honour sections in device handler preferences just now, but hopefully one day...
        section("Motion sensor settings") {
            input "motionDelayTime", "enum", title: "Motion Sensor Delay Time?",
                    options: ["20 seconds", "30 seconds", "1 minute", "2 minutes", "3 minutes", "4 minutes"], defaultValue: "1 minute", displayDuringSetup: true
            input "motionSensitivity", "number", title: "Motion Sensor Sensitivity? 0(min)..5(max)", range: "0..5", defaultValue: 5, displayDuringSetup: true
        }

        section("Automatic report settings") {
            input "reportInterval", "enum", title: "Sensors Report Interval?",
                    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
            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
            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
            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
        }

        section("Calibration settings") {
            input "tempOffset", "number", title: "Temperature Offset -128 to +127 (Tenths of a degree)?", range: "-127..128", description: "If your temperature is inaccurate this will offset/adjust it by this many tenths of a degree.", defaultValue: 0, required: false, displayDuringSetup: true
            input "humidOffset", "number", title: "Humidity Offset/Adjustment -50 to +50 in percent?", range: "-10..10", description: "If your humidity is inaccurate this will offset/adjust it by this percent.", defaultValue: 0, required: false, displayDuringSetup: true
            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
        }
    }
}

def updated() {
    log.debug "In Updated with settings: ${settings}"
    log.debug "${device.displayName} is now on ${device.latestValue("powerSource")} power"

    // Check for any null settings and change them to default values
    if (motionDelayTime == null)
        motionDelayTime = "1 minute"
    if (motionSensitivity == null)
        motionSensitivity = 3
    if (reportInterval == null)
        reportInterval = "5 minutes"
    if (tempChangeAmount == null)
        tempChangeAmount = 2
    if (tempOffset == null)
        tempOffset = 0
    if (humidOffset == null)
        humidOffset = 0
    if (luxOffset == null)
        luxOffset = 0


    if (motionSensitivity < 0)
    {
        log.debug "Illegal motion sensitivity ... resetting to 0!"
        motionSensitivity = 0
    }

    if (motionSensitivity > 5)
    {
        log.debug "Illegal motion sensitivity ... resetting to 5!"
        motionSensitivity = 5
    }

    // fix temp offset
    if (tempOffset < -128)
    {
        tempOffset = -128
        log.debug "Temperature Offset too low... resetting to -128 (-12.8 degrees)0"
    }

    if (tempOffset > 127)
    {
        tempOffset = 127
        log.debug "Temperature Offset too high ... resetting to 127 (+12.7 degrees)"
    }

    // fix humidity offset
    if (humidOffset < -50)
    {
        humidOffset = -50
        log.debug "Humidity Offset too low... resetting to -50%"
    }

    if (humidOffset > 50)
    {
        humidOffset = 50
        log.debug "Humidity Adjusment too high ... resetting to +50%"
    }

    if (luxOffset < -1000)
    {
        luxOffset = -1000
        log.debug "Luminance Offset too low ... resetting to -1000LUX"
    }

    if (luxOffset > 1000)
    {
        luxOffset = 1000
        log.debug "Luminance Offset too high ... resetting to +1000LUX"
    }


    if (device.latestValue("powerSource") == "dc") {  //case1: USB powered
        response(configure())
    } else if (device.latestValue("powerSource") == "battery") {  //case2: battery powered
        // setConfigured("false") is used by WakeUpNotification
        setConfigured("false") //wait until the next time device wakeup to send configure command after user change preference
    } else { //case3: power source is not identified, ask user to properly pair the sensor again
        log.warn "power source is not identified, check it sensor is powered by USB, if so > configure()"
        def request = []
        request << zwave.configurationV1.configurationGet(parameterNumber: 101)
        response(commands(request))
    }
    return(configure())
}

def parse(String description) {
    // log.debug "In parse() for description: $description"
    def result = null
    if (description.startsWith("Err 106")) {
        log.debug "parse() >> Err 106"
        result = createEvent( name: "secureInclusion", value: "failed", isStateChange: true,
                descriptionText: "This sensor 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.")
    } else if (description != "updated") {
        // log.debug "About to zwave.parse($description)"
        def cmd = zwave.parse(description, [0x31: 5, 0x30: 1, 0x70: 1, 0x72: 1, 0x84: 1])
        if (cmd) {
            // log.debug "About to call handler for ${cmd.toString()}"
            result = zwaveEvent(cmd)
        }
    }
    //log.debug "After zwaveEvent(cmd) >> Parsed '${description}' to ${result.inspect()}"
    return result
}

//this notification will be sent only when device is battery powered
def zwaveEvent(hubitat.zwave.commands.wakeupv1.WakeUpNotification cmd) {
    def result = [createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)]
    def cmds = []
    if (!isConfigured()) {
        log.debug("late configure")
        result << response(configure())
    } else {
        //log.debug("Device has been configured sending >> wakeUpNoMoreInformation()")
        cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format()
        result << response(cmds)
    }
    result
}

def zwaveEvent(hubitat.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
    def encapsulatedCommand = cmd.encapsulatedCommand([0x31: 5, 0x30: 1, 0x70: 1, 0x72: 1, 0x84: 1])
    state.sec = 1
    log.debug "encapsulated: ${encapsulatedCommand}"
    if (encapsulatedCommand) {
        zwaveEvent(encapsulatedCommand)
    } else {
        log.warn "Unable to extract encapsulated cmd from $cmd"
        createEvent(descriptionText: cmd.toString())
    }
}

def zwaveEvent(hubitat.zwave.commands.securityv1.SecurityCommandsSupportedReport cmd) {
    log.info "Executing zwaveEvent 98 (SecurityV1): 03 (SecurityCommandsSupportedReport) with cmd: $cmd"
    state.sec = 1
}

def zwaveEvent(hubitat.zwave.commands.securityv1.NetworkKeyVerify cmd) {
    state.sec = 1
    log.info "Executing zwaveEvent 98 (SecurityV1): 07 (NetworkKeyVerify) with cmd: $cmd (node is securely included)"
    def result = [createEvent(name:"secureInclusion", value:"success", descriptionText:"Secure inclusion was successful", isStateChange: true)]
    result
}

def zwaveEvent(hubitat.zwave.commands.manufacturerspecificv1.ManufacturerSpecificReport cmd) {
    log.debug "ManufacturerSpecificReport cmd = $cmd"

    log.debug "manufacturerId:   ${cmd.manufacturerId}"
    log.debug "manufacturerName: ${cmd.manufacturerName}"
    log.debug "productId:        ${cmd.productId}"
    def model = ""   // We'll decode the specific model for the log, but we don't currently use this info
    switch(cmd.productTypeId >> 8) {
        case 0: model = "EU"
                break
        case 1: model = "US"
                break
        case 2: model = "AU"
                break
        case 10: model = "JP"
                break
        case 29: model = "CN"
                break
        default: model = "unknown"
    }
    log.debug "model:            ${model}"
    log.debug "productTypeId:    ${cmd.productTypeId}"
    def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
    updateDataValue("MSR", msr)
}

def zwaveEvent(hubitat.zwave.commands.batteryv1.BatteryReport cmd) {
    log.debug "In BatteryReport"
    def result = []
    def map = [ name: "battery", unit: "%" ]
    if (cmd.batteryLevel == 0xFF) {
        map.value = 1
        map.descriptionText = "${device.displayName} battery is low"
        map.isStateChange = true
    } else {
        map.value = cmd.batteryLevel
    }
//    state.lastbatt = now()

    createEvent(map)

    //result << createEvent(map)
    //   if (device.latestValue("powerSource") != "dc"){
    //       result << createEvent(name: "batteryStatus", value: "${map.value} % battery", displayed: false)
    //   }
    //result
}

def zwaveEvent(hubitat.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd){
    log.debug "In multi level report cmd = $cmd"

    def map = [:]
    switch (cmd.sensorType) {
        case 1:
            log.debug "raw temp = $cmd.scaledSensorValue"

            def now = new Date()
            def tf = new java.text.SimpleDateFormat("dd-MMM-yyyy h:mm a")
            tf.setTimeZone(location.getTimeZone())
            def newtime = "${tf.format(now)}" as String
            sendEvent(name: "lastUpdate", value: newtime, descriptionText: "Last Update: $newtime ${tf.getTimeZone()}")

            log.debug "scaled sensor value = $cmd.scaledSensorValue  scale = $cmd.scale  precision = $cmd.precision"

            // Convert temperature (if needed) to the system's configured temperature scale
            def finalval = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmd.scale == 1 ? "F" : "C", cmd.precision)

            log.debug "finalval = $finalval"

            map.value = finalval
            map.unit = getTemperatureScale()
            map.name = "temperature"
            break
        case 3:
            log.debug "raw illuminance = $cmd.scaledSensorValue"
            map.name = "illuminance"
            map.value = cmd.scaledSensorValue.toInteger()
            map.unit = "lux"
            break
        case 5:
            log.debug "raw humidity = $cmd.scaledSensorValue"
            map.value = cmd.scaledSensorValue.toInteger()
            map.unit = "%"
            map.name = "humidity"
            break
        case 27:
            log.debug "raw uv index = $cmd.scaledSensorValue"
            map.name = "ultravioletIndex"
            map.value = cmd.scaledSensorValue.toInteger()
            break
        default:
            map.descriptionText = cmd.toString()
    }
    createEvent(map)
}

def motionEvent(value) {
    def map = [name: "motion"]
    if (value) {
        log.debug "motion active"
        map.value = "active"
        map.descriptionText = "$device.displayName detected motion"
    } else {
        log.debug "motion inactive"
        map.value = "inactive"
        map.descriptionText = "$device.displayName motion has stopped"
    }
    createEvent(map)
}

def zwaveEvent(hubitat.zwave.commands.sensorbinaryv1.SensorBinaryReport cmd) {
    motionEvent(cmd.sensorValue)
}

def zwaveEvent(hubitat.zwave.commands.basicv1.BasicSet cmd) {
    // Sensor sends value 0xFF on motion, 0x00 on no motion (after expiry interval)
    motionEvent(cmd.value)
}

def zwaveEvent(hubitat.zwave.commands.notificationv3.NotificationReport cmd) {
    def result = []
    if (cmd.notificationType == 7) {
        switch (cmd.event) {
            case 0:
                sendEvent(name: "contact", value: "closed", descriptionText: "$device.displayName is closed", displayed: true)
                result << motionEvent(0)
                //result << createEvent(name: "tamper", value: "clear", displayed: false)
                result << createEvent(name: "acceleration", value: "inactive", descriptionText: "$device.displayName is inactive", displayed: true)
                break
            case 3:
                sendEvent(name: "contact", value: "open", descriptionText: "$device.displayName is open", displayed: true)
                //result << createEvent(name: "tamper", value: "detected", descriptionText: "$device.displayName was tampered")
                result << createEvent(name: "acceleration", value: "active", descriptionText: "$device.displayName is active", displayed: true)
                break
            case 8:
                result << motionEvent(1)
                break
        }
    } else {
        log.warn "Need to handle this cmd.notificationType: ${cmd.notificationType}"
        result << createEvent(descriptionText: cmd.toString(), isStateChange: false)
    }
    result
}

def zwaveEvent(hubitat.zwave.commands.configurationv1.ConfigurationReport cmd) {
    log.debug "---CONFIGURATION REPORT V1--- ${device.displayName} parameter ${cmd.parameterNumber} with a byte size of ${cmd.size} is set to ${cmd.configurationValue}"

    def result = []
    def value
    if (cmd.parameterNumber == 9 && cmd.configurationValue[0] == 0) {
        value = "dc"
        if (!isConfigured()) {
            log.debug("ConfigurationReport: configuring device")
            result << response(configure())
        }
//        result << createEvent(name: "batteryStatus", value: value, displayed: false)
        result << createEvent(name: "powerSource", value: value, displayed: false)
    }else if (cmd.parameterNumber == 9 && cmd.configurationValue[0] == 1) {
        value = "battery"
        result << createEvent(name: "powerSource", value: value, displayed: false)
    } else if (cmd.parameterNumber == 101){
        result << response(configure())
    }
    result
}

def zwaveEvent(hubitat.zwave.Command cmd) {
    log.debug "General zwaveEvent cmd: ${cmd}"
    createEvent(descriptionText: cmd.toString(), isStateChange: false)
}

def configure() {
    // This sensor joins as a secure device if you double-click the button to include it
    log.debug "${device.displayName} is configuring its settings"

    if (motionDelayTime == null)
        motionDelayTime = "1 minute"
    if (reportInterval == null)
        reportInterval = "5 minutes"
    if (motionSensitivity == null)
        motionSensitivity = 3
    if (tempOffset == null)
        tempOffset = 0
    if (humidOffset == null)
        humidOffset = 0
//    if (tempScale == null)
//        tempScale = "F"
    if (tempChangeAmount == null)
        tempChangeAmount = 2

    if (motionSensitivity < 0) {
        log.debug "Motion sensitivity too low ... resetting to 0"
        motionSensitivity = 0
    } else if (motionSensitivity > 5) {
        log.debug "Motion sensitivity too high ... resetting to 5"
        motionSensitivity = 5
    }

    // fix temp offset
    if (tempOffset < -10) {
        tempOffset = -10
        log.debug "Temperature calibration too low... resetting to -10"
    } else if (tempOffset > 10) {
        tempOffset = 10
        log.debug "Temperature calibration too high ... resetting to 10"
    }

    // fix humidity offset
    if (humidOffset < -50) {
        humidOffset = -50
        log.debug "Humidity calibration too low... resetting to -50"
    } else if (humidOffset > 50) {
        humidOffset = 50
        log.debug "Humidity calibration too high ... resetting to 50"
    }

    if (luxOffset < -1000) {
        luxOffset = -1000
        log.debug "Luminance calibration too low ... resetting to -1000"
    } else if (luxOffset > 1000) {
        luxOffset = 1000
        log.debug "Luminance calibration too high ... resetting to 1000"
    }

    log.debug "In configure: Report Interval = $settings.reportInterval"
    log.debug "Motion Delay Time = $settings.motionDelayTime"
    log.debug "Motion Sensitivity = $settings.motionSensitivity"
    log.debug "Temperature adjust = $settings.TempOffset"
    log.debug "Humidity adjust = $settings.HumidOffset"
    log.debug "Temp Scale = $settings.tempScale"
    log.debug "Min Temp change for reporting = $settings.TempChangeAmount"

    def now = new Date()
    def tf = new java.text.SimpleDateFormat("dd-MMM-yyyy h:mm a")
    tf.setTimeZone(location.getTimeZone())
    def newtime = "${tf.format(now)}" as String
    sendEvent(name: "lastUpdate", value: newtime, descriptionText: "Configured: $newtime")

    setConfigured("true")
    def waketime

    if (timeOptionValueMap[settings.reportInterval] < 300)
        waketime = timeOptionValueMap[settings.reportInterval]
    else waketime = 300

    log.debug "wake time reset to $waketime"

    log.debug "Current firmware: $device.currentFirmware"

    // Retrieve local temperature scale: "C" = Celsius, "F" = Fahrenheit
    // Convert to a value of 1 or 2 as used by the device to select the scale
    log.debug "Location temperature scale: ${location.getTemperatureScale()}"
    byte tempScaleByte = (location.getTemperatureScale() == "C" ? 1 : 2)

    def request = [
            // set wakeup interval to report time otherwise it doesnt report in time
            zwave.wakeUpV1.wakeUpIntervalSet(seconds:waketime, nodeid:zwaveHubNodeId),

            zwave.versionV1.versionGet(),
            zwave.manufacturerSpecificV1.manufacturerSpecificGet(),

            // Hubitat have not yet implemented the firmwareUpdateMdV2 class
            //zwave.firmwareUpdateMdV2.firmwareMdGet(),

            //1. set association groups for hub
            zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId),
            zwave.associationV1.associationSet(groupingIdentifier:2, nodeId:zwaveHubNodeId),

            //2. automatic report flags
            // 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
            zwave.configurationV1.configurationSet(parameterNumber: 101, size: 4, scaledConfigurationValue: 241),   //association group 1 - all reports
            zwave.configurationV1.configurationSet(parameterNumber: 102, size: 4, scaledConfigurationValue: 1),     //association group 2 - battery report

            //3. no-motion report x seconds after motion stops (default 60 secs)
            zwave.configurationV1.configurationSet(parameterNumber: 3, size: 2, scaledConfigurationValue: timeOptionValueMap[motionDelayTime] ?: 60),

            //4. motion sensitivity: 0 (least sensitive) - 5 (most sensitive)
            zwave.configurationV1.configurationSet(parameterNumber: 4, size: 1, scaledConfigurationValue: motionSensitivity),

            //5. report every x minutes (threshold reports don't work on battery power, default 8 mins)
            zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: timeOptionValueMap[reportInterval]), //association group 1

            // battery report time.. too long at  every 6 hours change to 2 hours.
            zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: 2*60*60),  //association group 2

            //6. disable selective reporting only on thresholds
            zwave.configurationV1.configurationSet(parameterNumber: 40, size: 1, scaledConfigurationValue: 0),

            // Set the temperature scale for automatic reports
            // 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
            zwave.configurationV1.configurationSet(parameterNumber: 64, size: 1, configurationValue: [tempScaleByte]),

            // Automatically generate a report when temp changes by specified amount
            zwave.configurationV1.configurationSet(parameterNumber: 41, size: 4, configurationValue: [0, tempChangeAmount, tempScaleByte, 0]),

            // send binary sensor report for motion
            zwave.configurationV1.configurationSet(parameterNumber: 0x05, size: 1, scaledConfigurationValue: 2),

            // Enable the function of touch sensor
            // No such function defined in product's engineering spec
            //zwave.configurationV1.configurationSet(parameterNumber: 0x07, size: 1, scaledConfigurationValue: 1),

            // Set temperature calibration offset
            zwave.configurationV1.configurationSet(parameterNumber: 201, size: 2, configurationValue: [tempOffset, tempScaleByte]),

            // Set humidity calibration offset
            zwave.configurationV1.configurationSet(parameterNumber: 202, size: 1, scaledConfigurationValue: humidOffset),

            // Set luminance calibration offset
            zwave.configurationV1.configurationSet(parameterNumber: 203, size: 2, scaledConfigurationValue: luxOffset),

            //7. query sensor data
            zwave.batteryV1.batteryGet(),
            zwave.sensorBinaryV1.sensorBinaryGet(),
            zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1), //temperature
            zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 3), //illuminance
            zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 5), //humidity
            zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 27) //ultravioletIndex
    ]
    return commands(request) + ["delay 20000", zwave.wakeUpV1.wakeUpNoMoreInformation().format()]
}

def refresh() {
    log.debug "in refresh"

    return commands([
            zwave.versionV1.versionGet(),                                // Retrieve version info (includes firmware version)
//            zwave.firmwareUpdateMdV2.firmwareMdGet(),                  // Command class not implemented by Hubitat yet
            zwave.configurationV1.configurationGet(parameterNumber: 9),  // Retrieve current power mode
            zwave.batteryV1.batteryGet(),
            zwave.sensorBinaryV1.sensorBinaryGet(),                      //motion
            zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 1), //temperature
            zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 3), //illuminance
            zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 5), //humidity
            zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 27) //ultravioletIndex
    ])
}

private def getTimeOptionValueMap() { [
        "20 seconds" : 20,
        "30 seconds" : 30,
        "1 minute"   : 60,
        "2 minutes"  : 2*60,
        "3 minutes"  : 3*60,
        "4 minutes"  : 4*60,
        "5 minutes"  : 5*60,
        "10 minutes" : 10*60,
        "15 minutes" : 15*60,
        "30 minutes" : 30*60,
        "1 hour"     : 1*60*60,
        "6 hours"    : 6*60*60,
        "12 hours"   : 12*60*60,
        "18 hours"   : 18*60*60,
        "24 hours"   : 24*60*60,
]}

private setConfigured(configure) {
    updateDataValue("configured", configure)
}

private isConfigured() {
    getDataValue("configured") == "true"
}

private command(hubitat.zwave.Command cmd) {
    if (state.sec) {
        log.debug "Sending secure Z-wave command: ${cmd.toString()}"
        return zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
    } else {
        log.debug "Sending Z-wave command: ${cmd.toString()}"
        return cmd.format()
    }
}

private commands(commands, delay=1000) {
    //log.info "sending commands: ${commands}"
    return delayBetween(commands.collect{ command(it) }, delay)
}


def zwaveEvent(hubitat.zwave.commands.versionv1.VersionCommandClassReport cmd) {
    log.debug "in version command class report"
    //if (state.debug)
    log.debug "---VERSION COMMAND CLASS REPORT V1--- ${device.displayName} has version: ${cmd.commandClassVersion} for command class ${cmd.requestedCommandClass} - payload: ${cmd.payload}"
}

def zwaveEvent(hubitat.zwave.commands.versionv1.VersionReport cmd) {
    log.debug "in version report"
    // SubVersion is in 1/100ths so that 1.01 < 1.08 < 1.10, etc.
    BigDecimal fw = cmd.applicationVersion + (cmd.applicationSubVersion / 100)
    state.firmware = fw
    log.debug "---VERSION REPORT V1--- ${device.displayName} is running firmware version: ${String.format("%.2f",fw)}, Z-Wave version: ${cmd.zWaveProtocolVersion}.${cmd.zWaveProtocolSubVersion}"
    if(fw < 1.10)
        log.warn "--- WARNING: Device handler expects devices to have firmware 1.10 or later"
}

def zwaveEvent(hubitat.zwave.commands.firmwareupdatemdv2.FirmwareMdReport cmd) {
    // NOTE: This command class is not yet implemented by Hubitat...
    log.debug "---FIRMWARE METADATA REPORT V2 ${device.displayName}   manufacturerId: ${cmd.manufacturerId}   firmwareId: ${cmd.firmwareId}"
}
3 Likes

There's way too many "Aeon Multisensor 6 V1" versions around and few ways to determine which ones are superseded.

Yesterday a proposal to build a HubitatCommunity github was made and this fork might be a good one to migrate into it, I think.

1 Like

Hi,

Just my 2ยข :slight_smile:

In general I find program revisions often lacking in the apps and drivers I've seen. I've not yet written any Hubitat apps or drivers, however I normally use my 3 initials followed by a Version number. Even when I modify someone else's work I will leave their Revision (if it exists) and add my own after that. While I'm on a roll...... a few sentences on what the code is for would also be appreciated.

BTW I have no problem with the github suggestion but even on that platform revision numbers (even with no description) would be helpful.

John

Take a look at:

because I think it's trying to solve the same problem. At least 3 people have worked on that code.

@Chuckles and @csteele,
wow you guys rock and roll :slight_smile:
I will try the version from @Chuckles first and yes indeed @ericm hubitat ported drivers also rock big time...
Awesome work guys..I will try and test. Will keep ye posted.
Thank you!

1 Like

As previously mentioned, a "community" repository has been created. It's not getting a lot of action, but that's not the same as saying it's got no value. :slight_smile:

As there are several reasonably good copies of "AeonMultiSensor6 V1" out there, I took Chuckles version and added in Cobra's Excellent Code version checking, then Renamed it to use Aeotec and pushed it out to Github:

2 Likes

Thank you! This looks like a nice start! I just updated my Multisensor 6 driver, and saved the HubitatCommunity/ to my favorites.

What are people getting reported for the illuminance with this driver? (The GitHub one).

I've seen the values 6, 7 and 8. I was expecting a big range, e.g. 0-100 so not sure if it's working properly?

DriverStatus : Current
DriverVersion : 1.2
acceleration : inactive
battery : 59
contact : closed
humidity : 36
illuminance : 15
motion : inactive
temperature : 83.8
ultravioletIndex : 0

DriverStatus : Current
DriverVersion : 1.2
acceleration : inactive
battery : 100
contact : closed
humidity : 42
illuminance : 225
motion : inactive
temperature : 77.2
ultravioletIndex : 0

DriverStatus : Current
DriverVersion : 1.2
acceleration : inactive
battery : 100
contact : closed
humidity : 64
illuminance : 644
motion : inactive
temperature : 67.6
ultravioletIndex : 0

The first one is inside, 10ft from a north facing window. The second is inside, 15ft from a south facing window. The third one is outdoors, under an eve, facing south at a shaded area at this time of day.

Device Events log from a couple mins ago:
DriverStatus Current DEVICE 2018-09-19 10:44:20.268 AM PDT
DriverVersion 1.2 DEVICE 2018-09-19 10:44:20.129 AM PDT
DriverAuthor cSteele DEVICE 2018-09-19 10:44:20.129 AM PDT
DriverStatus Current DEVICE 2018-09-19 10:44:20.129 AM PDT
illuminance 644 lux DEVICE 2018-09-19 9:59:59.990 AM PDT
humidity 64 % DEVICE 2018-09-19 9:59:59.072 AM PDT
temperature 67.6 F DEVICE 2018-09-19 9:59:58.599 AM PDT

Thanks!

It's now gone to 42, which may be more realistic as it's getting dark here.

I think it just took an hour to refresh (it's on batteries). I had changed it to report every 30 minutes but I'm not sure it's actually doing that, possibly need to put the device into configure mode/wake it.

Thank you, I'll update my sensor.

Just curious regarding the 2015 Smartthings copywrite. What part of the driver code is still from Smartthings?

John

@JohnRob Don't know why I never saw this question before...

I didn't write the code, I didn't assign the copyright. I modified added or contributed to the open source software, but where it came from, what remains is unknown to me, therefore I'm not qualified to remove the notice or reassign it.

I read this, once upon a time, that crystallized my thoughts...

What many projects do is require that contributors assign copyright to a single legal entity or person which then has the power to enforce the copyright without requiring everybody get involved. Keep in mind that although this person or entity then owns the copyright, the code has been released under a license that allows free distribution. Thus the fact that the copyright has been assigned to an individual entity does not make the code any less open.

I've noticed that the driver is verbose in it's logging and I've incorporated the Hubitat way of shutting off debug logs 30 mins after enable.

I have a new version on github. v1.3 [RELEASED]

I have 12 of these Multisensors and a 12x reduction in my logs has to be a good thing :smiley:

1 Like