diff --git a/bundles/org.openhab.binding.insteon/README.md b/bundles/org.openhab.binding.insteon/README.md index 0e11905ee490c..5cf73d2f18c03 100644 --- a/bundles/org.openhab.binding.insteon/README.md +++ b/bundles/org.openhab.binding.insteon/README.md @@ -111,7 +111,7 @@ These have been tested and should work out of the box: ## Channels Below is the list of possible channels for the Insteon devices. -In order to determine which channels a device supports, you can look at the device in the UI. +In order to determine which channels a device supports, you can look at the device in the UI, or with the command `display_devices` in the console. | channel | type | description | |----------|--------|------------------------------| @@ -190,7 +190,7 @@ In order to determine which channels a device supports, you can look at the devi Sample things file: ``` -Bridge insteon:network:local [port="/dev/ttyUSB0"] { +Bridge insteon:network:home [port="/dev/ttyUSB0"] { Thing device 22F8A8 [address="22.F8.A8", productKey="F00.00.15"] { Channels: Type switch : keypadButtonA [ group=3 ] @@ -221,18 +221,32 @@ Bridge insteon:network:local [port="/dev/ttyUSB0"] { Sample items file: ``` -Switch switch1 { channel="insteon:device:local:243141:switch" } -Dimmer dimmer1 { channel="insteon:device:local:238F55:dimmer" } -Dimmer dimmer2 { channel="insteon:device:local:23B0D9:dimmer" } -Dimmer dimmer3 { channel="insteon:device:local:238FC9:dimmer" } -Dimmer keypad { channel="insteon:device:local:22F8A8:loadDimmer" } -Switch keypadA { channel="insteon:device:local:22F8A8:keypadButtonA" } -Switch keypadB { channel="insteon:device:local:22F8A8:keypadButtonB" } -Switch keypadC { channel="insteon:device:local:22F8A8:keypadButtonC" } -Switch keypadD { channel="insteon:device:local:22F8A8:keypadButtonD" } -Dimmer dimmer { channel="insteon:device:local:238D93:dimmer" } +Switch switch1 { channel="insteon:device:home:243141:switch" } +Dimmer dimmer1 { channel="insteon:device:home:238F55:dimmer" } +Dimmer dimmer2 { channel="insteon:device:home:23B0D9:dimmer" } +Dimmer dimmer3 { channel="insteon:device:home:238FC9:dimmer" } +Dimmer keypad { channel="insteon:device:home:22F8A8:loadDimmer" } +Switch keypadA { channel="insteon:device:home:22F8A8:keypadButtonA" } +Switch keypadB { channel="insteon:device:home:22F8A8:keypadButtonB" } +Switch keypadC { channel="insteon:device:home:22F8A8:keypadButtonC" } +Switch keypadD { channel="insteon:device:home:22F8A8:keypadButtonD" } +Dimmer dimmer { channel="insteon:device:home:238D93:dimmer" } ``` +## Console Commands + +The binding provides commands you can use to help with troubleshooting. +Enter `smarthome:insteon` in the console and you will get a list of available commands. + +``` +openhab> smarthome:insteon +Usage: smarthome:insteon display_devices - display devices that are online, along with available channels +Usage: smarthome:insteon display_channels - display channels that are linked, along with configuration information +Usage: smarthome:insteon display_local_database - display Insteon PLM or hub database details +``` + +Here is an example of command: `smarthome:insteon display_local_database`. + ## Insteon Groups and Scenes How do Insteon devices tell other devices on the network that their state has changed? They send out a broadcast message, labeled with a specific *group* number. @@ -267,8 +281,8 @@ Since Insteon devices can have multiple features (for instance a switchable rela For example, the following lines would create two Number items referring to the same thermostat device, but to different features of it: ``` -Number thermostatCoolPoint "cool point [%.1f °F]" { channel="insteon:device:local:32F422:coolSetPoint" } -Number thermostatHeatPoint "heat point [%.1f °F]" { channel="insteon:device:local:32F422:heatSetPoint" } +Number thermostatCoolPoint "cool point [%.1f °F]" { channel="insteon:device:home:32F422:coolSetPoint" } +Number thermostatHeatPoint "heat point [%.1f °F]" { channel="insteon:device:home:32F422:heatSetPoint" } ``` ### Simple Light Switches @@ -276,7 +290,7 @@ Number thermostatHeatPoint "heat point [%.1f °F]" { channel="insteon:device:lo The following example shows how to configure a simple light switch (2477S) in the .items file: ``` -Switch officeLight "office light" { channel="insteon:device:local:AABBCC:switch" } +Switch officeLight "office light" { channel="insteon:device:home:AABBCC:switch" } ``` ### Simple Dimmers @@ -284,7 +298,7 @@ Switch officeLight "office light" { channel="insteon:device:local:AABBCC:switch Here is how to configure a simple dimmer (2477D) in the .items file: ``` -Dimmer kitchenChandelier "kitchen chandelier" { channel="insteon:device:local:AABBCC:dimmer" } +Dimmer kitchenChandelier "kitchen chandelier" { channel="insteon:device:home:AABBCC:dimmer" } ``` Dimmers can be configured with a maximum level when turning a device on or setting a percentage level. @@ -295,7 +309,7 @@ The below example sets a maximum level of 70% for dim 1 and 60% for dim 2: **Things** ``` -Bridge insteon:network:local [port="/dev/ttyUSB0"] { +Bridge insteon:network:home [port="/dev/ttyUSB0"] { Thing device AABBCC [address="AA.BB.CC", productKey="F00.00.11"] { Channels: Type dimmer : dimmer [dimmermax=70] @@ -310,8 +324,8 @@ Bridge insteon:network:local [port="/dev/ttyUSB0"] { **Items** ``` -Dimmer d1 "dimmer 1" { channel="insteon:device:local:AABBCC:dimmer"} -Dimmer d2 "dimmer 2" { channel="insteon:device:local:AABBCD:loadDimmer"} +Dimmer d1 "dimmer 1" { channel="insteon:device:home:AABBCC:dimmer"} +Dimmer d2 "dimmer 2" { channel="insteon:device:home:AABBCD:loadDimmer"} ``` Setting a maximum level does not affect manual turning on or dimming a switch. @@ -321,8 +335,8 @@ Setting a maximum level does not affect manual turning on or dimming a switch. Here's how to configure the top and bottom outlet of the in-wall 2 outlet controller: ``` -Switch fOutTop "Front Outlet Top" { channel="insteon:device:local:AABBCC:topOutlet" } -Switch fOutBot "Front Outlet Bottom" { channel="insteon:device:local:AABBCC:bottomOutlet" } +Switch fOutTop "Front Outlet Top" { channel="insteon:device:home:AABBCC:topOutlet" } +Switch fOutBot "Front Outlet Bottom" { channel="insteon:device:home:AABBCC:bottomOutlet" } ``` This will give you individual control of each outlet. @@ -345,10 +359,10 @@ The modem's link database (see [Insteon Terminal](https://github.com/pfrommerd/i This goes into the items file: ``` - Switch miniRemoteButtonA "mini remote button a" { channel="insteon:device:local:AABBCC:buttonA", autoupdate="false" } - Switch miniRemoteButtonB "mini remote button b" { channel="insteon:device:local:AABBCC:buttonB", autoupdate="false" } - Switch miniRemoteButtonC "mini remote button c" { channel="insteon:device:local:AABBCC:buttonC", autoupdate="false" } - Switch miniRemoteButtonD "mini remote button d" { channel="insteon:device:local:AABBCC:buttonD", autoupdate="false" } + Switch miniRemoteButtonA "mini remote button a" { channel="insteon:device:home:AABBCC:buttonA", autoupdate="false" } + Switch miniRemoteButtonB "mini remote button b" { channel="insteon:device:home:AABBCC:buttonB", autoupdate="false" } + Switch miniRemoteButtonC "mini remote button c" { channel="insteon:device:home:AABBCC:buttonC", autoupdate="false" } + Switch miniRemoteButtonD "mini remote button d" { channel="insteon:device:home:AABBCC:buttonD", autoupdate="false" } ``` **Sitemap** @@ -373,9 +387,9 @@ Then create entries in the .items file like this: **Items** ``` - Contact motionSensor "motion sensor [MAP(contact.map):%s]" { channel="insteon:device:local:AABBCC:contact"} - Number motionSensorBatteryLevel "motion sensor battery level [%.1f]" { channel="insteon:device:local:AABBCC:batteryLevel" } - Number motionSensorLightLevel "motion sensor light level [%.1f]" { channel="insteon:device:local:AABBCC:lightLevel" } + Contact motionSensor "motion sensor [MAP(contact.map):%s]" { channel="insteon:device:home:AABBCC:contact"} + Number motionSensorBatteryLevel "motion sensor battery level [%.1f]" { channel="insteon:device:home:AABBCC:batteryLevel" } + Number motionSensorLightLevel "motion sensor light level [%.1f]" { channel="insteon:device:home:AABBCC:lightLevel" } ``` This will give you a contact, the battery level, and the light level. @@ -397,8 +411,8 @@ Create a contact.map file in the transforms directory like the following: Then create entries in the .items file like this: ``` - Contact doorSensor "Door sensor [MAP(contact.map):%s]" { channel="insteon:device:local:AABBCC:contact" } - Number doorSensorBatteryLevel "Door sensor battery level [%.1f]" { channel="insteon:device:local:AABBCC:batteryLevel" } + Contact doorSensor "Door sensor [MAP(contact.map):%s]" { channel="insteon:device:home:AABBCC:contact" } + Number doorSensorBatteryLevel "Door sensor battery level [%.1f]" { channel="insteon:device:home:AABBCC:batteryLevel" } ``` This will give you a contact and the battery level. @@ -412,7 +426,7 @@ Read the instructions very carefully: sync with lock within 5 feet to avoid bad Put something like this into your .items file: ``` - Switch doorLock "Front Door [MAP(lock.map):%s]" { channel="insteon:device:local:AABBCC:switch" } + Switch doorLock "Front Door [MAP(lock.map):%s]" { channel="insteon:device:home:AABBCC:switch" } ``` and create a file "lock.map" in the transforms directory with these entries: @@ -440,8 +454,8 @@ Add this map into your transforms directory as "contact.map": Along with this into your .items file: ``` - Switch garageDoorOpener "garage door opener" { channel="insteon:device:local:AABBCC:switch", autoupdate="false" } - Contact garageDoorContact "garage door contact [MAP(contact.map):%s]" { channel="insteon:device:local:AABBCC:contact" } + Switch garageDoorOpener "garage door opener" { channel="insteon:device:home:AABBCC:switch", autoupdate="false" } + Contact garageDoorContact "garage door contact [MAP(contact.map):%s]" { channel="insteon:device:home:AABBCC:contact" } ``` **Sitemap** @@ -498,9 +512,9 @@ In your items file you specify these groups with the "group=" parameters such th Here is a simple example, just using the load (main) switch: ``` - Switch keypadSwitch "main load" { channel="insteon:device:local:AABBCC:loadSwitch" } - Number keypadSwitchManualChange "main manual change" { channel="insteon:device:local:AABBCC:loadSwitchManualChange" } - Switch keypadSwitchFastOnOff "main fast on/off" { channel="insteon:device:local:AABBCC:loadSwitchFastOnOff" } + Switch keypadSwitch "main load" { channel="insteon:device:home:AABBCC:loadSwitch" } + Number keypadSwitchManualChange "main manual change" { channel="insteon:device:home:AABBCC:loadSwitchManualChange" } + Switch keypadSwitchFastOnOff "main fast on/off" { channel="insteon:device:home:AABBCC:loadSwitchFastOnOff" } ``` Most people will not use the fast on/off features or the manual change feature, so you really only need the first line. @@ -509,7 +523,7 @@ To make the buttons available, add the following: **Things** ``` -Bridge insteon:network:local [port="/dev/ttyUSB0"] { +Bridge insteon:network:home [port="/dev/ttyUSB0"] { Thing device AABBCC [address="AA.BB.CC", productKey="F00.00.15"] { Channels: Type switch : keypadButtonA [ group="0xf3" ] @@ -526,10 +540,10 @@ The hexadecimal value 0xf3 can either converted to a numeric value 243 or the st **Items** ``` - Switch keypadSwitchA "keypad button A" { channel="insteon:device:local:AABBCC:keypadButtonA"} - Switch keypadSwitchB "keypad button B" { channel="insteon:device:local:AABBCC:keypadButtonB"} - Switch keypadSwitchC "keypad button C" { channel="insteon:device:local:AABBCC:keypadButtonC"} - Switch keypadSwitchD "keypad button D" { channel="insteon:device:local:AABBCC:keypadButtonD"} + Switch keypadSwitchA "keypad button A" { channel="insteon:device:home:AABBCC:keypadButtonA"} + Switch keypadSwitchB "keypad button B" { channel="insteon:device:home:AABBCC:keypadButtonB"} + Switch keypadSwitchC "keypad button C" { channel="insteon:device:home:AABBCC:keypadButtonC"} + Switch keypadSwitchD "keypad button D" { channel="insteon:device:home:AABBCC:keypadButtonD"} ``` **Sitemap** @@ -555,8 +569,8 @@ The keypad dimmers are like keypad switches, except that the main load is dimmab **Items** ``` - Dimmer keypadDimmer "dimmer" { channel="insteon:device:local:AABBCC:loadDimmer" } - Switch keypadDimmerButtonA "keypad dimmer button A [%d %%]" { channel="insteon:device:local:AABBCC:keypadButtonA" } + Dimmer keypadDimmer "dimmer" { channel="insteon:device:home:AABBCC:loadDimmer" } + Switch keypadDimmerButtonA "keypad dimmer button A [%d %%]" { channel="insteon:device:home:AABBCC:keypadButtonA" } ``` **Sitemap** @@ -582,25 +596,25 @@ Again, refer to the [Insteon Terminal](https://github.com/pfrommerd/insteon-term This is an example of what to put into your .items file: ``` - Number thermostatCoolPoint "cool point [%.1f °F]" { channel="insteon:device:local:AABBCC:coolSetPoint" } - Number thermostatHeatPoint "heat point [%.1f °F]" { channel="insteon:device:local:AABBCC:heatSetPoint" } - Number thermostatSystemMode "system mode [%d]" { channel="insteon:device:local:AABBCC:systemMode" } - Number thermostatFanMode "fan mode [%d]" { channel="insteon:device:local:AABBCC:fanMode" } - Number thermostatIsHeating "is heating [%d]" { channel="insteon:device:local:AABBCC:isHeating"} - Number thermostatIsCooling "is cooling [%d]" { channel="insteon:device:local:AABBCC:isCooling" } - Number thermostatTempFahren "temperature [%.1f °F]" { channel="insteon:device:local:AABBCC:tempFahrenheit" } - Number thermostatTempCelsius "temperature [%.1f °C]" { channel="insteon:device:local:AABBCC:tempCelsius" } - Number thermostatHumidity "humidity [%.0f %%]" { channel="insteon:device:local:AABBCC:humidity" } + Number thermostatCoolPoint "cool point [%.1f °F]" { channel="insteon:device:home:AABBCC:coolSetPoint" } + Number thermostatHeatPoint "heat point [%.1f °F]" { channel="insteon:device:home:AABBCC:heatSetPoint" } + Number thermostatSystemMode "system mode [%d]" { channel="insteon:device:home:AABBCC:systemMode" } + Number thermostatFanMode "fan mode [%d]" { channel="insteon:device:home:AABBCC:fanMode" } + Number thermostatIsHeating "is heating [%d]" { channel="insteon:device:home:AABBCC:isHeating"} + Number thermostatIsCooling "is cooling [%d]" { channel="insteon:device:home:AABBCC:isCooling" } + Number thermostatTempFahren "temperature [%.1f °F]" { channel="insteon:device:home:AABBCC:tempFahrenheit" } + Number thermostatTempCelsius "temperature [%.1f °C]" { channel="insteon:device:home:AABBCC:tempCelsius" } + Number thermostatHumidity "humidity [%.0f %%]" { channel="insteon:device:home:AABBCC:humidity" } ``` Add this as well for some more exotic features: ``` - Number thermostatACDelay "A/C delay [%d min]" { channel="insteon:device:local:AABBCC:acDelay" } - Number thermostatBacklight "backlight [%d sec]" { channel="insteon:device:local:AABBCC:backlightDuration" } - Number thermostatStage1 "A/C stage 1 time [%d min]" { channel="insteon:device:local:AABBCC:stage1Duration" } - Number thermostatHumidityHigh "humidity high [%d %%]" { channel="insteon:device:local:AABBCC:humidityHigh" } - Number thermostatHumidityLow "humidity low [%d %%]" { channel="insteon:device:local:AABBCC:humidityLow" } + Number thermostatACDelay "A/C delay [%d min]" { channel="insteon:device:home:AABBCC:acDelay" } + Number thermostatBacklight "backlight [%d sec]" { channel="insteon:device:home:AABBCC:backlightDuration" } + Number thermostatStage1 "A/C stage 1 time [%d min]" { channel="insteon:device:home:AABBCC:stage1Duration" } + Number thermostatHumidityHigh "humidity high [%d %%]" { channel="insteon:device:home:AABBCC:humidityHigh" } + Number thermostatHumidityLow "humidity low [%d %%]" { channel="insteon:device:home:AABBCC:humidityLow" } ``` **Sitemap** @@ -633,10 +647,10 @@ See the example below: **Items** ``` - Number iMeterWatts "iMeter [%d watts]" { channel="insteon:device:local:AABBCC:watts" } - Number iMeterKwh "iMeter [%.04f kwh]" { channel="insteon:device:local:AABBCC:kwh" } - Switch iMeterUpdate "iMeter Update" { channel="insteon:device:local:AABBCC:update" } - Switch iMeterReset "iMeter Reset" { channel="insteon:device:local:AABBCC:reset" } + Number iMeterWatts "iMeter [%d watts]" { channel="insteon:device:home:AABBCC:watts" } + Number iMeterKwh "iMeter [%.04f kwh]" { channel="insteon:device:home:AABBCC:kwh" } + Switch iMeterUpdate "iMeter Update" { channel="insteon:device:home:AABBCC:update" } + Switch iMeterReset "iMeter Reset" { channel="insteon:device:home:AABBCC:reset" } ``` ### Fan Controllers @@ -646,8 +660,8 @@ Here is an example configuration for a FanLinc module, which has a dimmable ligh **Items** ``` - Dimmer fanLincDimmer "fanlinc dimmer [%d %%]" { channel="insteon:device:local:AABBCC:lightDimmer" } - Number fanLincFan "fanlinc fan" { channel="insteon:device:local:AABBCC:fan"} + Dimmer fanLincDimmer "fanlinc dimmer [%d %%]" { channel="insteon:device:home:AABBCC:lightDimmer" } + Number fanLincFan "fanlinc fan" { channel="insteon:device:home:AABBCC:fan"} ``` **Sitemap** @@ -668,9 +682,9 @@ Further note that X10 devices are addressed with `houseCode.unitCode`, e.g. `A.2 **Items** ``` - Switch x10Switch "X10 switch" { channel="insteon:device:local:AABB:switch" } - Dimmer x10Dimmer "X10 dimmer" { channel="insteon:device:local:AABB:dimmer" } - Contact x10Motion "X10 motion" { channel="insteon:device:local:AABB:contact" } + Switch x10Switch "X10 switch" { channel="insteon:device:home:AABB:switch" } + Dimmer x10Dimmer "X10 dimmer" { channel="insteon:device:home:AABB:dimmer" } + Contact x10Motion "X10 motion" { channel="insteon:device:home:AABB:contact" } ``` ## Direct Sending of Group Broadcasts (Triggering Scenes) @@ -682,7 +696,7 @@ The format is broadcastOnOff#X where X is the group that you want to be able to **Things** ``` -Bridge insteon:network:local [port="/dev/ttyUSB0"] { +Bridge insteon:network:home [port="/dev/ttyUSB0"] { Thing device AABBCC [address="AA.BB.CC", productKey="0x000045"] { Channels: Type switch : broadcastOnOff#2 @@ -694,7 +708,7 @@ Bridge insteon:network:local [port="/dev/ttyUSB0"] { **Items** ``` - Switch broadcastOnOff "group on/off" { channel="insteon:device:local:AABBCC:broadcastOnOff#2" } + Switch broadcastOnOff "group on/off" { channel="insteon:device:home:AABBCC:broadcastOnOff#2" } ``` Flipping this switch to "ON" will cause the modem to send a broadcast message with group=2, and all devices that are configured to respond to it should react. @@ -708,7 +722,7 @@ In this scenario, the "related" keyword can be used to have the binding poll a r A typical example would be two dimmers (A and B) in a 3-way configuration: ``` -Bridge insteon:network:local [port="/dev/ttyUSB0"] { +Bridge insteon:network:home [port="/dev/ttyUSB0"] { Thing device AABBCC [address="AA.BB.CC", productKey="F00.00.11"] { Channels: Type dimmer : dimmer [related="AA.BB.DD"] @@ -725,7 +739,7 @@ In this scenario, the "related" keyword can be used to have the binding poll one A typical example would be a switch configured to broadcast to a group, and one or more devices configured to respond to the message: ``` -Bridge insteon:network:local [port="/dev/ttyUSB0"] { +Bridge insteon:network:home [port="/dev/ttyUSB0"] { Thing device AABBCC [address="A.BB.CC", productKey="0x000045"] { Channels: Type switch : broadcastOnOff#3 [related="AA.BB.DD"] diff --git a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/InsteonBinding.java b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/InsteonBinding.java index 86ecdbbfebebc..6af3409683aa8 100644 --- a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/InsteonBinding.java +++ b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/InsteonBinding.java @@ -15,10 +15,12 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; @@ -44,7 +46,7 @@ import org.openhab.binding.insteon.internal.driver.DriverListener; import org.openhab.binding.insteon.internal.driver.ModemDBEntry; import org.openhab.binding.insteon.internal.driver.Poller; -import org.openhab.binding.insteon.internal.handler.InsteonDeviceHandler; +import org.openhab.binding.insteon.internal.driver.Port; import org.openhab.binding.insteon.internal.handler.InsteonNetworkHandler; import org.openhab.binding.insteon.internal.message.FieldException; import org.openhab.binding.insteon.internal.message.Msg; @@ -309,7 +311,7 @@ private int checkIfInModemDatabase(InsteonDevice dev) { HashMap dbes = driver.lockModemDBEntries(); if (dbes.containsKey(addr)) { if (!dev.hasModemDBEntry()) { - logger.debug("device {} found in the modem database and {}.", addr, getLinkInfo(dbes, addr)); + logger.debug("device {} found in the modem database and {}.", addr, getLinkInfo(dbes, addr, true)); dev.setHasModemDBEntry(true); } } else { @@ -323,6 +325,21 @@ private int checkIfInModemDatabase(InsteonDevice dev) { } } + public Map getDatabaseInfo() { + try { + Map databaseInfo = new HashMap<>(); + HashMap dbes = driver.lockModemDBEntries(); + for (InsteonAddress addr : dbes.keySet()) { + String a = addr.toString(); + databaseInfo.put(a, a + ": " + getLinkInfo(dbes, addr, false)); + } + + return databaseInfo; + } finally { + driver.unlockModemDBEntries(); + } + } + /** * Everything below was copied from Insteon PLM v1 */ @@ -350,26 +367,33 @@ public void shutdown() { return (dev); } - private String getLinkInfo(HashMap dbes, InsteonAddress a) { + private String getLinkInfo(HashMap dbes, InsteonAddress a, boolean prefix) { ModemDBEntry dbe = dbes.get(a); ArrayList controls = dbe.getControls(); ArrayList responds = dbe.getRespondsTo(); - StringBuilder buf = new StringBuilder("the modem"); - if (!controls.isEmpty()) { - buf.append(" controls groups ["); - buf.append(toGroupString(controls)); - buf.append("]"); - } - - if (!responds.isEmpty()) { - if (!controls.isEmpty()) { - buf.append(" and"); + Port port = dbe.getPort(); + String deviceName = port.getDeviceName(); + String s = deviceName.startsWith("/hub") ? "hub" : "plm"; + StringBuilder buf = new StringBuilder(); + if (port.isModem(a)) { + if (prefix) { + buf.append("it is the "); } - - buf.append(" responds to groups ["); + buf.append(s); + buf.append(" ("); + buf.append(Utils.redactPassword(deviceName)); + buf.append(")"); + } else { + if (prefix) { + buf.append("the "); + } + buf.append(s); + buf.append(" controls groups ("); + buf.append(toGroupString(controls)); + buf.append(") and responds to groups ("); buf.append(toGroupString(responds)); - buf.append("]"); + buf.append(")"); } return buf.toString(); @@ -377,15 +401,21 @@ private String getLinkInfo(HashMap dbes, private String toGroupString(ArrayList group) { ArrayList sorted = new ArrayList<>(group); - Collections.sort(sorted); + Collections.sort(sorted, new Comparator() { + @Override + public int compare(Byte b1, Byte b2) { + int i1 = b1 & 0xFF; + int i2 = b2 & 0xFF; + return i1 < i2 ? -1 : i1 == i2 ? 0 : 1; + } + }); StringBuilder buf = new StringBuilder(); for (Byte b : sorted) { if (buf.length() > 0) { buf.append(","); } - buf.append("0x"); - buf.append(Utils.getHexString(b)); + buf.append(b & 0xFF); } return buf.toString(); @@ -450,7 +480,8 @@ public void driverCompletelyInitialized() { } else { if (!dev.hasModemDBEntry()) { addrs.add(a); - logger.debug("device {} found in the modem database and {}.", a, getLinkInfo(dbes, a)); + logger.debug("device {} found in the modem database and {}.", a, + getLinkInfo(dbes, a, true)); dev.setHasModemDBEntry(true); } if (dev.getStatus() != DeviceStatus.POLLING) { @@ -462,7 +493,7 @@ public void driverCompletelyInitialized() { for (InsteonAddress k : dbes.keySet()) { if (!addrs.contains(k) && !k.equals(dbes.get(k).getPort().getAddress())) { logger.debug("device {} found in the modem database, but is not configured as a thing and {}.", - k, getLinkInfo(dbes, k)); + k, getLinkInfo(dbes, k, true)); missing.add(k.toString()); } diff --git a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/InsteonHandlerFactory.java b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/InsteonHandlerFactory.java index a025726b1efc3..61683f39b3ad6 100644 --- a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/InsteonHandlerFactory.java +++ b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/InsteonHandlerFactory.java @@ -54,6 +54,7 @@ public class InsteonHandlerFactory extends BaseThingHandlerFactory { .unmodifiableSet(Stream.of(DEVICE_THING_TYPE, NETWORK_THING_TYPE).collect(Collectors.toSet())); private final Map> discoveryServiceRegs = new HashMap<>(); + private final Map> serviceRegs = new HashMap<>(); private @Nullable SerialPortManager serialPortManager; @@ -77,7 +78,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { if (NETWORK_THING_TYPE.equals(thingTypeUID)) { InsteonNetworkHandler insteonNetworkHandler = new InsteonNetworkHandler((Bridge) thing, serialPortManager); - registerDeviceDiscoveryService(insteonNetworkHandler); + registerServices(insteonNetworkHandler); return insteonNetworkHandler; } else if (DEVICE_THING_TYPE.equals(thingTypeUID)) { @@ -90,14 +91,23 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { @Override protected synchronized void removeHandler(ThingHandler thingHandler) { if (thingHandler instanceof InsteonNetworkHandler) { - ServiceRegistration serviceReg = this.discoveryServiceRegs.remove(thingHandler.getThing().getUID()); - if (serviceReg != null) { - serviceReg.unregister(); + ThingUID uid = thingHandler.getThing().getUID(); + ServiceRegistration serviceRegs = this.serviceRegs.remove(uid); + if (serviceRegs != null) { + serviceRegs.unregister(); + } + + ServiceRegistration discoveryServiceRegs = this.discoveryServiceRegs.remove(uid); + if (discoveryServiceRegs != null) { + discoveryServiceRegs.unregister(); } } } - private synchronized void registerDeviceDiscoveryService(InsteonNetworkHandler handler) { + private synchronized void registerServices(InsteonNetworkHandler handler) { + this.serviceRegs.put(handler.getThing().getUID(), + bundleContext.registerService(InsteonNetworkHandler.class.getName(), handler, new Hashtable<>())); + InsteonDeviceDiscoveryService discoveryService = new InsteonDeviceDiscoveryService(handler); this.discoveryServiceRegs.put(handler.getThing().getUID(), bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>())); diff --git a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/command/InsteonCommandExtension.java b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/command/InsteonCommandExtension.java new file mode 100644 index 0000000000000..99e7d76bb80d6 --- /dev/null +++ b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/command/InsteonCommandExtension.java @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.insteon.internal.command; + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.io.console.Console; +import org.eclipse.smarthome.io.console.extensions.AbstractConsoleCommandExtension; +import org.eclipse.smarthome.io.console.extensions.ConsoleCommandExtension; +import org.openhab.binding.insteon.internal.handler.InsteonNetworkHandler; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; + +/** + * + * Console commands for the Insteon binding + * + * @author Rob Nielsen - Initial contribution + */ +@NonNullByDefault +@Component(service = ConsoleCommandExtension.class) +public class InsteonCommandExtension extends AbstractConsoleCommandExtension { + private static final String DISPLAY_DEVICES = "display_devices"; + private static final String DISPLAY_CHANNELS = "display_channels"; + private static final String DISPLAY_LOCAL_DATABASE = "display_local_database"; + + @Nullable + private InsteonNetworkHandler handler; + + public InsteonCommandExtension() { + super("insteon", "Interact with the Insteon integration."); + } + + @Override + public void execute(String[] args, Console console) { + if (args.length > 0) { + InsteonNetworkHandler handler = this.handler; // fix eclipse warnings about nullable + if (handler == null) { + console.println("No Insteon network bridge configured."); + printUsage(console); + } else { + String subCommand = args[0]; + switch (subCommand) { + case DISPLAY_DEVICES: + handler.displayDevices(console); + break; + case DISPLAY_CHANNELS: + handler.displayChannels(console); + break; + case DISPLAY_LOCAL_DATABASE: + handler.displayLocalDatabase(console); + break; + default: + console.println("Unknown command '" + subCommand + "'"); + printUsage(console); + break; + } + } + } else { + printUsage(console); + } + } + + @Override + public List getUsages() { + return Arrays.asList(new String[] { + buildCommandUsage(DISPLAY_DEVICES, "display devices that are online, along with available channels"), + buildCommandUsage(DISPLAY_CHANNELS, + "display channels that are linked, along with configuration information"), + buildCommandUsage(DISPLAY_LOCAL_DATABASE, "display Insteon PLM or hub database details") }); + } + + @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY) + public void setInsteonNetworkHandler(InsteonNetworkHandler handler) { + this.handler = handler; + } + + public void unsetInsteonNetworkHandler(InsteonNetworkHandler handler) { + this.handler = null; + } +} diff --git a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/driver/Port.java b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/driver/Port.java index 1aff089531431..53a5e8f222392 100644 --- a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/driver/Port.java +++ b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/driver/Port.java @@ -102,6 +102,10 @@ public Port(String devName, Driver d, @Nullable SerialPortManager serialPortMana this.mdbb = new ModemDBBuilder(this); } + public boolean isModem(InsteonAddress a) { + return modem.getAddress().equals(a); + } + public synchronized boolean isModemDBComplete() { return (modemDBComplete); } diff --git a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/handler/InsteonDeviceHandler.java b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/handler/InsteonDeviceHandler.java index ec78a6f9f8b67..ac5efe288706b 100644 --- a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/handler/InsteonDeviceHandler.java +++ b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/handler/InsteonDeviceHandler.java @@ -217,8 +217,17 @@ public void initialize() { if (!channels.isEmpty()) { updateThing(editThing().withChannels(channels).build()); - logger.debug("{} address = {} productKey = {} channels = {}", thingId, address, productKey, - channelList.toString()); + StringBuilder builder = new StringBuilder(thingId); + builder.append(" address = "); + builder.append(address); + builder.append(" productKey = "); + builder.append(productKey); + builder.append(" channels = "); + builder.append(channelList.toString()); + String msg = builder.toString(); + logger.debug("{}", msg); + + getInsteonNetworkHandler().initialized(getThing().getUID(), msg); updateStatus(ThingStatus.ONLINE); } else { @@ -250,6 +259,8 @@ public void dispose() { logger.debug("removed {} address = {}", getThing().getUID().getAsString(), address); } + getInsteonNetworkHandler().disposed(getThing().getUID()); + super.dispose(); } @@ -322,18 +333,30 @@ public void channelLinked(ChannelUID channelUID) { new InsteonAddress(config.getAddress()), productKey, params); getInsteonBinding().addFeatureListener(bindingConfig); - logger.debug("channel {} linked with the feature: {} parameters: {}", channelUID.getAsString(), feature, - params); + StringBuilder builder = new StringBuilder(channelUID.getAsString()); + builder.append(" feature = "); + builder.append(feature); + builder.append(" parameters = "); + builder.append(params); + String msg = builder.toString(); + logger.debug("{}", msg); + + getInsteonNetworkHandler().linked(channelUID, msg); } @Override public void channelUnlinked(ChannelUID channelUID) { getInsteonBinding().removeFeatureListener(channelUID); + getInsteonNetworkHandler().unlinked(channelUID); logger.debug("channel {} unlinked ", channelUID.getAsString()); } + private @Nullable InsteonNetworkHandler getInsteonNetworkHandler() { + return (InsteonNetworkHandler) getBridge().getHandler(); + } + private @Nullable InsteonBinding getInsteonBinding() { - return ((InsteonNetworkHandler) getBridge().getHandler()).getInsteonBinding(); + return getInsteonNetworkHandler().getInsteonBinding(); } } diff --git a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/handler/InsteonNetworkHandler.java b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/handler/InsteonNetworkHandler.java index e673f82c15e5b..9d74c571c1b56 100644 --- a/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/handler/InsteonNetworkHandler.java +++ b/bundles/org.openhab.binding.insteon/src/main/java/org/openhab/binding/insteon/internal/handler/InsteonNetworkHandler.java @@ -12,7 +12,11 @@ */ package org.openhab.binding.insteon.internal.handler; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -22,9 +26,11 @@ import org.eclipse.smarthome.core.thing.ChannelUID; import org.eclipse.smarthome.core.thing.ThingStatus; import org.eclipse.smarthome.core.thing.ThingStatusDetail; +import org.eclipse.smarthome.core.thing.ThingUID; import org.eclipse.smarthome.core.thing.binding.BaseBridgeHandler; import org.eclipse.smarthome.core.types.Command; import org.eclipse.smarthome.core.types.State; +import org.eclipse.smarthome.io.console.Console; import org.eclipse.smarthome.io.transport.serial.SerialPortManager; import org.openhab.binding.insteon.internal.InsteonBinding; import org.openhab.binding.insteon.internal.config.InsteonNetworkConfiguration; @@ -41,7 +47,6 @@ @NonNullByDefault @SuppressWarnings("null") public class InsteonNetworkHandler extends BaseBridgeHandler { - private static final int LOG_DEVICE_STATISTICS_DELAY_IN_SECONDS = 600; private static final int SETTLE_TIME_IN_SECONDS = 5; @@ -54,6 +59,8 @@ public class InsteonNetworkHandler extends BaseBridgeHandler { private @Nullable ScheduledFuture settleJob = null; private long lastInsteonDeviceCreatedTimestamp = 0; private @Nullable SerialPortManager serialPortManager; + private Map deviceInfo = new ConcurrentHashMap<>(); + private Map channelInfo = new ConcurrentHashMap<>(); public static ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); @@ -119,7 +126,13 @@ public void dispose() { settleJob = null; } - insteonBinding.shutdown(); + if (insteonBinding != null) { + insteonBinding.shutdown(); + insteonBinding = null; + } + + deviceInfo.clear(); + channelInfo.clear(); super.dispose(); } @@ -146,4 +159,42 @@ public void addMissingDevices(List missing) { insteonDeviceDiscoveryService.addInsteonDevices(missing, getThing().getUID()); }); } + + public void displayDevices(Console console) { + display(console, deviceInfo); + } + + public void displayChannels(Console console) { + display(console, channelInfo); + } + + public void displayLocalDatabase(Console console) { + Map databaseInfo = insteonBinding.getDatabaseInfo(); + console.println("local database contains " + databaseInfo.size() + " entries"); + display(console, databaseInfo); + } + + public void initialized(ThingUID uid, String msg) { + deviceInfo.put(uid.getAsString(), msg); + } + + public void disposed(ThingUID uid) { + deviceInfo.remove(uid.getAsString()); + } + + public void linked(ChannelUID uid, String msg) { + channelInfo.put(uid.getAsString(), msg); + } + + public void unlinked(ChannelUID uid) { + channelInfo.remove(uid.getAsString()); + } + + private void display(Console console, Map info) { + ArrayList ids = new ArrayList<>(info.keySet()); + Collections.sort(ids); + for (String id : ids) { + console.println(info.get(id)); + } + } }