Insteon Local Control!

GOOD NEWS!!!

Thanks to the work the @pstuart did i was able to modify his code to allow local control of Insteon devices through Smartlinc!!! So now my Garage Door opener isn’t open to anyone outside the network. (assuming you change the internal port and don’t allow external access to that port)

Here is the code for both an Insteon Switch and a I/O Linc (Garage Opener)

Insteon Switch Code:

/**
 *  Insteon Switch (LOCAL)
 *
 *  Copyright 2014 patrick@patrickstuart.com
 *
 *  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.
 *
 */
metadata {
	definition (name: "Insteon Switch (LOCAL)", namespace: "ps", author: "patrick@patrickstuart.com/tslagle13@gmail.com") {
		capability "Switch"
		capability "Sensor"
		capability "Actuator"
        
	}

    preferences {
    input("InsteonIP", "string", title:"Insteon IP Address", description: "Please enter your Insteon Hub IP Address", required: true, displayDuringSetup: true)
    input("InsteonPort", "string", title:"Insteon Port", description: "Please enter your Insteon Hub Port", defaultValue: 80 , required: true, displayDuringSetup: true)
    input("OnPath", "string", title:"Path to 'ON' Command", description: "Please enter the path to the 'ON' command", required: true, displayDuringSetup: true)
    input("OffPath", "bool", title:"Path to 'OFF' Command", description: "Please enter the path to the 'OFF' command", displayDuringSetup: true)
	}
    
	simulator {
		// status messages
		status "on": "on/off: 1"
		status "off": "on/off: 0"

		// reply messages
		reply "zcl on-off on": "on/off: 1"
		reply "zcl on-off off": "on/off: 0"
	}

	// UI tile definitions
	tiles {
		standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
			state "off", label: '${name}', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
			state "on", label: '${name}', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821"
		}

		main "switch"
		details "switch"
	}
}
// handle commands
def on() {
	//log.debug "Executing 'take'"
    sendEvent(name: "switch", value: "on")
    def host = InsteonIP
    def hosthex = convertIPToHex(host)
    def porthex = Long.toHexString(Long.parseLong((InsteonPort)))
    if (porthex.length() < 4) { porthex = "00" + porthex }
    
    //log.debug "Port in Hex is $porthex"
    //log.debug "Hosthex is : $hosthex"
    device.deviceNetworkId = "$hosthex:$porthex" 
    
    //log.debug "The device id configured is: $device.deviceNetworkId"
    
    def path = CameraPath //"/SnapshotJPEG?Resolution=640x480&Quality=Clarity"
    log.debug "path is: $OnPath"
    
    def headers = [:] //"HOST:" + getHostAddress() + ""
    headers.put("HOST", "$host:$InsteonPort")
    
    try {
    def hubAction = new physicalgraph.device.HubAction(
    	method: method,
    	path: OnPath,
    	headers: headers
        )  
    }
    catch (Exception e) 
    {
    log.debug "Hit Exception on $hubAction"
    log.debug e
    }
    }

def off() {
	//log.debug "Executing 'take'"
    sendEvent(name: "switch", value: "off")
    def host = InsteonIP
    def hosthex = convertIPToHex(host)
    def porthex = Long.toHexString(Long.parseLong((InsteonPort)))
    if (porthex.length() < 4) { porthex = "00" + porthex }
    
    //log.debug "Port in Hex is $porthex"
    //log.debug "Hosthex is : $hosthex"
    device.deviceNetworkId = "$hosthex:$porthex" 
    
    //log.debug "The device id configured is: $device.deviceNetworkId"
    
    def path = CameraPath //"/SnapshotJPEG?Resolution=640x480&Quality=Clarity"
    log.debug "path is: $OffPath"

    
    def headers = [:] //"HOST:" + getHostAddress() + ""
    headers.put("HOST", "$host:$InsteonPort")
    
    try {
    def hubAction = new physicalgraph.device.HubAction(
    	method: method,
    	path: OffPath,
    	headers: headers
        )
    }
    catch (Exception e) 
    {
    log.debug "Hit Exception on $hubAction"
    log.debug e
    }
    }

private getPictureName() {
	def pictureUuid = java.util.UUID.randomUUID().toString().replaceAll('-', '')
	return device.deviceNetworkId + "_$pictureUuid" + ".jpg"
}

private Long converIntToLong(ipAddress) {
	long result = 0
	def parts = ipAddress.split("\\.")
    for (int i = 3; i >= 0; i--) {
        result |= (Long.parseLong(parts[3 - i]) << (i * 8));
    }

    return result & 0xFFFFFFFF;
}

private String convertIPToHex(ipAddress) {
	return Long.toHexString(converIntToLong(ipAddress));
}

private Integer convertHexToInt(hex) {
	Integer.parseInt(hex,16)
}
private String convertHexToIP(hex) {
log.debug("Convert hex to ip: $hex") //	a0 00 01 6
	[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}

private getHostAddress() {
	def parts = device.deviceNetworkId.split(":")
    log.debug device.deviceNetworkId
	def ip = convertHexToIP(parts[0])
	def port = convertHexToInt(parts[1])
	return ip + ":" + port
}

I/O Linc (Garage Opener) Code:

 /**
 *  Insteon Garage Opener (LOCAL)
 *
 *  Copyright 2014 patrick@patrickstuart.com
 *
 *  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.
 *
 */
metadata {
	definition (name: "Insteon Garage Opener (LOCAL)", namespace: "ps", author: "patrick@patrickstuart.com\tslagle13@gmail.com") {
		capability "Actuator"
		capability "Switch"
		capability "Momentary"
		capability "Sensor"
	}

    preferences {
    input("InsteonIP", "string", title:"Insteon IP Address", description: "Please enter your Insteon Hub IP Address", required: true, displayDuringSetup: true)
    input("InsteonPort", "string", title:"Insteon Port", description: "Please enter your Insteon Hub Port", defaultValue: 80 , required: true, displayDuringSetup: true)
    input("OnPath", "string", title:"Path to 'ON' Command", description: "Please enter the path to the 'ON' command", required: true, displayDuringSetup: true)
	}
    
	simulator {
	}

	// UI tile definitions
	tiles {
		standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
			state "off", label: 'Garage Door Opener', action: "momentary.push", backgroundColor: "#ffffff", nextState: "on"
			state "on", label: 'Garage Door Opener', action: "momentary.push", backgroundColor: "#53a7c0"
		}
		main "switch"
		details "switch"
	}
}
// handle commands
def push() {
	//log.debug "Executing 'take'"
    sendEvent(name: "switch", value: "on", isStateChange: true, display: false)
	sendEvent(name: "switch", value: "off", isStateChange: true, display: false)
	sendEvent(name: "momentary", value: "pushed", isStateChange: true)
    def host = InsteonIP
    def hosthex = convertIPToHex(host)
    def porthex = Long.toHexString(Long.parseLong((InsteonPort)))
    if (porthex.length() < 4) { porthex = "00" + porthex }
    
    //log.debug "Port in Hex is $porthex"
    //log.debug "Hosthex is : $hosthex"
    device.deviceNetworkId = "$hosthex:$porthex" 
    
    //log.debug "The device id configured is: $device.deviceNetworkId"
    
    def path = CameraPath //"/SnapshotJPEG?Resolution=640x480&Quality=Clarity"
    log.debug "path is: $OnPath"
    
    def headers = [:] //"HOST:" + getHostAddress() + ""
    headers.put("HOST", "$host:$InsteonPort")
    
    try {
    def hubAction = new physicalgraph.device.HubAction(
    	method: method,
    	path: OnPath,
    	headers: headers
        )  
    }
    catch (Exception e) 
    {
    log.debug "Hit Exception on $hubAction"
    log.debug e
    }
    }
    
def on() {
	push()
}

def off() {
	push()
}    

private Long converIntToLong(ipAddress) {
	long result = 0
	def parts = ipAddress.split("\\.")
    for (int i = 3; i >= 0; i--) {
        result |= (Long.parseLong(parts[3 - i]) << (i * 8));
    }

    return result & 0xFFFFFFFF;
}

private String convertIPToHex(ipAddress) {
	return Long.toHexString(converIntToLong(ipAddress));
}

private Integer convertHexToInt(hex) {
	Integer.parseInt(hex,16)
}
private String convertHexToIP(hex) {
log.debug("Convert hex to ip: $hex") //	a0 00 01 6
	[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}

private getHostAddress() {
	def parts = device.deviceNetworkId.split(":")
    log.debug device.deviceNetworkId
	def ip = convertHexToIP(parts[0])
	def port = convertHexToInt(parts[1])
	return ip + ":" + port
}

Here is the info you need to make this work.

IP Address = IP Address of the Smartlinc
Port = Port the smartlinc communicates on
Path to On Command = /3?0262[DEVICE ID]0F11FF=I=3
Path to Off Command = /3?0262[DEVICE ID]0F13FF=I=3
Replace “[DEVICE ID]” with your switch’s ID found in the smartlinc control panel or on the physical device

I’ve tested all my Insteon Devices and they work. So as long as your IP range is above 100.x.x.x you should be able to use this code. If your IP range is something like 10.x.x.x this will not work. (Until STs fixes this bug for IP-to-Hex Conversion) See this thread for more info on the issue with IP addresses.

Big thanks to Patrick Stuart!!!

7 Likes

anyone tried this yet? Looks awesome…i dont have an Insteon hub yet but there are several devices I want…want to make sure this works fine before I go out and buy a smartlinc hub and devices…

I’m actually working on some more integrated control and a SmartApp for Insteon devices using a PLM instead of a hub. The SmartLink is no longer sold so it wasn’t an option - which was disappointing. I decided against the Insteon Hub as it requires using the cloud - and although the smartthings also does, I want to limit cloud connection - and the Hub was also more expensive.The hub also has a terrible api (so I’m told and from what I’ve seen). Oh, and there’s also the lack of two way communication back to a ST app as well. The downside is that i needed a way to expose control of the PLM(usb version) via IP - so rasberryPi to the rescue.

I’m working on a service that connects control of the PLM to a LAN connected rPi serving up a nice REST client. So far my proof of concept works. It’s nothing magic, and unfortunately requires additional hardware. I’m coming from the revolv world with a lot of insteon devices - so I need the option - as do several others I’m sure. The other benefit is that I can receive messages from the PLM directly into a SmartApp.

There will be limitations of course. Biggest one IMO so far is that a smartapp can’t define deviceTypes. You’ll still be required to define deviceTypes in the web UI for any insteon device. Secondly, I’ve not yet determined if the ST ui can handle “finding” devices. I can set the PLM into discovery mode from the smartapp and handle any notifications of new devices - but not sure how to expose to user (yet).

That all being said, I think there is great possibility here, especially with the ability to handle two way communication. There’s a lot of work to be done, but I’ve got proof of concept completed with a single insteon switch. If anyone wants to help on either side of the puzzle, PM me. I am using mono on the rPi as I’m a c# developer and it’s just the easiest and fastest route. I have no time to bother with any other language - it’s enough dealing with ST groovy and lack of documentation.

Thanks @tslagle13 and @pstuart

I’ve updated the code for Insteon Light Switches and it now works witht the Insteon Hub.

Here is the link to my code on GitHub

What you’ll need to get this to work:

  • Add the code as a new device type: Insteon Switch (LOCAL) in My Device Types in the SmartThings IDE
  • Configure a new device with the settings below
  • Insteon IP (static IP address of your hub
  • Insteon Port (default 25105)
  • Device Insteon ID (In the Insteon App > Settings > Devices > Switch Name) #numbers only - leave out the dots
  • Insteon Hub Username & Password (In the Insteon App > Settings > House)

Hope this helps some people automate their old Insteon switches with SmartThings.

Mine are working great :smile:

4 Likes

This is why this community is so cool!!

2 Likes

Thanks man. I’m going to figure out how to do on/off state detection sometime after CES.

1 Like

I know the insteon will report a jason header with reports. just to busy to try and figure out how to parse it.

Does this also work with an Insteon USB modem (ie PowerLinc) ? As far as I know, there is no dedicated username/pass/ip/port for the device. Thoughts? Id love to figure out a way to bring my old insteon door sensors and motion sensors into smartthings. Is there a workaround?

1 Like

I do’t think there is any IP/Network communication for this device. As far as i know its just not possible.

Not possibly directly. You would/will need a web service on top of the insteon protocol to communicate with the PLM. I have a beta working of this, but it’s not ready for others yet.

I tried to play around with some code to query the state of the switch and since there’s not a lot of documentation on connecting to LAN devices, it’s been mostly trial and error.

TL;DR: I’ve been unsuccessful in polling the lights so far. My initial code for turing the lights on and off still works. But, SmartThings isn’t notified when someone turns the light on or off using the physical switches.

My learnings are below:

This doRefresh() method using HubAction() effectively queries the insteon hub for the xml file that has the status of the switch.

def doRefresh() {
	def path = "/sx.xml?" + "${InsteonID}"  + "=1900"
    def method = "GET"
    def host = InsteonIP

    log.debug "refresh path is: $path"
 
 
    def userpassascii = "${InsteonHubUsername}:${InsteonHubPassword}"
	def userpass = "Basic " + userpassascii.encodeAsBase64().toString()
    def headers = [:] //"HOST:" 
    headers.put("HOST", "$host:$InsteonPort")
    headers.put("Authorization", userpass)
 
    try {
    def hubAction = new physicalgraph.device.HubAction(
    	method: method,
    	path: path,
    	headers: headers
        )
    log.debug "hubAction: " + hubAction
    hubAction  
    def result = hubAction
   	log.debug result.text    
    }
    catch (Exception e) {
    	log.debug "Hit Exception on $hubAction"
    	log.debug e
    }
}

What we need to do next is parse the XML file that the Insteon hub returns to determine whether the light is on or off.

<Xs>
<X D="1E65F22502FF"/>
</Xs>

If the string ends in “FF” the light is on. If it ends in “00” it’s off.

This should be fairly straightforward, but I’ve been unable to figure out how to parse the XML file.

Here’s where I’m running into problems:

After looking at @pstuart’s code on Github, my assumption is that I would need to change the device’s NetworkID to a hex version of the IP and port in order to be able to receive the XML file to parse.

He uses the code:

device.deviceNetworkId = "$hosthex:$porthex" 

The problem with this, is the SmartThings Hub does not allow multiple devices to have the same NetworkID.

I have five Insteon Devices using the same IP and port (because they are on the same Insteon hub). So this wouldn’t work. I’m not sure if I should proceed further down this road by using a child device relationship. I’ve seen it mentioned very briefly in the documentation.

The other thing I tried was using the httpGet() method. Some people have said they have gotten this to work when talking to local IPs. Others have said httpGet originates from the cloud so it can’t talk to local IPs.

It didn’t work for me. I got timeout errors in the event log when using this code:

def doRefresh2() {
	def path = "/sx.xml?" + "${InsteonID}"  + "=1900" 
    def uri = "http://${InsteonIP}:${InsteonPort}" 
	def userAgent = "curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.8x zlib/1.2.5"
    def userpassascii = "${InsteonHubUsername}:${InsteonHubPassword}"
	def userpass = "Basic " + userpassascii.encodeAsBase64().toString()
    
    
    def content
    httpGet(
        [
            uri: uri,
            path: path,
            headers: [Authorization: userpass, 'User-Agent': userAgent],
            requestContentType: "application/x-www-form-urlencoded"
        ],
        { response -> content = response.data }
    )
    log.debug "content: " + content
    def props = content?.items
    if (props) {
        return props[0]
    }
    return [:]
}

Maybe httpGet() would work if I set up port forwarding from my router’s WAN to the Insteon Hub on the LAN, but that seems like a lot of hoops to jump through.

This has been difficult to accomplish without documentation. So, I’m not sure if the assumptions I’ve made are correct. Anyone have any insights here on what to try next?

Yes, you are correct DNI has to be unique to process info.

What you really need to do is write a SmartApp / Service Manager to do the following:

Discover / set up the insteon hub.

Discover / add / edit / delete “devices” on hub

Create child devices for each discovered device.

Lastly, since you are in a smartapp, you can do all the code for each child device in the smartapp. Then, all send/receive is done at the smartapp level, handling the communication for the hub and then routing the commands and status to the child devices.

Hope that makes sense.

Not sure if what I am going to say is what Patrick just said… I have no programming or development experience at all so I have fumbled through all this. Full disclosure.
I have used you Insteon app and got it to work for one device. When I try and add another device to control SH shows this error:
500: Internal Server Error
URI
/device/save
Reference Id
573de79e-c4b3-4350-ade1-f62830eb4992
Date
Fri Jan 16 17:15:40 UTC 2015
Class
grails.validation.ValidationException
Message
Validation Error(s) occurred during save(): - Field error in object ‘physicalgraph.device.Device’ on field ‘deviceNetworkId’: rejected value [192.168.0.24]; codes [physicalgraph.device.Device.deviceNetworkId.unique.error.physicalgraph.device.Device.deviceNetworkId,physicalgraph.device.Device.deviceNetworkId.unique.error.deviceNetworkId,physicalgraph.device.Device.deviceNetworkId.unique.error.java.lang.String,physicalgraph.device.Device.deviceNetworkId.unique.error,device.deviceNetworkId.unique.error.physicalgraph.device.Device.deviceNetworkId,device.deviceNetworkId.unique.error.deviceNetworkId,device.deviceNetworkId.unique.error.java.lang.String,device.deviceNetworkId.unique.error,physicalgraph.device.Device.deviceNetworkId.unique.physicalgraph.device.Device.deviceNetworkId,physicalgraph.device.Device.deviceNetworkId.unique.deviceNetworkId,physicalgraph.device.Device.deviceNetworkId.unique.java.lang.String,physicalgraph.device.Device.deviceNetworkId.unique,device.deviceNetworkId.unique.physicalgraph.device.Device.deviceNetworkId,device.deviceNetworkId.unique.deviceNetworkId,device.deviceNetworkId.unique.java.lang.String,device.deviceNetworkId.unique,unique.physicalgraph.device.Device.deviceNetworkId,unique.deviceNetworkId,unique.java.lang.String,unique]; arguments [deviceNetworkId,class physicalgraph.device.Device,192.168.0.24]; default message [{0} must be unique]

Do I need to create a device type for each insteon device I have? Or am I fumbling around wrong???
Thanks!

Hi Brian,

You only need one device type, but you do need to pick a unique Device Network ID for each device. I used the Insteon ID for that. See the screenshot attached.

Thanks, Patrick.

I used the Insteon Hub API documentation from leftover code. There isn’t any way to do hub / device discovery and management mentioned in these docs.

I’m waiting for an API key for Insteon’s new cloud API, which does look like it supports discovery and management.

Are there any good examples or documentation from the community for doing what you described?

A few examples but st doesn’t give a good way to manually adding devices in a smartapp.

Use pages to list add edit or delete devices and then configure child devices that mirror insteon functionality.

Royal PIA. But that is how it has to work.

I assume there is some sort of deviceid on the insteon hub and a command set and status.

Just ask for an insteon deviceid and choose a type and then add a child device

The smartapp handles the code and polls for status and sends commands for the child devices.

1 Like

Thank you Mr. Gold! Works perfect as I expected! (once I got it right…)
Next question/comment. I have the Insteon hub for mainly one reason, control over X10. I did use the insteon outdoor plugs for my christmas lights this year. All my home theater amps turn on using x-10 (I use commercial amps not consumer) So my harmony remote can turn on the x-10 stuff for the amps using an IR to x-10 converter. I tried to create a device type for the insteon hub and use the address for the x-10, example a01. Well that does not work, googled x-10 insteon address and no help.
Is it even possible to control x-10 with the insteon hub through smartthings? Or should I hope for harmony ultimate (been putting purchase off) and use harmony to control x-10 through smartthings via the ir to x-10 converter? Yikes what a mess!!!

Someone with X10 devices to test this out should be able to fork my gist and modify the code.

It looks like there is a different API for the X10 devices.
It requires a few new parameters - Instead of Insteon device IDs, this API uses house codes, and unit codes. Both of these parameters would need to be converted into the value code mentioned in the API documentation.

Ok, I was looking at the code and I found where it sends the commands on line 62 and 91. I can copy the code and change from 0262 to 0263 to make it x10. the reason to copy is to send the “on” code after the house and unit code. I will do the same thing with the off code. I do not do coding but seems like what I am doing is just copy and paste. I noticed the copyrights at the top of the code.Is anyone going to get mad if I start messing with there work? This may take me some time, I work about 78 hours a week starting monday. But will try and play on my lunch.

Everyone that wrote the code is on this thread. I say go for it!