From 784a336490a344e4e78b89c492604fbff9d79605 Mon Sep 17 00:00:00 2001 From: James Hewitt Date: Mon, 14 Jun 2021 08:05:03 +0100 Subject: [PATCH] [rfxcom] Add support for receiving RAW messages (#10833) This is a new feature in the Pro firmwares that provides the real raw RF pulse lengths as shorts. Good for being able to parrot switches that aren't otherwise supported, once we add the tx support as well. Signed-off-by: James Hewitt --- bundles/org.openhab.binding.rfxcom/README.md | 28 ++- .../internal/RFXComBindingConstants.java | 29 +-- .../internal/messages/RFXComBaseMessage.java | 3 +- .../messages/RFXComMessageFactory.java | 1 + .../internal/messages/RFXComRawMessage.java | 184 ++++++++++++++++++ .../main/resources/OH-INF/thing/channels.xml | 4 +- .../src/main/resources/OH-INF/thing/raw.xml | 41 ++++ .../messages/RFXComRawMessageTest.java | 65 +++++++ 8 files changed, 338 insertions(+), 17 deletions(-) create mode 100644 bundles/org.openhab.binding.rfxcom/src/main/java/org/openhab/binding/rfxcom/internal/messages/RFXComRawMessage.java create mode 100644 bundles/org.openhab.binding.rfxcom/src/main/resources/OH-INF/thing/raw.xml create mode 100644 bundles/org.openhab.binding.rfxcom/src/test/java/org/openhab/binding/rfxcom/internal/messages/RFXComRawMessageTest.java diff --git a/bundles/org.openhab.binding.rfxcom/README.md b/bundles/org.openhab.binding.rfxcom/README.md index 62f89fe0d3db5..b82ae0dc82c91 100644 --- a/bundles/org.openhab.binding.rfxcom/README.md +++ b/bundles/org.openhab.binding.rfxcom/README.md @@ -174,7 +174,7 @@ This binding currently supports following channel types: | rainrate | Number | Rain fall rate in millimeters per hour. | | raintotal | Number | Total rain in millimeters. | | rawmessage | String | Hexadecimal representation of the raw RFXCOM msg incl. header and payload | -| rawpayload | String | Hexadecimal representation of payload RFXCOM messages | +| rawpayload | String | Hexadecimal representation of the payload of RFXCOM messages | | setpoint | Number | Requested temperature. | | shutter | Rollershutter | Shutter/blind channel. | | status | String | Status channel. | @@ -229,6 +229,7 @@ This binding currently supports the following things / message types: * [lighting5 - RFXCOM Lighting5 Actuator](#lighting5---rfxcom-lighting5-actuator) * [lighting6 - RFXCOM Lighting6 Actuator](#lighting6---rfxcom-lighting6-actuator) * [rain - RFXCOM Rain Sensor](#rain---rfxcom-rain-sensor) +* [raw - RFXCOM Raw Messages](#raw---rfxcom-raw-messages) * [rfxsensor - RFXCOM rfxsensor](#rfxsensor) * [rfy - RFXCOM Rfy Actuator](#rfy---rfxcom-rfy-actuator) * [security1 - RFXCOM Security1 Sensor](#security1---rfxcom-security1-sensor) @@ -884,6 +885,31 @@ A Rain device * RAIN6 - La Crosse TX5 * RAIN9 - TFA 30.3233.1 +### raw - RFXCOM Raw Messages + +Raw messages. + +#### Channels + +| Name | Channel Type | Item Type | Remarks | +|------------|---------------------------|-----------|-------------| +| rawMessage | [rawmessage](#channels) | String | | +| rawPayload | [rawpayload](#channels) | String | | + +#### Configuration Options + +* deviceId - Device Id + * Raw items cannot provide a device ID, so this value is always RAW. + +* subType - Sub Type + * Specifies message sub type. + + * RAW_PACKET1 + * RAW_PACKET2 + * RAW_PACKET3 + * RAW_PACKET4 + + ### rfxsensor - RFXCOM RFXSensor A RFXSensor sensor diff --git a/bundles/org.openhab.binding.rfxcom/src/main/java/org/openhab/binding/rfxcom/internal/RFXComBindingConstants.java b/bundles/org.openhab.binding.rfxcom/src/main/java/org/openhab/binding/rfxcom/internal/RFXComBindingConstants.java index c558cdc223f25..d300abab5a258 100644 --- a/bundles/org.openhab.binding.rfxcom/src/main/java/org/openhab/binding/rfxcom/internal/RFXComBindingConstants.java +++ b/bundles/org.openhab.binding.rfxcom/src/main/java/org/openhab/binding/rfxcom/internal/RFXComBindingConstants.java @@ -147,6 +147,7 @@ public class RFXComBindingConstants { private static final ThingTypeUID THING_TYPE_POWER = new ThingTypeUID(BINDING_ID, "power"); private static final ThingTypeUID THING_TYPE_RADIATOR1 = new ThingTypeUID(BINDING_ID, "radiator1"); private static final ThingTypeUID THING_TYPE_RAIN = new ThingTypeUID(BINDING_ID, "rain"); + private static final ThingTypeUID THING_TYPE_RAW = new ThingTypeUID(BINDING_ID, "raw"); private static final ThingTypeUID THING_TYPE_REMOTE_CONTROL = new ThingTypeUID(BINDING_ID, "remotecontrol"); private static final ThingTypeUID THING_TYPE_RFX_METER = new ThingTypeUID(BINDING_ID, "rfxmeter"); private static final ThingTypeUID THING_TYPE_RFX_SENSOR = new ThingTypeUID(BINDING_ID, "rfxsensor"); @@ -171,19 +172,20 @@ public class RFXComBindingConstants { /** * Presents all supported Thing types by RFXCOM binding. */ - public static final Set SUPPORTED_DEVICE_THING_TYPES_UIDS = Collections.unmodifiableSet(Stream.of( - THING_TYPE_BAROMETRIC, THING_TYPE_BBQ_TEMPERATURE, THING_TYPE_BLINDS1, THING_TYPE_CAMERA1, THING_TYPE_CHIME, - THING_TYPE_CURRENT, THING_TYPE_CURRENT_ENERGY, THING_TYPE_CURTAIN1, THING_TYPE_DATE_TIME, THING_TYPE_ENERGY, - THING_TYPE_FAN, THING_TYPE_FAN_SF01, THING_TYPE_FAN_ITHO, THING_TYPE_FAN_SEAV, THING_TYPE_FAN_LUCCI_DC, - THING_TYPE_FAN_FT1211R, THING_TYPE_FAN_FALMEC, THING_TYPE_FAN_LUCCI_DC_II, THING_TYPE_GAS_USAGE, - THING_TYPE_HOME_CONFORT, THING_TYPE_HUMIDITY, THING_TYPE_IO_LINES, THING_TYPE_LIGHTNING1, - THING_TYPE_LIGHTNING2, THING_TYPE_LIGHTNING3, THING_TYPE_LIGHTNING4, THING_TYPE_LIGHTNING5, - THING_TYPE_LIGHTNING6, THING_TYPE_POWER, THING_TYPE_RADIATOR1, THING_TYPE_RAIN, THING_TYPE_REMOTE_CONTROL, - THING_TYPE_RFX_METER, THING_TYPE_RFX_SENSOR, THING_TYPE_RFY, THING_TYPE_SECURITY1, THING_TYPE_SECURITY2, - THING_TYPE_TEMPERATURE, THING_TYPE_TEMPERATURE_HUMIDITY, THING_TYPE_TEMPERATURE_HUMIDITY_BAROMETRIC, - THING_TYPE_TEMPERATURE_RAIN, THING_TYPE_THERMOSTAT1, THING_TYPE_THERMOSTAT2, THING_TYPE_THERMOSTAT3, - THING_TYPE_UNDECODED, THING_TYPE_UV, THING_TYPE_WATER_USAGE, THING_TYPE_WEIGHTING_SCALE, THING_TYPE_WIND) - .collect(Collectors.toSet())); + public static final Set SUPPORTED_DEVICE_THING_TYPES_UIDS = Collections + .unmodifiableSet(Stream.of(THING_TYPE_BAROMETRIC, THING_TYPE_BBQ_TEMPERATURE, THING_TYPE_BLINDS1, + THING_TYPE_CAMERA1, THING_TYPE_CHIME, THING_TYPE_CURRENT, THING_TYPE_CURRENT_ENERGY, + THING_TYPE_CURTAIN1, THING_TYPE_DATE_TIME, THING_TYPE_ENERGY, THING_TYPE_FAN, THING_TYPE_FAN_SF01, + THING_TYPE_FAN_ITHO, THING_TYPE_FAN_SEAV, THING_TYPE_FAN_LUCCI_DC, THING_TYPE_FAN_FT1211R, + THING_TYPE_FAN_FALMEC, THING_TYPE_FAN_LUCCI_DC_II, THING_TYPE_GAS_USAGE, THING_TYPE_HOME_CONFORT, + THING_TYPE_HUMIDITY, THING_TYPE_IO_LINES, THING_TYPE_LIGHTNING1, THING_TYPE_LIGHTNING2, + THING_TYPE_LIGHTNING3, THING_TYPE_LIGHTNING4, THING_TYPE_LIGHTNING5, THING_TYPE_LIGHTNING6, + THING_TYPE_POWER, THING_TYPE_RADIATOR1, THING_TYPE_RAIN, THING_TYPE_RAW, THING_TYPE_REMOTE_CONTROL, + THING_TYPE_RFX_METER, THING_TYPE_RFX_SENSOR, THING_TYPE_RFY, THING_TYPE_SECURITY1, + THING_TYPE_SECURITY2, THING_TYPE_TEMPERATURE, THING_TYPE_TEMPERATURE_HUMIDITY, + THING_TYPE_TEMPERATURE_HUMIDITY_BAROMETRIC, THING_TYPE_TEMPERATURE_RAIN, THING_TYPE_THERMOSTAT1, + THING_TYPE_THERMOSTAT2, THING_TYPE_THERMOSTAT3, THING_TYPE_UNDECODED, THING_TYPE_UV, + THING_TYPE_WATER_USAGE, THING_TYPE_WEIGHTING_SCALE, THING_TYPE_WIND).collect(Collectors.toSet())); /** * Map RFXCOM packet types to RFXCOM Thing types and vice versa. @@ -223,6 +225,7 @@ public class RFXComBindingConstants { put(PacketType.POWER, RFXComBindingConstants.THING_TYPE_POWER); put(PacketType.RADIATOR1, RFXComBindingConstants.THING_TYPE_RADIATOR1); put(PacketType.RAIN, RFXComBindingConstants.THING_TYPE_RAIN); + put(PacketType.RAW, RFXComBindingConstants.THING_TYPE_RAW); put(PacketType.REMOTE_CONTROL, RFXComBindingConstants.THING_TYPE_REMOTE_CONTROL); put(PacketType.RFXMETER, RFXComBindingConstants.THING_TYPE_RFX_METER); put(PacketType.RFXSENSOR, RFXComBindingConstants.THING_TYPE_RFX_SENSOR); diff --git a/bundles/org.openhab.binding.rfxcom/src/main/java/org/openhab/binding/rfxcom/internal/messages/RFXComBaseMessage.java b/bundles/org.openhab.binding.rfxcom/src/main/java/org/openhab/binding/rfxcom/internal/messages/RFXComBaseMessage.java index dd54535c552a5..1f49b97f339b9 100644 --- a/bundles/org.openhab.binding.rfxcom/src/main/java/org/openhab/binding/rfxcom/internal/messages/RFXComBaseMessage.java +++ b/bundles/org.openhab.binding.rfxcom/src/main/java/org/openhab/binding/rfxcom/internal/messages/RFXComBaseMessage.java @@ -83,6 +83,7 @@ public enum PacketType implements ByteEnumWrapper { RFXSENSOR(112), RFXMETER(113), FS20(114), + RAW(127), IO_LINES(128); private final int packetType; @@ -163,7 +164,7 @@ public String toString() { } str += ", Packet type = " + packetType; - str += ", Seq number = " + (short) (seqNbr & 0xFF); + str += ", Seq number = " + Byte.toUnsignedInt(seqNbr); return str; } diff --git a/bundles/org.openhab.binding.rfxcom/src/main/java/org/openhab/binding/rfxcom/internal/messages/RFXComMessageFactory.java b/bundles/org.openhab.binding.rfxcom/src/main/java/org/openhab/binding/rfxcom/internal/messages/RFXComMessageFactory.java index 4c759a17166e8..0a3ec7d47bea6 100644 --- a/bundles/org.openhab.binding.rfxcom/src/main/java/org/openhab/binding/rfxcom/internal/messages/RFXComMessageFactory.java +++ b/bundles/org.openhab.binding.rfxcom/src/main/java/org/openhab/binding/rfxcom/internal/messages/RFXComMessageFactory.java @@ -84,6 +84,7 @@ public class RFXComMessageFactory { put(PacketType.RFXSENSOR, RFXComRFXSensorMessage.class); // put(PacketType.RFXMETER, RFXComRFXMeterMessage.class); // put(PacketType.FS20, RFXComFS20Message.class); + put(PacketType.RAW, RFXComRawMessage.class); // put(PacketType.IO_LINES, RFXComIOLinesMessage.class); } }); diff --git a/bundles/org.openhab.binding.rfxcom/src/main/java/org/openhab/binding/rfxcom/internal/messages/RFXComRawMessage.java b/bundles/org.openhab.binding.rfxcom/src/main/java/org/openhab/binding/rfxcom/internal/messages/RFXComRawMessage.java new file mode 100644 index 0000000000000..c4f1ed56129c1 --- /dev/null +++ b/bundles/org.openhab.binding.rfxcom/src/main/java/org/openhab/binding/rfxcom/internal/messages/RFXComRawMessage.java @@ -0,0 +1,184 @@ +/** + * Copyright (c) 2010-2021 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.rfxcom.internal.messages; + +import static org.openhab.binding.rfxcom.internal.RFXComBindingConstants.*; +import static org.openhab.binding.rfxcom.internal.messages.RFXComBaseMessage.PacketType.RAW; + +import java.nio.ByteBuffer; + +import org.openhab.binding.rfxcom.internal.exceptions.RFXComException; +import org.openhab.binding.rfxcom.internal.exceptions.RFXComMessageTooLongException; +import org.openhab.binding.rfxcom.internal.exceptions.RFXComUnsupportedChannelException; +import org.openhab.binding.rfxcom.internal.exceptions.RFXComUnsupportedValueException; +import org.openhab.binding.rfxcom.internal.handler.DeviceState; +import org.openhab.core.library.types.StringType; +import org.openhab.core.types.State; +import org.openhab.core.types.Type; +import org.openhab.core.util.HexUtils; + +/** + * RFXCOM data class for raw messages. + * + * @author James Hewitt-Thomas - New addition to the PRO RFXCom firmware + */ +public class RFXComRawMessage extends RFXComDeviceMessageImpl { + + public enum SubType implements ByteEnumWrapper { + RAW_PACKET1(0x00), + RAW_PACKET2(0x01), + RAW_PACKET3(0x02), + RAW_PACKET4(0x03), + + UNKNOWN(0xFF); + + private final int subType; + + SubType(int subType) { + this.subType = subType; + } + + @Override + public byte toByte() { + return (byte) subType; + } + + public static SubType fromByte(int input) { + for (SubType c : SubType.values()) { + if (c.subType == input) { + return c; + } + } + + return SubType.UNKNOWN; + } + } + + public SubType subType; + public byte repeat; + public short[] pulses; + + public RFXComRawMessage() { + super(RAW); + pulses = new short[0]; + } + + public RFXComRawMessage(byte[] message) throws RFXComException { + encodeMessage(message); + } + + @Override + public String toString() { + String str = super.toString(); + + str += ", Sub type = " + subType; + + return str; + } + + @Override + public void encodeMessage(byte[] message) throws RFXComException { + super.encodeMessage(message); + + final int pulsesByteLen = rawMessage.length - 5; + if (pulsesByteLen % 4 != 0) { + throw new RFXComException("Incorrect byte length for pulses - must be divisible by 4"); + } + + subType = SubType.fromByte(super.subType); + repeat = rawMessage[4]; + pulses = new short[pulsesByteLen / 2]; + ByteBuffer.wrap(rawMessage, 5, rawMessage.length - 5).asShortBuffer().get(pulses); + } + + @Override + public byte[] decodeMessage() throws RFXComException { + if (pulses.length > 124) { + throw new RFXComMessageTooLongException("Longest payload according to RFXtrx SDK is 124 shorts."); + } + + final int pulsesByteLen = pulses.length * 2; + byte[] data = new byte[5 + pulsesByteLen]; + + data[0] = (byte) (data.length - 1); + data[1] = RAW.toByte(); + data[2] = subType.toByte(); + data[3] = seqNbr; + data[4] = repeat; + + ByteBuffer.wrap(data, 5, pulsesByteLen).asShortBuffer().put(pulses); + + return data; + } + + @Override + public String getDeviceId() { + return "RAW"; + } + + @Override + public State convertToState(String channelId, DeviceState deviceState) throws RFXComUnsupportedChannelException { + switch (channelId) { + case CHANNEL_RAW_MESSAGE: + return new StringType(HexUtils.bytesToHex(rawMessage)); + + case CHANNEL_RAW_PAYLOAD: + byte[] payload = new byte[pulses.length * 2]; + ByteBuffer.wrap(payload).asShortBuffer().put(pulses); + return new StringType(HexUtils.bytesToHex(payload)); + + default: + throw new RFXComUnsupportedChannelException("Nothing relevant for " + channelId); + } + } + + @Override + public void setSubType(SubType subType) { + throw new UnsupportedOperationException(); + } + + @Override + public void setDeviceId(String deviceId) { + throw new UnsupportedOperationException(); + } + + @Override + public void convertFromState(String channelId, Type type) throws RFXComUnsupportedChannelException { + switch (channelId) { + case CHANNEL_RAW_MESSAGE: + if (type instanceof StringType) { + // TODO: Check the raw message for validity (length, no more than 124 shorts, multiple of 4 bytes in + // payload) + throw new RFXComUnsupportedChannelException("Channel " + channelId + " inot yet implemented"); + } else { + throw new RFXComUnsupportedChannelException("Channel " + channelId + " does not accept " + type); + } + + case CHANNEL_RAW_PAYLOAD: + if (type instanceof StringType) { + // TODO: Check the payload for validity (no more than 124 shorts, multiple of 4 bytes + throw new RFXComUnsupportedChannelException("Channel " + channelId + " not yet implemented"); + } else { + throw new RFXComUnsupportedChannelException("Channel " + channelId + " does not accept " + type); + } + + default: + throw new RFXComUnsupportedChannelException("Channel " + channelId + " is not relevant here"); + } + } + + @Override + public SubType convertSubType(String subType) throws RFXComUnsupportedValueException { + return ByteEnumUtil.convertSubType(SubType.class, subType); + } +} diff --git a/bundles/org.openhab.binding.rfxcom/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.rfxcom/src/main/resources/OH-INF/thing/channels.xml index 30af6f83af301..3707b8d7f5079 100644 --- a/bundles/org.openhab.binding.rfxcom/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.rfxcom/src/main/resources/OH-INF/thing/channels.xml @@ -9,13 +9,13 @@ String - Hexadecimal representation of undecoded RFXCOM messages including header and payload + Hexadecimal representation of RFXCOM messages including header and payload String - Hexadecimal representation of payload of undecoded RFXCOM messages + Hexadecimal representation of payload of raw and undecoded RFXCOM messages diff --git a/bundles/org.openhab.binding.rfxcom/src/main/resources/OH-INF/thing/raw.xml b/bundles/org.openhab.binding.rfxcom/src/main/resources/OH-INF/thing/raw.xml new file mode 100644 index 0000000000000..52618f3d11529 --- /dev/null +++ b/bundles/org.openhab.binding.rfxcom/src/main/resources/OH-INF/thing/raw.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + Raw messages. + + + + + + + + + + Raw items cannot provide a device ID, so this value is always RAW. + + + + Specifies device sub type. + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.rfxcom/src/test/java/org/openhab/binding/rfxcom/internal/messages/RFXComRawMessageTest.java b/bundles/org.openhab.binding.rfxcom/src/test/java/org/openhab/binding/rfxcom/internal/messages/RFXComRawMessageTest.java new file mode 100644 index 0000000000000..f3baebb59bda8 --- /dev/null +++ b/bundles/org.openhab.binding.rfxcom/src/test/java/org/openhab/binding/rfxcom/internal/messages/RFXComRawMessageTest.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2010-2021 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.rfxcom.internal.messages; + +import static org.junit.jupiter.api.Assertions.*; + +import java.nio.ByteBuffer; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.rfxcom.internal.exceptions.RFXComException; +import org.openhab.binding.rfxcom.internal.exceptions.RFXComMessageTooLongException; +import org.openhab.binding.rfxcom.internal.messages.RFXComBaseMessage.PacketType; +import org.openhab.core.util.HexUtils; + +/** + * Test for RFXCom-binding + * + * @author James Hewitt-Thomas - New addition to the PRO RFXCom firmware + */ +@NonNullByDefault +public class RFXComRawMessageTest { + + private void testMessage(String hexMsg, RFXComRawMessage.SubType subType, int seqNbr, int repeat, String pulses) + throws RFXComException { + final RFXComRawMessage msg = (RFXComRawMessage) RFXComMessageFactory.createMessage(HexUtils.hexToBytes(hexMsg)); + assertEquals(subType, msg.subType, "SubType"); + assertEquals(seqNbr, (short) (msg.seqNbr & 0xFF), "Seq Number"); + assertEquals("RAW", msg.getDeviceId(), "Device Id"); + assertEquals(repeat, msg.repeat, "Repeat"); + byte[] payload = new byte[msg.pulses.length * 2]; + ByteBuffer.wrap(payload).asShortBuffer().put(msg.pulses); + assertEquals(pulses, HexUtils.bytesToHex(payload), "Pulses"); + + byte[] decoded = msg.decodeMessage(); + + assertEquals(hexMsg, HexUtils.bytesToHex(decoded), "Message converted back"); + } + + @Test + public void testSomeMessages() throws RFXComException { + testMessage("087F0027051356ECC0", RFXComRawMessage.SubType.RAW_PACKET1, 0x27, 5, "1356ECC0"); + } + + @Test + public void testLongMessage() throws RFXComException { + RFXComRawMessage msg = (RFXComRawMessage) RFXComMessageFactory.createMessage(PacketType.RAW); + msg.subType = RFXComRawMessage.SubType.RAW_PACKET1; + msg.seqNbr = 1; + msg.repeat = 5; + msg.pulses = new short[125]; + + assertThrows(RFXComMessageTooLongException.class, () -> msg.decodeMessage()); + } +}