[Release] Insteon HTTP switch/dimmer


#42

User ATOM text editor. It will point out errors in your config.json file by highlighting them in a different color.


#43

Should do, but it's possible you could be missing some dependencies. Probably not since it starts without an error.


#44

There are two similar version of this server. One has the websocket the other does not.

So if you npm installed the above server you should be good.

Several moving parts. Once everything is set right install of devices is a breeze. Get some rest.


#45

If you think you might be missing dependencies, install them via

npm -g install home-controller and also install npm -g install express


#46

@peter_aquino So sorry. We're trying to create a driver to the Insteon Keypad and it looks like something was inadvertently broken in the lasted parent driver build posted in Chris' repo. Please replace your Insteon WS parent driver code with the code pasted below and your child devices should now auto populate now after you click Initialize and then Save Device.

/**
 *  Insteon WS Parent
 *
 *  Copyright 2019 Chris Wilson
 *
 *  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.
 *
 *  
 *  
 *
 *  Original Author     : ethomasii@gmail.com
 *  Creation Date       : 2013-12-08
 *
 *  Rewritten by        : idealerror
 *  Last Modified Date  : 2016-12-13 
 *
 *  Rewritten by        : kuestess
 *  Last Modified Date  : 2017-09-30
 * 
 *  Hubitat port by    @cwwilson08
 *  Last Modified Date  : 2019-06-24
 *
 * 
 *  Changelog:
 *  2019-06-24: Merge functions of original Insteon Direct dimmer code with WS so all calls are completed by parent device
 *  2019-06-23: Convert to async http calls - major code cleanup
 *  2019-06-23: Utilize ogiewon's websocket reconncet code
 *  2018-10-01: Added ability to disable auto refresh in driver
 *  2018-09-14: Added Fast ON/OFF & Refresh setting in driver
 *  2018-09-09: Initial release for Hubitat Elevation Hub
 *  2016-12-13: Added polling for Hub2
 *  2016-12-13: Added background refreshing every 3 minutes
 *  2016-11-21: Added refresh/polling functionality
 *  2016-10-15: Added full dimming functions
 *  2016-10-01: Redesigned interface tiles
 * 
 */


import hubitat.helper.InterfaceUtils
import groovy.json.JsonSlurper

metadata {
definition (name: "Insteon WS Parent", namespace: "cw", author: "Chris Wilson") {
    capability "Initialize"
    capability "Switch"
    attribute "connection", ""
    
}
}

preferences {
input("ip", "text", title: "Websocket IP Address", description: "Websocket IP Address", required: true)
	input("wsPort", "text", title: "WS Port", description: "Websocket Port", required: true)
    input("host", "text", title: "URL", description: "The URL of your Insteon Hub (without http:// example: my.hub.com ")
    input("instPort", "text", title: "Insteon Hub Port", description: "The insteon hub port.")
    input("username", "text", title: "Username", description: "The hub username (found in app)")
    input("password", "text", title: "Password", description: "The hub password (found in app)")
    input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: true
}


def parse(String description) {
    if (logEnable)log.debug "data from websocketparsed here"
    if (logEnable)log.debug "description: ${description}"
    if (description.startsWith("Connected to Insteon Server")) {
    sendEvent(name: "connection", value: "connected")
		return
   }
	

	def jsonSlurper = new JsonSlurper()
	def msg = jsonSlurper.parseText(description)

if (msg.dimmable){
    createDevices(msg)
    return
}

if (msg.id != null){
    if(state.childDevice.contains(msg.id)){
	child = getChildDevice(msg.id)
        log.debug "Got event for Device ${child} with a value of ${msg.state}"   
if (msg.deviceType == "contactsensor") {childContact(child, msg.state)}
if (msg.deviceType == "dimmer") {childDimmer(child, msg.state)}
if (msg.deviceType == "lightbulb") {childDimmer(child, msg.state)}        
if (msg.deviceType == "leaksensor") {childLeakSensor(child, msg.state)}

                                          } 
 }
}
                                                          
def childContact(child, state){
if (logEnable) log.debug "child conact = ${child}"
if (logEnable) log.debug "child state = ${state}"
child.sendEvent(name: "contact", value: state)}

def childLeakSensor(child, state){
if (logEnable) log.debug "child leak = ${child}"
if (logEnable)log.debug "child state = ${state}"
child.sendEvent(name: "water", value: state)}


def childDimmer(child, state){
if (state == 0){
    child.sendEvent(name: "level", value: state)
    child.sendEvent(name: "switch", value: "off")
}
   
if (state > 0){
   if (logEnable) log.debug state
    child.sendEvent(name: "level", value: state)
    child.sendEvent(name: "switch", value: "on")
}
}


def initialize() {
   log.info "initialize() called"
   if (!ip) {
    log.warn "Please enter an IP"
    return
}

	try {
		
   
		InterfaceUtils.webSocketConnect(device, "ws://${ip}:${wsPort}")
} 
catch(e) {
    if (logEnable) log.debug "initialize error: ${e.message}"
    log.error "WebSocket connect failed"
    sendEvent(name: "connection", value: "disconnected")
}

}

def updated() {
log.info "updated() called"
//Unschedule any existing schedules
unschedule()

//Create a 30 minute timer for debug logging
if (logEnable) runIn(1800,logsOff)

//Connect the webSocket
    initialize()
    runIn(2, listChild)
    runIn(4, getDevices)
    
}

def logsOff(){
log.warn "debug logging disabled..."
device.updateSetting("logEnable",[value:"false",type:"bool"])
}


def getDevices() {
	if (logEnable) log.debug "getDevices sent"
	sendMsg ("getDevices")
}


def createDevices(msg){ msg.each { it->
    namespace = "cw"
    if (it.deviceType == "motionsensor") {type = "Insteon Motion Child"}
	if (it.deviceType == "contactsensor") {type = "Insteon Contact Child"}
    if (it.deviceType == "dimmer") {type = "Insteon Dimmer Child"}
    if (it.deviceType == "leaksensor") {type = "Insteon Leak Child"}
    if (it.deviceType == "lightbulb") {type = "Insteon Dimmer Child"}
    if (logEnable)log.debug type
    if (logEnable)log.debug it.deviceID
    if (logEnable)log.debug it.name
    if(!state.childDevice.contains(it.deviceID)){
    log.debug "creating device: ${it.name}"
	addChildDevice (namespace, type, it.deviceID, [label: it.name, isComponent: false, name: type])
}

}
listChild()

}
def listChild (){
def myMap =[]
	if (logEnable)log.debug "listChild called"
	childDevices.each{ it ->
        if (logEnable)log.debug "child: ${it.deviceNetworkId}"
        myMap << it.deviceNetworkId
        
        
}
if (logEnable)log.debug myMap
state.childDevice = myMap

}

def sendMsg(String s) {
InterfaceUtils.sendWebSocketMessage(device, s)
}



def webSocketStatus(String status){
if (logEnable)log.debug "webSocketStatus- ${status}"

if(status.startsWith('failure: ')) {
    log.warn("failure message from web socket ${status}")
    sendEvent(name: "connection", value: "disconnected")
    reconnectWebSocket()
} 
else if(status == 'status: open') {
    sendEvent(name: "connection", value: "connected")    
    log.info "websocket is open"
    if (logEnable)log.debug "Resetting reconnect delay"
    // success! reset reconnect delay
    pauseExecution(1000)
    state.reconnectDelay = 1
} 
else if (status == "status: closing"){
    log.warn "WebSocket connection closing."
    sendEvent(name: "connection", value: "disconnected")
} 
else {
    log.warn "WebSocket error, reconnecting."
    reconnectWebSocket()
}
}

def reconnectWebSocket() {
// first delay is 2 seconds, doubles every time
state.reconnectDelay = (state.reconnectDelay ?: 1) * 2
// don't let delay get too crazy, max it out at 10 minutes
if(state.reconnectDelay > 600) state.reconnectDelay = 600

   
runIn(state.reconnectDelay, initialize)
}


////control functions here


def setLevel(value, deviceid) {

    if (logEnable)log.debug "setLevel >> value: $value"
    
    // Max is 255
    def percent = value / 100
    def realval = percent * 255
    def valueaux = realval as Integer
    def level = Math.max(Math.min(valueaux, 255), 0)
    if (level > 0) {
        sendEvent(name: "switch", value: "on")
    } else {
        sendEvent(name: "switch", value: "off")
    }
    if (logEnable)log.debug "dimming value is $valueaux"
    if (logEnable)log.debug "dimming to $level"
    dim(level,value, deviceid)
}

def setLevel(value, rate, deviceid) {

    if (logEnable)log.debug "setLevel >> value: $value"
    
    // Max is 255
    def percent = value / 100
    def realval = percent * 255
    def valueaux = realval as Integer
    def level = Math.max(Math.min(valueaux, 255), 0)
    if (level > 0) {
        sendEvent(name: "switch", value: "on")
    } else {
        sendEvent(name: "switch", value: "off")
    }
    if (logEnable)log.debug "dimming value is $valueaux"
    if (logEnable)log.debug "dimming to $level"
    dim(level,value)
}

def dim(level, real, deviceid) {
    String hexlevel = level.toString().format( '%02x', level.toInteger() )
   
    if (logEnable)log.debug "Dimming Device: ${deviceid} to hex $hexlevel"
    sendCmd("11",hexlevel, deviceid)
}

def sendCmd(num, level, deviceid){
    
    def requestParams = [ uri: "http://${settings.username}:${settings.password}@${settings.host}:${settings.instPort}//3?0262${deviceid}0F${num}${level}=I=3" ]
    if(logEnable)log.debug requestParams
    if(logEnable)log.debug "Sending command to Device: ${device} with parameters Command: ${num} Level ${level}"
	asynchttpGet("cmdHandler", requestParams)
}

def cmdHandler(resp, data) {
	if(resp.getStatus() == 200 || resp.getStatus() == 207) {
        if (logEnable) "Command Sent successfully"
    
        } else {
        
		    log.error "Error sending command to device"
	}
}

#47

Ouch. Will check it out when I get home. My apologies.


#48

Thanks for the updated driver code. I ended up wiping the pi clean and starting from scratch but it works now and I got the parent to recognize the 1 child device I configured. I've noticed that sometimes when I slide the dimmer, the light adjusts to my desired level but then the slider springs back up to 100% (but doesn't affect the light level). If I adjust it again, it will again adjust the dimmer to desired level and then spring back to 100%. It doesn't happen all the time, but often enough to be annoying. Have you ever noticed this behavior?


#49

Yes there is a bug I the insteon server. When I get home I'll direct you at the line of code to fix.

Btw thanks for trying it out. Sorry for the troubles.


#50

Oh you mean in the Insteon app? I wouldn't recommend relying on using the dimming control from the Insteon app while you're using this setup. It can work as you've seen, but it's not going to work smoothly while both things are hitting the hub at once. Not a lot of capacity in the Insteon hub for that. ON/OFF should be fine.

Update to status in HE are about 5 seconds too, so keep that in mind. I would suggest if you want to dim from an app, use HE Dashboard.


#51

I'm referring to the HE dashboard. I never use the Insteon app.


#52

Thanks. I'll do some testing with it on my end too.


#53

think i found a bug in the insteonserver.js file. I was having some issues until I changed this line at 357

from this

message = {name: foundDevice.name, id: foundDevice.deviceID, deviceType: foundDevice.deviceType, state: 100}

to this

message = {name: foundDevice.name, id: foundDevice.deviceID, deviceType: foundDevice.deviceType, state: level}

Once I did that everything started working perfectly. Thank you again for doing this.


#54

This was the fix I posted to the author.


#55

I recall that. I think the old line got put back in there by mistake. Are my changes for the leak sensor still there? NM, I see that it is indeed there in our versions, but won't be there from Scott's repo.

Can you confirm; It was just the line with state=100 correct?


#56

In the Insteon Server you linked above, that line isn't at 357. Are you referring to line 362 (where the code is handling a command==11 for a dimmer/lightbulb device)?


#57

The line he posted. The line numbers may not correspond exactly if you have more spaces between lines. If you use the Atom text editor, there's a find function that will highlight it.


#58

That exact line exists for 2 commands, 11 and 12. After looking at the code more it makes sense that I would only change it for an 11.


#59

Just the line the ends in state=100


#60

Sorry. I understand what you mean now. I changed it in both places. One is for fast on and the other is for ON. I'm fairly certain both have to change.


#61

Fast on should probally always be 100 however using the level variable will make sure it shows what is reported by the device.