HomeSeer Floodlight (HS-FLS100+) Lux setting - app controlled

The FLS100+ sensor currently only has two default states that can be set with automation…Lights on ALL the time or lights motion activated. There is no third state available to switch off the lights entirely unless you kill power to the lights (not possible in my case) or set Lux to 0 (0 - motion doesn’t control lights, 255 - motion always controls lights).

To introduce the 3rd state (always off), it looks like the HomeSeer device handler needs to be modified so that an automation app can actually change the Lux setting for the device (rather then doing it manually). The device handler code doesn’t allow that to happen right now (as far as I can see). The device handler code won’t even allow Lux settings of 0 and 255…it min/max’s it to 30 and 200.

I’m just looking for a 3rd state that I can set through automation where the lights are always off. In the background, that would be done by setting the lux to 0. To resume normal operation again, the lux would need to be set back to the previous value but 30 would work if previous value wasn’t possible.

HomeSeer said they their not interested in modifying the device handler code. I can see what’s wrong but have no idea how to change the device handler code appropriately.
[https://helpdesk.homeseer.com/article/213-how-to-add-hs-fls100-to-smartthings-hub]

Anyone done this, thinking of doing this (based on the above need), or just wants to give it a shot? I’m sure there will be a few appreciative people!

I actually have two of these but haven’t gotten around to installing them yet. Just so I understand, you want it so that even if it is dark out to prevent the lights from coming on, correct? That is what setting it to zero ultimately does it seems.

Regardless I looked at the DTH and the device z–wave guide and it should be pretty trivial to add the ability to change the lux to zero through a custom command. How do you hope to automate this? Assuming you are using webCore I think it will be easy to implement. I don’t think the standard smartapps (smartlighting etc) can make use of the custom command. Let me know and I can probably make it work. Being able to keep them off might actually come in handy for me too because I want to wire them without a switch as well.

Yes, that’s the gist of it.

The motion light is over our deck…when we’re out on the deck after dark, we don’t want to be highly illuminated which currently, is only possible if we stay very still. I could configure a lux setting of zero everytime but just tapping on our Homeseer switch is more my wife’s speed. Even tapping a tile in the FLS100+ control in the smartthings app would be more intuitive then the current method.

Unfortunately I can’t just turn off power to the lights…too many things on a single circuit.

You’re post spurred me to pair one of my sensors and give it a shot. I have had the same issue as you with wanting to hang out in the backyard without the big light coming on. As I suspected it was pretty simple. I am just using a webcore piston to control it (see screenshot). Just use the new setLux() command and set it to zero. You can actually use it to set to whatever you want so you could store what you had it at and revert back to it later etc. Give it a shot and let me know if it works.

/**
 *  HomeSeer HS-FLS100+
 *
 *  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:
 *
 *      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.
 *
 *	Author: HomeSeer
 *	Date: 7/24/2018
 *
 *	Changelog:
 *
 *	1.0	Initial Version
 *
 *
 *
 */
 
metadata {
	definition (name: "FLS100+ Motion Sensor", namespace: "homeseer", author: "support@homeseer.com") {
		capability "Switch"
		capability "Motion Sensor"
		capability "Sensor"
		capability "Polling"
		capability "Refresh"
        capability "Configuration"
        capability "Illuminance Measurement"
        
        command "setLux", ["number"]
        
        fingerprint mfr: "000C", prod: "0201", model: "000B"
}

	simulator {
		status "on":  "command: 2003, payload: FF"
		status "off": "command: 2003, payload: 00"		

		// reply messages
		reply "2001FF,delay 5000,2602": "command: 2603, payload: FF"
		reply "200100,delay 5000,2602": "command: 2603, payload: 00"		
	}

    preferences {            
       input ( "onTime", "number", title: "Press Configuration button after changing preferences\n\nOn Time: Duration (8-720 seconds) [default: 15]", defaultValue: 15,range: "8..720", required: false)
       input ( "luxDisableValue", "number", title: "Lux Value to Disable Sensor: (30-200 lux) [default: 50]", defaultValue: 50, range: "30..200", required: false)       
       input ( "luxReportInterval", "number", title: "Lux Report Interval: (0-1440 minutes) [default 10]", defaultValue: 10, range: "0..1440", required: false)
    }
    
	tiles(scale: 2) {
		multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
			tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
				attributeState "on", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#79b821", nextState:"turningOff"
				attributeState "off", label:'${name}', action:"switch.on", icon:"st.Home.home30", backgroundColor:"#ffffff", nextState:"turningOn"
				attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#79b821", nextState:"turningOff"
				attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.Home.home30", backgroundColor:"#ffffff", nextState:"turningOn"
			}			
            tileAttribute("device.status", key: "SECONDARY_CONTROL") {
                attributeState("default", label:'${currentValue}', unit:"")
            }
		}
        /*
        multiAttributeTile(name:"motion", type: "generic", width: 6, height: 4, canChangeIcon: false){
			tileAttribute ("device.motion", key: "PRIMARY_CONTROL") {
				attributeState "inactive", 
					label:'No Motion', 
					icon:"st.motion.motion.inactive", 
					backgroundColor:"#cccccc"
				attributeState "active", 
					label:'Motion', 
					icon:"st.motion.motion.active", 
					backgroundColor:"#00a0dc"
			}			
		}
        */
		/*
		valueTile("motion", "device.motion", inactiveLabel: false, width: 2, height: 2) {
			state "motion", label:'${currentValue}'	
		}	
        */
        
        standardTile("motion", "device.motion", inactiveLabel: true, decoration: "flat", width: 2, height: 2) {
			state "inactive",  icon: "st.motion.motion.inactive", label: 'No Motion'
			state "active",  icon: "st.motion.motion.active", label: 'Motion'		
		}
        
        
		valueTile("illuminance", "device.illuminance", inactiveLabel: false, width: 2, height: 2) {
			state "illuminance", label:'${currentValue} lux',unit:"lux"
				
		}	
        
		standardTile("refresh", "device.switch", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
			state "default", label:'', action:"refresh.refresh", icon:"st.secondary.configure"
		}
        
        valueTile("firmwareVersion", "device.firmwareVersion", width:2, height: 2, decoration: "flat", inactiveLabel: false) {
			state "default", label: '${currentValue}'
		}       

		main(["switch"])
        
		details(["switch","motion","illuminance","firmwareVersion", "refresh"])
	}
}

def parse(String description) {
	def result = null
    log.debug (description)
    if (description != "updated") {
	    def cmd = zwave.parse(description, [0x20: 1, 0x26: 1, 0x70: 1])	
        if (cmd) {
		    result = zwaveEvent(cmd)
	    }
    }
    if (!result){
        log.debug "Parse returned ${result} for command ${cmd}"
    }
    else {
		log.debug "Parse returned ${result}"
    }   
	return result
}


// Creates motion events.
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
	log.debug "NotificationReport: ${cmd}"
	
	if (cmd.notificationType == 0x07) {
		switch (cmd.event) {
			case 0:
				log.debug "NO MOTION"	
                createEvent(name:"motion", value: "inactive", isStateChange: true)
				break
			case 8:
				log.debug "MOTION"
				createEvent(name:"motion", value: "active", isStateChange: true)
				break
			default:
				logDebug "Sensor is ${cmd.event}"
		}
	}	
}

def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
	//log.debug("sensor multilevel report")
    //log.debug "cmd:  ${cmd}"
    
    def lval = cmd.scaledSensorValue
    
    createEvent(name:"illuminance", value: lval)
}

def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
	log.debug "manufacturerId:   ${cmd.manufacturerId}"
	log.debug "manufacturerName: ${cmd.manufacturerName}"
    state.manufacturer=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)	
    setFirmwareVersion()
    createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
}

def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) {	
    //updateDataValue("applicationVersion", "${cmd.applicationVersion}")
    log.debug ("received Version Report")
    log.debug "applicationVersion:      ${cmd.applicationVersion}"
    log.debug "applicationSubVersion:   ${cmd.applicationSubVersion}"
    state.firmwareVersion=cmd.applicationVersion+'.'+cmd.applicationSubVersion
    log.debug "zWaveLibraryType:        ${cmd.zWaveLibraryType}"
    log.debug "zWaveProtocolVersion:    ${cmd.zWaveProtocolVersion}"
    log.debug "zWaveProtocolSubVersion: ${cmd.zWaveProtocolSubVersion}"
    setFirmwareVersion()
    createEvent([descriptionText: "Firmware V"+state.firmwareVersion, isStateChange: false])
}

def zwaveEvent(physicalgraph.zwave.commands.firmwareupdatemdv2.FirmwareMdReport cmd) { 
    log.debug ("received Firmware Report")
    log.debug "checksum:       ${cmd.checksum}"
    log.debug "firmwareId:     ${cmd.firmwareId}"
    log.debug "manufacturerId: ${cmd.manufacturerId}"
    [:]
}

def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) { 
	log.debug ("received switch binary Report")
    createEvent(name:"switch", value: cmd.value ? "on" : "off")
}

def zwaveEvent(physicalgraph.zwave.Command cmd) {
	// Handles all Z-Wave commands we aren't interested in
	[:]
}

def on() {
	
	delayBetween([
			zwave.basicV1.basicSet(value: 0xFF).format(),
			zwave.switchMultilevelV1.switchMultilevelGet().format()
	],5000)
}

def off() {
	
	delayBetween([
			zwave.basicV1.basicSet(value: 0x00).format(),
			zwave.switchMultilevelV1.switchMultilevelGet().format()
	],5000)
}

def setLux(lux) {
  def cmds = []
  if (lux == 0) {
    cmds << zwave.configurationV1.configurationSet(parameterNumber:2, size:2, scaledConfigurationValue: lux ).format()
  } else { 
  	adjustLux = Math.max(Math.min(lux, 200), 30)
	cmds << zwave.configurationV1.configurationSet(parameterNumber:2, size:2, scaledConfigurationValue: adjustLux ).format()
  }
}

def poll() {
/*
zwave.commands.switchbinaryv1.SwitchBinaryGet
	zwave.switchMultilevelV1.switchMultilevelGet().format()
*/
}

def refresh() {
	log.debug "refresh() called"
    configure()
}


def setFirmwareVersion() {
   def versionInfo = ''
   if (state.manufacturer)
   {
      versionInfo=state.manufacturer+' '
   }
   if (state.firmwareVersion)
   {
      versionInfo=versionInfo+"Firmware V"+state.firmwareVersion
   }
   else 
   {
     versionInfo=versionInfo+"Firmware unknown"
   }   
   sendEvent(name: "firmwareVersion",  value: versionInfo, isStateChange: true, displayed: false)
}

def configure() {
   log.debug ("configure() called")
 
   sendEvent(name: "numberOfButtons", value: 12, displayed: false)
   def commands = []
   commands << setPrefs()
   commands << zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
   commands << zwave.versionV1.versionGet().format()
   delayBetween(commands,500)
}

def setPrefs() 
{
   log.debug ("set prefs")
   def cmds = []

	if (onTime)
	{
    	def onTime = Math.max(Math.min(onTime, 720), 8)
		cmds << zwave.configurationV1.configurationSet(parameterNumber:1, size:2, scaledConfigurationValue: onTime ).format()
	}
    if (luxDisableValue)
	{
    	def luxDisableValue = Math.max(Math.min(luxDisableValue, 200), 30)
		cmds << zwave.configurationV1.configurationSet(parameterNumber:2, size:2, scaledConfigurationValue: luxDisableValue ).format()
	}
    if (luxReportInterval)
	{
    	def luxReportInterval = Math.max(Math.min(luxReportInterval, 1440), 0)
		cmds << zwave.configurationV1.configurationSet(parameterNumber:3, size:2, scaledConfigurationValue: luxReportInterval).format()
	}
   
   
   
   //Enable the following configuration gets to verify configuration in the logs
   //cmds << zwave.configurationV1.configurationGet(parameterNumber: 7).format()
   //cmds << zwave.configurationV1.configurationGet(parameterNumber: 8).format()
   //cmds << zwave.configurationV1.configurationGet(parameterNumber: 9).format()
   //cmds << zwave.configurationV1.configurationGet(parameterNumber: 10).format()
   
   return cmds
}
 
def updated()
{
 def cmds= []
 cmds << setPrefs
 delayBetween(cmds, 500)
}

Did you try it? Did it work for what you need?

Sorry…no yet. Thanks for following up. Back at work again so it may be a little while before I get around to it. I’ll report back when I do.

I modified the code to remove the range limitation for the LuxDisableValue so that any number from 0-255 can be entered. This required modifying the code in two places, where the value is entered and where the value is written.

But while this seemed to work, updating the parameters seemed to be inconsistent. Sometimes they would seem to change and sometimes they wouldn’t. It is easy to see how what the luxReportInterval is by simply looking at how often illumination level is reported. And just updating it once did not seem to reliably change the value.

@njschwartz - I have updated the DTH to include the SetLux command but I don’t see how to access it in WebCore. Do I need to remove the device and re-add it to make the command show up? Here are the only options I get when I try to add a task in WebCore for the FLS devices.

Answered my own question. I deleted the device from WebCore and added it back in and the new command showed up.

Cool cool…did it work for you like you wanted?

@njschwartz No it doesn’t seem to work. When I try to set the Lux Value using SetLux(50) from WebCore, I get the following error in the Logs in Live Logging in the ST console.

error groovy.lang.MissingPropertyException: No such property: adjustLux for class: script_dth_d5ddf5910af4f0abdf859eacc2727329dc7acbbac39e5851e3886df5c1966feb @line 228 (setLux)

I changed line 228 so that it did not define a new variable adjustLux, but just did ‘lux = Math.max(Math.min(lux, 200), 30)’ and the error went away, but it does not seem to change the lux value properly for any value other than 0. I have tried using setLux(50) using an integer variable instead of a constant.

@njschwartz I did some more testing and I think it is working after all. I had set the OnTime value to 3 minutes and that made the behaviour seem wrong - but I think it was just waiting for the 3 minute time of no motion to elapse. So at this point I think it is working. I am also trying to add a call to allow the OnTime value to be set but I am running into errors. But it is late so I will save that for another day.

Ok cool~Glad it seems to be working. I haven’t tested it since I originally did it months ago so I suppose it is possible something changed and broke it, but it should work.

As for the onTime, you are wanting to set that dynamically as well with something like webCoRE? That should be simple enough to do. I haven’t tested it, but try this:
At line 40 add:

command “setOnTime”, [“number”]

Then at line 234 add:

def setOnTime(onTime) {
  def cmds = []
  onTime = Math.max(Math.min(onTime, 720), 8)
		cmds << zwave.configurationV1.configurationSet(parameterNumber:1, size:2, scaledConfigurationValue: onTime ).format()
}

That should do it. You should now have a onTime command available in webCoRE. It has to be between 8s and 720s (12min). If you send something higher or lower it will just choose the 720s or 8s. Let me know if it works out.

That worked, thanks. I had tried to do that on my own and came very close, but I had used my own variable onTimeSec instead of onTime but that caused me to get the following error:
groovy.lang.MissingMethodException: No signature of method: static java.lang.Math.min() is applicable for argument types: (null, java.lang.Integer) values: [null, 720]
Possible solutions: min(int, int), min(double, double), min(float, float), min(long, long), find(), max(int, int) @line 241 (setOnTime)

It makes it sound like I would need to explicitly declare the proper type for onTimeSec, but I don’t see that onTime is explicitly declared anywhere. As I said, I am not a java/groovy programmer - I need to learn more.

Thanks for your help but if you know why my version with a different variable name didn’t work, please let me know.

No problem. You could change onTime to onTimeSec and it would still work. That error you got is saying that the variable you were using onTimeSec is null (has no value assigned to it). I cannot say without seeing exactly what you had why your variable was null. If you paste it here I can look at it.

It looked exactly like the code snippet you posted to add at line 234, except that everywhere you had onTime, I had onTimeSec.

def setOnTime(OnTimeSec) {
def cmds =
onTimeSec = Math.max(Math.min(onTimeSec, 720), 8)
cmds << zwave.configurationV1.configurationSet(parameterNumber:1, size:2, scaledConfigurationValue: onTimeSec ).format()
}

I called it from WebCore using an integer variable as the parameter.

Also, I would post my updated DTH including the calls for setLux and setOnTime (giving you credit for the changes) as well as the changes I made to allow for manual update of the parameters through the device page in ST Classic, but I can’t figure out how to paste it in here to make it easy to copy for someone else. Any pointers on how to do that?

But for now, here is the GitHub link

I think your issue is a simple typo based on what you pasted here. Your parameter name in the method definition had a capital ‘O’ for onTimeSec but everywhere else it is lowercase. Probably the problem…

As for pasting code in here, paste it in and then select it and use the little quotation marks option which will format it nicely like I did above. Hope that helps. Oh and you can share it however you see fit. Not worried about credit. :slight_smile:

Thanks for the debug. I feel a bit embarrassed to have missed that. I really appreciate your pointers here. I feel very good about my project to control three of the FLS100+ floods. I posted my pistons in the WebCore forum here.

1 Like

I just installed one of these and am looking for the motion sensor to stay active during the day but not turn on the flood lights. Is this possible?