HomeSeer new sensors HS-MS100+ and HS-LS100+

Is anyone working on Device Handlers (DTH) for these new sensors? After adding them to ST, they are installed as Door/Window open/close sensors, which are obviously not appropriate, and I cannot seem to change the device type in the app.

You should be able to change the device type in the Website IDE > Devices > Edit Device

That said I do not know if there is an appropriate Device Type for you :frowning:

I have the same motion sensor and couldn’t find a working generic device handler. I tried all of the obvious ones available in the ide.

Use daily secure inclusion and then changed the device handler to the generic zwave+ temp/motion sensors and it appears to be working.

Anyone planning on writing a DTH for the LS100+? You can set it as a generic Z-wave water sensor,but then you lose out on the tamper control and temperature monitoring.


has anyone gotten a default DH to work with these?

This DH was provided to me by HomeSeer support for the water leak sensor, it works great! I just wish the sensor worked with a locally executable DH…


  • HS-LS100+

  • Copyright 2018 HomeSeer

  • 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:

  • 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.

  • Version 1.0 1/15/18
    metadata {
    definition (
    name: “HomeSeer Water Sensor”,
    namespace: “HomeSeer”,
    author: ""
    ) {
    capability "Sensor"
    capability “Water Sensor”
    capability "Battery"
    capability "Configuration"
    capability "Refresh"
    capability "Health Check"
    capability “Temperature Measurement”

    attribute “lastCheckin”, “string”

    fingerprint mfr:“000C”, prod:“0201”, model:“000A”

simulator { }

preferences {
input “tempReporting”, “enum”,
title: “Temperature Reporting Interval:”,
defaultValue: tempReportingSetting,
required: false,
displayDuringSetup: false,
options: tempReportingOptions.collect { }

tiles(scale: 2) {
multiAttributeTile(name:“water”, type: “generic”, width: 6, height: 4, canChangeIcon: false){
tileAttribute (“device.water”, key: “PRIMARY_CONTROL”) {
attributeState “dry”,
icon: “st.alarm.water.dry”,
attributeState “wet”,

  valueTile("temperature", "device.temperature", inactiveLabel: false, width: 2, height: 2) {
  	state "temperature", label:'${currentValue}°',
  			[value: 31, color: "#153591"],
  			[value: 44, color: "#1e9cbb"],
  			[value: 59, color: "#90d2a7"],
  			[value: 74, color: "#44b621"],
  			[value: 84, color: "#f1d801"],
  			[value: 95, color: "#d04e00"],
  			[value: 96, color: "#bc2323"]
  standardTile("refresh", "device.refresh", width: 2, height: 2) {
  	state "refresh", label:'Refresh', action: "refresh", icon:"st.secondary.refresh-icon"
  valueTile("battery", "device.battery", decoration: "flat", width: 2, height: 2){
  	state "battery", label:'${currentValue}% battery', unit:""
  main "water"
  details(["water", "temperature", "refresh", "battery"])


// Sets flag so that configuration is updated the next time it wakes up.
def updated() {
// This method always gets called twice when preferences are saved.
if (!isDuplicateCommand(state.lastUpdated, 3000)) {
state.lastUpdated = new Date().time
logTrace “updated()”

  logForceWakeupMessage "The configuration will be updated the next time the device wakes up."
  state.pendingChanges = true


// Initializes the device state when paired and updates the device’s configuration.
def configure() {
logTrace "configure()"
def cmds = []
def refreshAll = (!state.isConfigured || state.pendingRefresh || !settings?.ledEnabled)

if (!state.isConfigured) {
//logTrace “Waiting 1 second because this is the first time being configured”
sendEvent(getEventMap(“water”, “dry”, false))
//cmds << “delay 1000”

configData.sort { it.paramNum }.each {
cmds += updateConfigVal(it.paramNum, it.size, it.value, refreshAll)

if (!cmds) {
state.pendingChanges = false

if (refreshAll || canReportBattery()) {
cmds << batteryGetCmd()

//cmds << wakeUpIntervalSetCmd(checkinIntervalSettingMinutes)

if (cmds) {
logDebug "Sending configuration to device."
return delayBetween(cmds, 1000)
else {
return cmds

private updateConfigVal(paramNum, paramSize, val, refreshAll) {
def result = []
def configVal = state[“configVal${paramNum}”]

if (refreshAll || (configVal != val)) {
result << configSetCmd(paramNum, paramSize, val)
result << configGetCmd(paramNum)
return result

private initializeCheckin() {
// Set the Health Check interval so that it can be skipped once plus 2 minutes.
def checkInterval = ((checkinIntervalSettingMinutes * 2 * 60) + (2 * 60))

sendEvent(name: “checkInterval”, value: checkInterval, displayed: false, data: [protocol: “zwave”, hubHardwareId: device.hub.hardwareID])

// Required for HealthCheck Capability, but doesn’t actually do anything because this device sleeps.
def ping() {
logDebug “ping()”

// Forces the configuration to be resent to the device the next time it wakes up.
def refresh() {
logForceWakeupMessage "The sensor data will be refreshed the next time the device wakes up."
state.pendingRefresh = true

private logForceWakeupMessage(msg) {
logDebug “${msg} You can force the device to wake up immediately by removing and re-inserting the battery.”

// Processes messages received from device.
def parse(String description) {
def result = []

logTrace "parse description: $description"

sendEvent(name: “lastCheckin”, value: convertToLocalTimeString(new Date()), displayed: false, isStateChange: true)

def cmd = zwave.parse(description, commandClassVersions)
if (cmd) {
result += zwaveEvent(cmd)
else {
logDebug “Unable to parse description: $description”
return result

private getCommandClassVersions() {
0x30: 2, // Sensor Binary
0x31: 5, // Sensor Multilevel
0x59: 1, // AssociationGrpInfo
0x5A: 1, // DeviceResetLocally
0x5E: 2, // ZwaveplusInfo
0x70: 1, // Configuration
0x71: 3, // Notification v4
0x72: 2, // ManufacturerSpecific
0x73: 1, // Powerlevel
0x80: 1, // Battery
0x84: 2, // WakeUp
0x85: 2, // Association
0x86: 1 // Version (2)

// Updates devices configuration, requests battery report, and/or creates last checkin event.
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd)
def result = []

if (state.pendingChanges != false) {
result += configure()
else if (state.pendingRefresh || canReportBattery()) {
result << batteryGetCmd()
else {
logTrace “Skipping battery check because it was already checked within the last ${batteryReportingIntervalSetting}.”

//if (result) {
// result << “delay 2000”

result << wakeUpNoMoreInfoCmd()

return sendResponse(result)

private sendResponse(cmds) {
logTrace "sendResponse $cmds"
def actions = []
cmds?.each { cmd ->
actions << new physicalgraph.device.HubAction(cmd)
return []

// Creates the event for the battery level.
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
logTrace "BatteryReport: $cmd"
def val = (cmd.batteryLevel == 0xFF ? 1 : cmd.batteryLevel)
if (val > 100) {
val = 100
state.lastBatteryReport = new Date().time
logDebug “Battery ${val}%”
createEvent(getEventMap(“battery”, val, null, null, “%”))

// Stores the configuration values so that it only updates them when they’ve changed or a refresh was requested.
def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) {
def name = configData.find { it.paramNum == cmd.parameterNumber }?.name
if (name) {
def val = hexToInt(cmd.configurationValue, cmd.size)

  logDebug "${name} = ${val}"

  state."configVal${cmd.parameterNumber}" = val

else {
logDebug “Parameter ${cmd.parameterNumber}: ${cmd.configurationValue}”
state.isConfigured = true
state.pendingRefresh = false
return []

// Creates water events.
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
def result = []
logTrace “NotificationReport: $cmd”

if (cmd.notificationType == 0x05) {
switch (cmd.event) {
case 0:
logDebug “Sensor is Dry”
result << createEvent(getEventMap(“water”, “dry”))
case 2:
logDebug "Sensor is Wet"
result << createEvent(getEventMap(“water”, “wet”))
logDebug “Sensor is ${cmd.event}”
return result

def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
logTrace “SensorMultilevelReport: ${cmd}”

def result = []
if (cmd.sensorType == 1) {
result += handleTemperatureEvent(cmd)
else {
logDebug “Unknown Sensor Type: ${cmd}”
return result

private handleTemperatureEvent(cmd) {
def result = []
def cmdScale = cmd.scale == 1 ? “F” : “C”

def val = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision)

if ("$val".endsWith(".")) {
val = safeToInt("${val}"[0…-2])

result << createEvent(createEventMap(“temperature”, val, null, “Temperature ${val}°${getTemperatureScale()}”, getTemperatureScale()))
return result

private createEventMap(name, value, displayed=null, desc=null, unit=null) {
def eventMap = [
name: name,
value: value,
displayed: (displayed == null ? ("${getAttrVal(name)}" != “${value}”) : displayed),
isStateChange: true
if (unit) {
eventMap.unit = unit
if (desc && eventMap.displayed) {
logDebug desc
eventMap.descriptionText = “${device.displayName} - ${desc}”
else {
logTrace “Creating Event: ${eventMap}”
return eventMap

private getAttrVal(attrName) {
try {
return device?.currentValue("${attrName}")
catch (ex) {
logTrace "$ex"
return null

// Ignoring event because water events are being handled by notification report.
def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv2.SensorBinaryReport cmd) {
// logTrace "SensorBinaryReport: $cmd"
return []

// Logs unexpected events from the device.
def zwaveEvent(physicalgraph.zwave.Command cmd) {
logDebug "Unhandled Command: $cmd"
return []

private getEventMap(name, value, displayed=null, desc=null, unit=null) {
def isStateChange = (device.currentValue(name) != value)
displayed = (displayed == null ? isStateChange : displayed)
def eventMap = [
name: name,
value: value,
displayed: displayed,
isStateChange: isStateChange
if (desc) {
eventMap.descriptionText = desc
if (unit) {
eventMap.unit = unit
logTrace "Creating Event: ${eventMap}"
return eventMap

private wakeUpIntervalSetCmd(minutesVal) {
state.checkinIntervalMinutes = minutesVal
logTrace “wakeUpIntervalSetCmd(${minutesVal})”

return zwave.wakeUpV2.wakeUpIntervalSet(seconds:(minutesVal * 60), nodeid:zwaveHubNodeId).format()

private wakeUpNoMoreInfoCmd() {
return zwave.wakeUpV2.wakeUpNoMoreInformation().format()

private batteryGetCmd() {
return zwave.batteryV1.batteryGet().format()

private configGetCmd(paramNum) {
return zwave.configurationV1.configurationGet(parameterNumber: paramNum).format()

private configSetCmd(paramNum, size, val) {
return zwave.configurationV1.configurationSet(parameterNumber: paramNum, size: size, scaledConfigurationValue: val).format()

private canReportBattery() {
def reportEveryMS = (batteryReportingIntervalSettingMinutes * 60 * 1000)

return (!state.lastBatteryReport || ((new Date().time) - state.lastBatteryReport > reportEveryMS))

// Settings
private getTempReportingSetting() {
return settings?.tempReporting ?: findDefaultOptionName(tempReportingOptions)

// Configuration Parameters
private getConfigData() {
return [
[paramNum: 19, name: “Temperature Reporting Interval”, value: convertOptionSettingToInt(tempReportingOptions, tempReportingSetting), size: 1],

private getTempReportingOptions() {
[name: formatDefaultOptionName(“Disabled”), value: 0],
[name: “1 Minute”, value: 60],
[name: “2 Minutes”, value: 120],
[name: “3 Minutes”, value: 180],
[name: “4 Minutes”, value: 240],
[name: “5 Minutes”, value: 300],
[name: “10 Minutes”, value: 600],
[name: “30 Minutes”, value: 1800],
[name: “1 Hour”, value: 3600],
[name: “2 Hours”, value: 7200],
[name: “4 Hours”, value: 1440],
[name: “8 Hours”, value: 28800]

private convertOptionSettingToInt(options, settingVal) {
return safeToInt(options?.find { “${settingVal}” == }?.value, 0)

private formatDefaultOptionName(val) {
return “${val}${defaultOptionSuffix}”

private findDefaultOptionName(options) {
def option = options?.find {"${defaultOptionSuffix}") }
return option?.name ?: “”

private getDefaultOptionSuffix() {
return " (Default)"

private safeToInt(val, defaultVal=-1) {
return “${val}”?.isInteger() ? “${val}”.toInteger() : defaultVal

private safeToDec(val, defaultVal=-1) {
return “${val}”?.isBigDecimal() ? “${val}”.toBigDecimal() : defaultVal

private hexToInt(hex, size) {
if (size == 2) {
return hex[1] + (hex[0] * 0x100)
else {
return hex[0]

private canCheckin() {
// Only allow the event to be created once per minute.
def lastCheckin = device.currentValue(“lastCheckin”)
return (!lastCheckin || lastCheckin < (new Date().time - 60000))

private convertToLocalTimeString(dt) {
def timeZoneId = location?.timeZone?.ID
if (timeZoneId) {
return dt.format(“MM/dd/yyyy hh:mm:ss a”, TimeZone.getTimeZone(timeZoneId))
else {
return “$dt”

private isDuplicateCommand(lastExecuted, allowedMil) {
!lastExecuted ? false : (lastExecuted + allowedMil > new Date().time)

private logDebug(msg) {
if (settings?.debugOutput || settings?.debugOutput == null) {
log.debug “$msg”

private logTrace(msg) {
//logDebug “$msg”
// log.trace “$msg”

1 Like

I couldn’t find a DTH for the HS-MS100+ motion sensor- and I wanted this to run locally. It’s relatively inexpensive for a zwave+ sensor with attractive format. It installs as a door/window sensor and unfortunately, when I change to the generic motion sensor, it seems remains in “motion” and doesn’t transmit any motion stopped signal.

A work around for this was to use Zwave tweaker. Change the device type to zwave tweaker after downloading and installing this as the device handler:

Set parameter 18 to the length of time you want the sensor to remain in motion before sending a motion stopped signal.

Press the button on the sensor rapidly 4 times to wake up and it will sync with zwave tweaker. Once synchronized you can change back to the generic zwave motion sensor handler and the parameter will now be set.

This appears to execute locally and works very well. This sensor seems to have a great format with a wide sensor angle and a easily adjusted magnetic mounting system that looks better than any other sensor i’ve used (IMO). It only communicates motion - so isn’t useful if you want luminescence, temp or other features that more expensive sensors have, but its great where the job is to just detect motion. Good value.

My thanks to codersauer for this great little tool for zwave devices.

1 Like

These seem to be in the same price ranges as so many other similar sensors.
So a question: is there an inherent performance advantage in these devices that makes it worth getting them and developing a DTH vs, say, just picking up Iris sensors?

Have you tried using this with the water leak sensor?

No. This is the motion sensor. There’s a DTH in the post above that should work with that device - but won’t run locally. Haven’t tried a water sensor and not sure if they run locally - a similar approach should work if the generic handler is one that does run without the cloud.

I try to have as much working locally as possible.

The iris sensors are good and popular. They are zigbee and not zwave, but that doesn’t really matter if you are not using the associate function (which is a good option for direct automation of lights if you have zwave switches) as they do run locally on the hub.

The form factor and the magnetic mount of the homeseer is the best I’ve come across so far (from aeotec 6,zooz 2 and 4, iris and smartthings own multi-sensor - all of which I’ve used) In addition the homeseer can be powered off micro usb and act as zwave repeaters, if that works for the location you want to use them in.

Good to have options.

Has anyone been successful in having the Leak Sensor (HS-LS100+) report temperature readings? I have the DH installed as received from HomeSeer. Been exchanging e-mails with their tech support and they claim temp readings should work but after following their instructions on 4 of the devices, no temps display. the water sensor works just fine.

I’m beginning to think it’s a DH issue or integration with Smrtthings issue.



I have not seen any temperature readings, I’ve also noticed the battery reporting is a bit erratic ranging from 70% to 90% in the same hour. HomeSeer support claimed it should only report battery every 12 hours.

I wish there was a way to make these work with the generic DH so they can be executed locally.

looking at buy one of these but where do you see homeseer can be powered off micro usb?
oh sorry you were referring to the motion sensor not the leak sensor.
looking at the leak sensor DH temperature reading should work not sure how often it polls though.

Homeseer has a DH on their support site now.

Works I’m using it.

1 Like

HS-MS100+ DTH works for me, too. Thanks for the notification.

In the app, it looks the same as the generic Z-Wave Plus Motion/Temp sensor that I was using.

Although the device is advertised as a motion sensor, I am wondering why the certification shows an air temperature sensor. If it really has that feature it would be best to display the temperature so it can be used to trigger events.

David… Are you receiving temperature readings with this DH yet? I too am using this DH but never receive any temp updates.

I’ve traded some messages directly with HomeSeer and they claim there should be temp readings. they even provided some added pairing instructions none of which worked to show temps.


No I’m not getting any temp reported. They also claimed the battery status should update every 24 hrs but my battery % changes almost every hour.

Interesting. My battery levels haven’t changed in weeks. Still at 100% but I do check to be sure the units are alive from time to time and they do trigger.

I think I’ll go back to the Samsung sensor but I don’t like the one and done concept for it if water gets too deep. Not having to replace the unit if it gets wet (within reason) was what drew me to the Homeseer unit as it floats.