Looks like we may have either abandoned the attempt because we didn't have a motion sensor to test with, or just forgot to finish adding it. Part of the code was in the ws parent file, but not all of it, and as you mentioned, nothing was in the insteonserver.js file. I'm not sure I did it right, but give these a try and see if they work for you.
Insteonserver.js (with motion sensor - no keypad support)
'use strict'
var Insteon = require('home-controller').Insteon
var hub = new Insteon()
var express = require('express')
var app = express()
var fs = require('fs')
var _ = require('underscore')
var websocket = require('ws')
var wss = new websocket.Server({port: 8080})
var configFile = fs.readFileSync('./config.json')
var configJSON = JSON.parse(configFile)
var platformIndex = configJSON.platforms.findIndex(function(item){return item.platform =='InsteonLocal'})
var config = configJSON.platforms[platformIndex]
InsteonServer()
function InsteonServer() {
var devices = config.devices
var deviceIDs = []
var deviceJSON = []
devices.forEach(function(device){
deviceIDs.push(device.deviceID)
})
devices.forEach(function(device){
var devJSON = {name: device.name, deviceID: device.deviceID, dimmable: device.dimmable, deviceType: device.deviceType}
deviceJSON.push(devJSON)
})
var host = config.host
var port = config.port
var user = config.user
var pass = config.pass
var model = config.model
var server_port = config.server_port || 3000
var hubConfig = {
host: host,
port: port,
user: user,
password: pass
}
connectToHub()
init()
app.get('/light/:id/on', function(req, res) {
var id = req.params.id.toUpperCase()
hub.light(id).turnOn().then(function(status) {
if (status.response) {
res.sendStatus(200)
} else {
res.sendStatus(404)
}
})
})
app.get('/light/:id/off', function(req, res) {
var id = req.params.id.toUpperCase()
hub.light(id).turnOff().then(function(status) {
if (status.response) {
res.sendStatus(200)
} else {
res.sendStatus(404)
}
})
})
app.get('/light/:id/faston', function(req, res) {
var id = req.params.id.toUpperCase()
hub.light(id).turnOnFast().then(function(status) {
if (status.response) {
res.sendStatus(200)
} else {
res.sendStatus(404)
}
})
})
app.get('/light/:id/fastoff', function(req, res) {
var id = req.params.id.toUpperCase()
hub.light(id).turnOffFast().then(function(status) {
if (status.response) {
res.sendStatus(200)
} else {
res.sendStatus(404)
}
})
})
app.get('/light/:id/status', function(req, res) {
var id = req.params.id
hub.light(id).level(function(err, level) {
res.json({
'level': level
})
})
})
app.get('/light/:id/level/:targetLevel', function(req, res) {
var id = req.params.id
var targetLevel = req.params.targetLevel
hub.light(id).level(targetLevel).then(function(status) {
if (status.response) {
res.sendStatus(200)
} else {
res.sendStatus(404)
}
})
})
app.get('/scene/:group/on', function(req, res) {
var group = parseInt(req.params.group)
hub.sceneOn(group).then(function(status) {
if (status.aborted) {
res.sendStatus(404)
}
if (status.completed) {
res.sendStatus(200)
} else {
res.sendStatus(404)
}
})
})
app.get('/scene/:group/off', function(req, res) {
var group = parseInt(req.params.group)
hub.sceneOff(group).then(function(status) {
if (status.aborted) {
res.sendStatus(404)
}
if (status.completed) {
res.sendStatus(200)
} else {
res.sendStatus(404)
}
})
})
app.get('/links', function(req, res) {
hub.links(function(err, links) {
res.json(links)
})
})
app.get('/links/:id', function(req, res) {
var id = req.params.id
hub.links(id, function(err, links) {
res.json(links)
})
})
app.get('/info/:id', function(req, res) {
var id = req.params.id
hub.info(id, function(err, info) {
res.json(info)
})
})
app.get('/iolinc/:id/relay_on', function(req, res) {
var id = req.params.id
hub.ioLinc(id).relayOn().then(function(status) {
if (status.response) {
res.sendStatus(200)
} else {
res.sendStatus(404)
}
})
})
app.get('/iolinc/:id/relay_off', function(req, res) {
var id = req.params.id
hub.ioLinc(id).relayOff().then(function(status) {
if (status.response) {
res.sendStatus(200)
} else {
res.sendStatus(404)
}
})
})
app.get('/iolinc/:id/sensor_status', function(req, res) {
var id = req.params.id
hub.ioLinc(id).status(function(err, status) {
res.json(status.sensor)
})
})
app.get('/iolinc/:id/relay_status', function(req, res) {
var id = req.params.id
hub.ioLinc(id).status(function(err, status) {
res.json(status.relay)
})
})
app.listen(server_port)
function connectToHub() {
console.log('Model: ' + model)
if (model == '2245') {
console.log('Connecting to Insteon Model 2245 Hub...')
hub.httpClient(hubConfig, function() {
console.log('Connected to Insteon Model 2245 Hub...')
connectedToHub = true
})
} else if (model == '2243') {
console.log('Connecting to Insteon "Hub Pro" Hub...')
connectingToHub = true
hub.serial('/dev/ttyS4',{baudRate:19200}, function() {
console.log('Connected to Insteon "Hub Pro" Hub...')
connectedToHub = true
})
} else if (model == '2242') {
console.log('Connecting to Insteon Model 2242 Hub...')
hub.connect(host, function() {
console.log('Connected to Insteon Model 2242 Hub...')
connectedToHub = true
})
} else {
console.log('Connecting to Insteon PLM...')
hub.serial(host,{baudRate:19200}, function() {
console.log('Connected to Insteon PLM...')
connectedToHub = true
})
}
}
function init() {
console.log('Initiating websocket...')
var message
wss.on('connection', function (ws) {
console.log('Client connected to websocket')
ws.isAlive = true
ws.on('close', function(){
console.log('Websocket closed by client')
ws.isAlive = false
})
ws.send('Connected to Insteon Server')
ws.on('message', function (message) {
if(message == 'getDevices'){
console.log(deviceJSON)
if(ws.isAlive){ws.send(JSON.stringify(deviceJSON))}
}
})
devices.forEach(function(device){
switch (device.deviceType) {
case 'doorsensor':
case 'windowsensor':
case 'contactsensor':
case 'motionsensor':
device.door = hub.door(device.deviceID)
device.door.on('opened', function(){
console.log('Got open for ' + device.name)
message = {name: device.name, id: device.deviceID, deviceType: device.deviceType, state: 'open'}
if(ws.isAlive){ws.send(JSON.stringify(message))}
})
device.door.on('closed', function(){
console.log('Got closed for ' + device.name)
message = {name: device.name, id: device.deviceID, deviceType: device.deviceType, state: 'closed'}
if(ws.isAlive){ws.send(JSON.stringify(message))}
})
break
case 'leaksensor':
device.leak = hub.leak(device.deviceID)
device.leak.on('dry', function(){
console.log('Got dry for ' + device.name)
message = {name: device.name, id: device.deviceID, deviceType: device.deviceType, state: 'dry'}
if(ws.isAlive){ws.send(JSON.stringify(message))}
})
device.leak.on('wet', function(){
console.log('Got wet for ' + device.name)
message = {name: device.name, id: device.deviceID, deviceType: device.deviceType, state: 'wet'}
if(ws.isAlive){ws.send(JSON.stringify(message))}
})
break
case 'motionsensor':
device.motion = hub.motion(device.deviceID)
device.motion.on('inactive', function(){
console.log('Got inactive for ' + device.name)
message = {name: device.name, id: device.deviceID, deviceType: device.deviceType, state: 'inactive'}
if(ws.isAlive){ws.send(JSON.stringify(message))}
})
device.motion.on('active', function(){
console.log('Got active for ' + device.name)
message = {name: device.name, id: device.deviceID, deviceType: device.deviceType, state: 'active'}
if(ws.isAlive){ws.send(JSON.stringify(message))}
})
break
case 'switch':
device.light = hub.light(device.deviceID)
device.light.on('turnOn', function (group, level) {
console.log(device.name + ' turned on')
message = {name: device.name, id: device.deviceID, deviceType: device.deviceType, state: level}
if(ws.isAlive){ws.send(JSON.stringify(message))}
})
device.light.on('turnOff', function () {
console.log(device.name + ' turned off')
message = {name: device.name, id: device.deviceID, deviceType: device.deviceType, state: 0}
if(ws.isAlive){ws.send(JSON.stringify(message))}
})
break
case 'lightbulb':
case 'dimmer':
device.light = hub.light(device.deviceID)
device.light.level().then(function(level) {
message = {name: device.name, id: device.deviceID, deviceType: device.deviceType, state: level}
if(ws.isAlive){ws.send(JSON.stringify(message))}
})
break
}
})
eventListener()
function eventListener() {
console.log('Insteon event listener started...')
hub.on('command', function(data) {
if (typeof data.standard !== 'undefined') {
//console.log('Received command for ' + data.standard.id)
var info = JSON.stringify(data)
var id = data.standard.id.toUpperCase()
var command1 = data.standard.command1
var command2 = data.standard.command2
var messageType = data.standard.messageType
var isDevice = _.contains(deviceIDs, id, 0)
var message
if (isDevice) {
var foundDevices = devices.filter(function(item) {
return item.deviceID == id
})
console.log('Found ' + foundDevices.length + ' accessories matching ' + id)
console.log('Hub command: ' + info)
for (var i = 0, len = foundDevices.length; i < len; i++) {
var foundDevice = foundDevices[i]
console.log('Got event for ' + foundDevice.name + ' (' + foundDevice.deviceID + ')')
switch (foundDevice.deviceType) {
case 'lightbulb':
case 'dimmer':
if (command1 == '19' || command1 == '03' || command1 == '04' || (command1 == '00' && command2 != '00') || (command1 == '06' && messageType == '1')) { //19 = status
var level_int = parseInt(command2, 16) * (100 / 255)
var level = Math.ceil(level_int)
console.log('Got updated status for ' + foundDevice.name)
message = {name: foundDevice.name, id: foundDevice.deviceID, deviceType: foundDevice.deviceType, state: level}
if(ws.isAlive){ws.send(JSON.stringify(message))}
}
if (command1 == 11) { //11 = on
var level_int = parseInt(command2, 16)*(100/255)
var level = Math.ceil(level_int)
console.log('Got on event for ' + foundDevice.name)
message = {name: foundDevice.name, id: foundDevice.deviceID, deviceType: foundDevice.deviceType, state: level}
if(ws.isAlive){ws.send(JSON.stringify(message))}
}
if (command1 == 12) { //fast on
console.log('Got fast on event for ' + foundDevice.name)
message = {name: foundDevice.name, id: foundDevice.deviceID, deviceType: foundDevice.deviceType, state: 100}
if(ws.isAlive){ws.send(JSON.stringify(message))}
}
if (command1 == 13 || command1 == 14) { //13 = off, 14= fast off
if (command1 == 13) {
console.log('Got off event for ' + foundDevice.name)
} else {console.log('Got fast off event for ' + foundDevice.name)}
message = {name: foundDevice.name, id: foundDevice.deviceID, deviceType: foundDevice.deviceType, state: 0}
if(ws.isAlive){ws.send(JSON.stringify(message))}
}
if (command1 == 18) { //stop dimming
console.log('Got dim event for ' + foundDevice.name)
foundDevice.light.level().then(function(level) {
message = {name: foundDevice.name, id: foundDevice.deviceID, deviceType: foundDevice.deviceType, state: level}
if(ws.isAlive){ws.send(JSON.stringify(message))}
})
}
break
}
}
}
}
})
}
}
)}
}
ws parent (with motion sensor - no keypad support)
/**
* 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 == "motionsensor") {childMotion(child, 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 childMotion(child, state){
if (logEnable) log.debug "child motion = ${child}"
if (logEnable) log.debug "child state = ${state}"
child.sendEvent(name: "motion", value: 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"
}
}
motion sensor child
/**
* Insteon Motion Child
*
* Copyright 2022 Logname
*
* 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 Motion Child", namespace: "ln", author: "logname") {
capability "Motion Sensor"
command "active"
command "inactive"
}
}
def installed() {
}
def open() {
sendEvent(name: "motion", value: "active")
}
def close() {
sendEvent(name: "motion", value: "inactive")
}