[insteon] Fix iolinc device support (#18273)
Signed-off-by: Jeremy Setton <jeremy.setton@gmail.com>pull/18686/head
parent
1f7183b7fd
commit
c646a0704a
|
@ -298,8 +298,9 @@ In order to determine which channels a device supports, check the device in the
|
|||
| program-lock | Switch | R/W | Local Programming Lock |
|
||||
| pump | Switch | R/W | Pump Control |
|
||||
| ramp-rate | Number:Time | R/W | Ramp Rate |
|
||||
| relay-mode | String | R/W | Output Relay Mode |
|
||||
| relay-sensor-follow | Switch | R/W | Output Relay Sensor Follow |
|
||||
| relay-mode | String | R/W | Relay Mode |
|
||||
| relay-sensor-follow | Switch | R/W | Relay Sensor Follow |
|
||||
| relay-sensor-inverted | Switch | R/W | Relay Sensor Inverted |
|
||||
| resume-dim | Switch | R/W | Resume Dim Level |
|
||||
| reverse-direction | Switch | R/W | Reverse Motor Direction |
|
||||
| rollershutter | Rollershutter | R/W | Rollershutter |
|
||||
|
@ -1122,21 +1123,33 @@ OFF=unlocked
|
|||
|
||||
### I/O Linc (garage door openers)
|
||||
|
||||
The I/O Linc devices are really two devices in one: a relay and a contact.
|
||||
The I/O Linc devices are really two devices in one: an output relay and an input contact sensor.
|
||||
To control the relay, link the modem as a controller using the set buttons as described in the instructions.
|
||||
To get the status of the contact, the modem must also be linked as a responder to the I/O Linc.
|
||||
The I/O Linc has a feature to invert the contact or match the contact when it sends commands to any linked responders.
|
||||
This is based on the status of the contact when it is linked, and was intended for controlling other devices with the contact.
|
||||
The binding expects the contact to be inverted to work properly.
|
||||
Ensure the contact is OFF (status LED is dark/garage door open) when linking the modem as a responder to the I/O Linc in order for it to function properly.
|
||||
To get the state of the relay and sensor, the modem must also be linked as a responder to the I/O Linc.
|
||||
The contact state is based on the sensor state at the time it is linked.
|
||||
To invert the state, either relink the modem as a responder with the sensor state inverted, or toggle the channel `relay-sensor-inverted`.
|
||||
By default, the device is inverted where an on command is sent when the sensor is closed, and off when open.
|
||||
For a garage door opener, ensure the input sensor is closed (status LED off) during the linking process.
|
||||
|
||||
##### Items
|
||||
|
||||
```java
|
||||
Switch garageDoorOpener "door opener" { channel="insteon:device:home:aabbcc:switch" }
|
||||
Contact garageDoorContact "door contact [MAP(contact.map):%s]" { channel="insteon:device:home:aabbcc:contact" }
|
||||
Switch garageDoorOpener "door opener" { channel="insteon:device:home:aabbcc:switch" }
|
||||
Contact garageDoorContact "door contact [MAP(contact.map):%s]" { channel="insteon:device:home:aabbcc:contact" }
|
||||
String garageDoorRelayMode "door relay mode" { channel="insteon:device:home:aabbcc:relay-mode" }
|
||||
Switch garageDoorRelaySensorInverted "door relay sensor inverted" { channel="insteon:device:home:aabbcc:relay-sensor-inverted" }
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Legacy</summary>
|
||||
|
||||
```java
|
||||
Switch garageDoorOpener "door opener" { channel="insteon:device:home:AABBCC:switch" }
|
||||
Contact garageDoorContact "door contact [MAP(contact.map):%s]" { channel="insteon:device:home:AABBCC:contact" }
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
and create a file "contact.map" in the transforms directory with these entries:
|
||||
|
||||
```text
|
||||
|
@ -1145,11 +1158,6 @@ CLOSED=closed
|
|||
-=unknown
|
||||
```
|
||||
|
||||
> NOTE: If the I/O Linc contact status appears delayed, or returns the wrong value when the sensor changes states, the contact was likely ON (status LED lit) when the modem was linked as a responder.
|
||||
Examples of this behavior would include: The status remaining CLOSED for up to 3 minutes after the door is opened, or the status remains OPEN for up to three minutes after the garage is opened and immediately closed again.
|
||||
To resolve this behavior the I/O Linc will need to be unlinked and then re-linked to the modem with the contact OFF (stats LED off).
|
||||
That would be with the door open when using the Insteon garage kit.
|
||||
|
||||
### Fan Controllers
|
||||
|
||||
Here is an example configuration for a FanLinc module, which has a dimmable light and a variable speed fan:
|
||||
|
|
|
@ -75,10 +75,13 @@ public class InsteonBindingConstants {
|
|||
public static final String FEATURE_LED_ON_OFF = "ledOnOff";
|
||||
public static final String FEATURE_LINK_FF_GROUP = "linkFFGroup";
|
||||
public static final String FEATURE_LOW_BATTERY_THRESHOLD = "lowBatteryThreshold";
|
||||
public static final String FEATURE_MOMENTARY_DURATION = "momentaryDuration";
|
||||
public static final String FEATURE_MONITOR_MODE = "monitorMode";
|
||||
public static final String FEATURE_ON_LEVEL = "onLevel";
|
||||
public static final String FEATURE_PING = "ping";
|
||||
public static final String FEATURE_RAMP_RATE = "rampRate";
|
||||
public static final String FEATURE_RELAY_MODE = "relayMode";
|
||||
public static final String FEATURE_RELAY_SENSOR_FOLLOW = "relaySensorFollow";
|
||||
public static final String FEATURE_SCENE = "scene";
|
||||
public static final String FEATURE_STAY_AWAKE = "stayAwake";
|
||||
public static final String FEATURE_TEMPERATURE_SCALE = "temperatureScale";
|
||||
|
|
|
@ -237,9 +237,9 @@ public class DeviceFeature {
|
|||
public int getComponentId() {
|
||||
int componentId = 0;
|
||||
if (device instanceof InsteonDevice insteonDevice && isControllerOrResponderFeature()) {
|
||||
// use feature group as component id if device has more than one controller or responder feature,
|
||||
// use feature group as component id if device has more than one controller or responder feature group,
|
||||
// set to 1 if device link db has a matching record, otherwise fall back to 0
|
||||
if (insteonDevice.getControllerOrResponderFeatures().size() > 1) {
|
||||
if (insteonDevice.getControllerOrResponderFeatureGroups().size() > 1) {
|
||||
componentId = getGroup();
|
||||
} else if (insteonDevice.getLinkDB().hasComponentIdRecord(1, isControllerFeature())) {
|
||||
componentId = 1;
|
||||
|
|
|
@ -112,8 +112,9 @@ public class InsteonDevice extends BaseDevice<InsteonAddress, InsteonDeviceHandl
|
|||
return getFeatures().stream().filter(DeviceFeature::isResponderFeature).toList();
|
||||
}
|
||||
|
||||
public List<DeviceFeature> getControllerOrResponderFeatures() {
|
||||
return getFeatures().stream().filter(DeviceFeature::isControllerOrResponderFeature).toList();
|
||||
public List<Integer> getControllerOrResponderFeatureGroups() {
|
||||
return getFeatures().stream().filter(DeviceFeature::isControllerOrResponderFeature).map(DeviceFeature::getGroup)
|
||||
.distinct().toList();
|
||||
}
|
||||
|
||||
public List<DeviceFeature> getFeatures(String type) {
|
||||
|
|
|
@ -1727,6 +1727,10 @@ public abstract class CommandHandler extends BaseFeatureHandler {
|
|||
* I/O linc momentary duration command handler
|
||||
*/
|
||||
public static class IOLincMomentaryDurationCommandHandler extends CommandHandler {
|
||||
private static final double DEFAULT_DURATION = 2;
|
||||
private static final double MIN_DURATION = 0.1;
|
||||
private static final double MAX_DURATION = 6300;
|
||||
|
||||
IOLincMomentaryDurationCommandHandler(DeviceFeature feature) {
|
||||
super(feature);
|
||||
}
|
||||
|
@ -1740,34 +1744,26 @@ public abstract class CommandHandler extends BaseFeatureHandler {
|
|||
public void handleCommand(InsteonChannelConfiguration config, Command cmd) {
|
||||
try {
|
||||
double duration = getDuration(cmd);
|
||||
if (duration != -1) {
|
||||
InsteonAddress address = getInsteonDevice().getAddress();
|
||||
int prescaler = 1;
|
||||
int delay = (int) Math.round(duration * 10);
|
||||
if (delay > 255) {
|
||||
prescaler = (int) Math.ceil(delay / 255.0);
|
||||
delay = (int) Math.round(delay / (double) prescaler);
|
||||
}
|
||||
boolean setCRC = getInsteonDevice().getInsteonEngine().supportsChecksum();
|
||||
// define ext command message to set momentary duration delay
|
||||
Msg delayMsg = Msg.makeExtendedMessage(address, (byte) 0x2E, (byte) 0x00,
|
||||
new byte[] { (byte) 0x01, (byte) 0x06, (byte) delay }, setCRC);
|
||||
// define ext command message to set momentary duration prescaler
|
||||
Msg prescalerMsg = Msg.makeExtendedMessage(address, (byte) 0x2E, (byte) 0x00,
|
||||
new byte[] { (byte) 0x01, (byte) 0x07, (byte) prescaler }, setCRC);
|
||||
// send requests
|
||||
feature.sendRequest(delayMsg);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("{}: sent momentary duration delay {} request to {}", nm(),
|
||||
HexUtils.getHexString(delay), address);
|
||||
}
|
||||
feature.sendRequest(prescalerMsg);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("{}: sent momentary duration prescaler {} request to {}", nm(),
|
||||
HexUtils.getHexString(prescaler), address);
|
||||
}
|
||||
} else {
|
||||
logger.warn("{}: got unexpected momentary duration command {}, ignoring request", nm(), cmd);
|
||||
int multiplier = getDurationMultiplier(duration);
|
||||
int delay = (int) Math.round(duration * 10 / multiplier);
|
||||
InsteonAddress address = getInsteonDevice().getAddress();
|
||||
boolean setCRC = getInsteonDevice().getInsteonEngine().supportsChecksum();
|
||||
// define ext command message to set momentary duration delay
|
||||
Msg delayMsg = Msg.makeExtendedMessage(address, (byte) 0x2E, (byte) 0x00,
|
||||
new byte[] { (byte) 0x01, (byte) 0x06, (byte) delay }, setCRC);
|
||||
// define ext command message to set momentary duration multiplier
|
||||
Msg multiplierMsg = Msg.makeExtendedMessage(address, (byte) 0x2E, (byte) 0x00,
|
||||
new byte[] { (byte) 0x01, (byte) 0x07, (byte) multiplier }, setCRC);
|
||||
// send requests
|
||||
feature.sendRequest(delayMsg);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("{}: sent momentary duration delay {} request to {}", nm(),
|
||||
HexUtils.getHexString(delay), address);
|
||||
}
|
||||
feature.sendRequest(multiplierMsg);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("{}: sent momentary duration multiplier {} request to {}", nm(),
|
||||
HexUtils.getHexString(multiplier), address);
|
||||
}
|
||||
} catch (InvalidMessageTypeException e) {
|
||||
logger.warn("{}: invalid message: ", nm(), e);
|
||||
|
@ -1777,12 +1773,29 @@ public abstract class CommandHandler extends BaseFeatureHandler {
|
|||
}
|
||||
|
||||
private double getDuration(Command cmd) {
|
||||
double duration = DEFAULT_DURATION;
|
||||
if (cmd instanceof DecimalType time) {
|
||||
return time.doubleValue();
|
||||
duration = time.doubleValue();
|
||||
} else if (cmd instanceof QuantityType<?> time) {
|
||||
return Objects.requireNonNullElse(time.toInvertibleUnit(Units.SECOND), time).doubleValue();
|
||||
duration = Objects.requireNonNullElse(time.toInvertibleUnit(Units.SECOND), time).doubleValue();
|
||||
}
|
||||
return -1;
|
||||
return Math.max(MIN_DURATION, Math.min(MAX_DURATION, duration));
|
||||
}
|
||||
|
||||
private int getDurationMultiplier(double duration) {
|
||||
int delay = (int) Math.round(duration * 10);
|
||||
// multiplier discrete steps as used by the insteon app
|
||||
int multiplier = 1;
|
||||
if (delay > 51000) {
|
||||
multiplier = 250;
|
||||
} else if (delay > 25500) {
|
||||
multiplier = 200;
|
||||
} else if (delay > 2550) {
|
||||
multiplier = 100;
|
||||
} else if (delay > 255) {
|
||||
multiplier = 10;
|
||||
}
|
||||
return multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import java.math.BigDecimal;
|
|||
import java.math.RoundingMode;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.measure.Unit;
|
||||
|
@ -1265,6 +1266,83 @@ public abstract class MessageHandler extends BaseFeatureHandler {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* I/O linc relay switch on message handler
|
||||
*/
|
||||
public static class IOLincRelaySwitchOnMsgHandler extends SwitchOnMsgHandler {
|
||||
IOLincRelaySwitchOnMsgHandler(DeviceFeature feature) {
|
||||
super(feature);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(byte cmd1, Msg msg) {
|
||||
State state = getInsteonDevice().getFeatureState(FEATURE_RELAY_SENSOR_FOLLOW);
|
||||
// handle message only if relay sensor follow is on
|
||||
if (OnOffType.ON.equals(state)) {
|
||||
super.handleMessage(cmd1, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* I/O linc relay switch off message handler
|
||||
*/
|
||||
public static class IOLincRelaySwitchOffMsgHandler extends SwitchOffMsgHandler {
|
||||
IOLincRelaySwitchOffMsgHandler(DeviceFeature feature) {
|
||||
super(feature);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(byte cmd1, Msg msg) {
|
||||
State state = getInsteonDevice().getFeatureState(FEATURE_RELAY_SENSOR_FOLLOW);
|
||||
// handle message only if relay sensor follow is on
|
||||
if (OnOffType.ON.equals(state)) {
|
||||
super.handleMessage(cmd1, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* I/O linc relay switch reply message handler
|
||||
*/
|
||||
public static class IOLincRelaySwitchReplyHandler extends SwitchRequestReplyHandler {
|
||||
private static final int DEFAULT_DURATION = 2;
|
||||
|
||||
IOLincRelaySwitchReplyHandler(DeviceFeature feature) {
|
||||
super(feature);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(byte cmd1, Msg msg) {
|
||||
super.handleMessage(cmd1, msg);
|
||||
// trigger poll with delay based on momentary duration if not status reply and relay mode not latching
|
||||
if (feature.getQueryCommand() != 0x19 && getRelayMode() != IOLincRelayMode.LATCHING) {
|
||||
long delay = getPollDelay();
|
||||
feature.triggerPoll(delay);
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable IOLincRelayMode getRelayMode() {
|
||||
try {
|
||||
State state = getInsteonDevice().getFeatureState(FEATURE_RELAY_MODE);
|
||||
if (state instanceof StringType mode) {
|
||||
return IOLincRelayMode.valueOf(mode.toString());
|
||||
}
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private long getPollDelay() {
|
||||
double delay = DEFAULT_DURATION;
|
||||
State state = getInsteonDevice().getFeatureState(FEATURE_MOMENTARY_DURATION);
|
||||
if (state instanceof QuantityType<?> duration) {
|
||||
delay = Objects.requireNonNullElse(duration.toInvertibleUnit(Units.SECOND), duration).doubleValue();
|
||||
}
|
||||
return (long) (delay * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* I/O linc momentary duration message handler
|
||||
*/
|
||||
|
@ -1275,17 +1353,17 @@ public abstract class MessageHandler extends BaseFeatureHandler {
|
|||
|
||||
@Override
|
||||
protected @Nullable State getState(byte cmd1, double value) {
|
||||
int duration = getDuration((int) value);
|
||||
double duration = getDuration((int) value);
|
||||
return new QuantityType<Time>(duration, Units.SECOND);
|
||||
}
|
||||
|
||||
private int getDuration(int value) {
|
||||
int prescaler = value >> 8; // high byte
|
||||
private double getDuration(int value) {
|
||||
int multiplier = Math.max(value >> 8, 1); // high byte
|
||||
int delay = value & 0xFF; // low byte
|
||||
if (delay == 0) {
|
||||
delay = 255;
|
||||
}
|
||||
return delay * prescaler / 10;
|
||||
return delay * multiplier / 10.0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -287,13 +287,15 @@ channel-type.insteon.program-lock.description = Prevent manual device configurat
|
|||
channel-type.insteon.pump.label = Pump Control
|
||||
channel-type.insteon.ramp-rate.label = Ramp Rate
|
||||
channel-type.insteon.ramp-rate.description = Control how fast the dimmer turns on or off.
|
||||
channel-type.insteon.relay-mode.label = Output Relay Mode
|
||||
channel-type.insteon.relay-mode.label = Relay Mode
|
||||
channel-type.insteon.relay-mode.state.option.LATCHING = Latching
|
||||
channel-type.insteon.relay-mode.state.option.MOMENTARY_A = Momentary A
|
||||
channel-type.insteon.relay-mode.state.option.MOMENTARY_B = Momentary B
|
||||
channel-type.insteon.relay-mode.state.option.MOMENTARY_C = Momentary C
|
||||
channel-type.insteon.relay-sensor-follow.label = Output Relay Sensor Follow
|
||||
channel-type.insteon.relay-sensor-follow.description = Set the output relay to trigger when the sensor input changes state.
|
||||
channel-type.insteon.relay-sensor-follow.label = Relay Sensor Follow
|
||||
channel-type.insteon.relay-sensor-follow.description = Set the relay to trigger when the sensor changes state.
|
||||
channel-type.insteon.relay-sensor-inverted.label = Relay Sensor Inverted
|
||||
channel-type.insteon.relay-sensor-inverted.description = Invert the sensor state when the relay triggers.
|
||||
channel-type.insteon.resume-dim.label = Resume Dim Level
|
||||
channel-type.insteon.resume-dim.description = Resume previous dim level when turned on locally.
|
||||
channel-type.insteon.reverse-direction.label = Reverse Motor Direction
|
||||
|
|
|
@ -408,7 +408,7 @@
|
|||
<item-type>Number:Time</item-type>
|
||||
<label>Momentary Duration</label>
|
||||
<description>Set the output relay closure duration for momentary relay modes (A-C).</description>
|
||||
<state min="0.2" max="25" step="0.1" pattern="%.1f %unit%"/>
|
||||
<state min="0.1" max="6300" step="0.1" pattern="%.1f %unit%"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="monitor-mode" advanced="true">
|
||||
|
@ -539,7 +539,7 @@
|
|||
|
||||
<channel-type id="relay-mode" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Output Relay Mode</label>
|
||||
<label>Relay Mode</label>
|
||||
<state>
|
||||
<options>
|
||||
<option value="LATCHING">Latching</option>
|
||||
|
@ -552,8 +552,14 @@
|
|||
|
||||
<channel-type id="relay-sensor-follow" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Output Relay Sensor Follow</label>
|
||||
<description>Set the output relay to trigger when the sensor input changes state.</description>
|
||||
<label>Relay Sensor Follow</label>
|
||||
<description>Set the relay to trigger when the sensor changes state.</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="relay-sensor-inverted" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Relay Sensor Inverted</label>
|
||||
<description>Invert the sensor state when the relay triggers.</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="resume-dim" advanced="true">
|
||||
|
|
|
@ -230,6 +230,16 @@
|
|||
<poll-handler>NoPollHandler</poll-handler>
|
||||
</feature-type>
|
||||
|
||||
<feature-type name="IOLincRelaySwitch" link="responder">
|
||||
<message-dispatcher>DefaultDispatcher</message-dispatcher>
|
||||
<message-handler command="0x11" group="1">IOLincRelaySwitchOnMsgHandler</message-handler>
|
||||
<message-handler command="0x13" group="1">IOLincRelaySwitchOffMsgHandler</message-handler>
|
||||
<message-handler command="0x19">IOLincRelaySwitchReplyHandler</message-handler>
|
||||
<message-handler default="true">NoOpMsgHandler</message-handler>
|
||||
<command-handler command="OnOffType">SwitchOnOffCommandHandler</command-handler>
|
||||
<command-handler command="RefreshType">RefreshCommandHandler</command-handler>
|
||||
<poll-handler ext="0" cmd1="0x19" cmd2="0x00">FlexPollHandler</poll-handler>
|
||||
</feature-type>
|
||||
<feature-type name="IOLincRelayMode">
|
||||
<message-dispatcher>DefaultDispatcher</message-dispatcher>
|
||||
<message-handler command="0x19">IOLincRelayModeReplyHandler</message-handler>
|
||||
|
@ -1102,5 +1112,6 @@
|
|||
<message-handler command="0x16" group="1">X10EventMsgHandler</message-handler>
|
||||
<message-handler default="true">NoOpMsgHandler</message-handler>
|
||||
<command-handler default="true">NoOpCommandHandler</command-handler>
|
||||
<poll-handler>NoPollHandler</poll-handler>
|
||||
</feature-type>
|
||||
</xml>
|
||||
|
|
|
@ -825,8 +825,8 @@
|
|||
<!-- Sensors and Actuators -->
|
||||
|
||||
<device-type name="SensorsActuators_IOLinc">
|
||||
<feature name="switch">GenericSwitch</feature>
|
||||
<feature name="contact" group="2">GenericSensorContact</feature>
|
||||
<feature name="switch">IOLincRelaySwitch</feature>
|
||||
<feature name="contact">GenericSensorContact</feature>
|
||||
<feature-group name="extDataGroup" type="ExtDataGroup">
|
||||
<feature name="momentaryDuration">IOLincMomentaryDuration</feature>
|
||||
</feature-group>
|
||||
|
@ -834,9 +834,10 @@
|
|||
<feature name="programLock" bit="0" on="0x00" off="0x01">OpFlags</feature>
|
||||
<feature name="ledTraffic" bit="1" on="0x02" off="0x03">OpFlags</feature>
|
||||
<feature name="relaySensorFollow" bit="2" on="0x04" off="0x05">OpFlags</feature>
|
||||
<feature name="relaySensorInverted" bit="6" on="0x0E" off="0x0F" inverted="true">OpFlags</feature>
|
||||
<feature name="relayMode">IOLincRelayMode</feature>
|
||||
</feature-group>
|
||||
<default-link name="relay" type="controller" group="1" data1="0x03" data2="0x00" data3="0x00"/>
|
||||
<default-link name="sensor" type="controller" group="1" data1="0x03" data2="0x00" data3="0x00"/>
|
||||
</device-type>
|
||||
|
||||
<device-type name="SensorsActuators_Siren">
|
||||
|
|
Loading…
Reference in New Issue