From fd1c96677e37352283875f25755ab510b4f7dffa Mon Sep 17 00:00:00 2001 From: Daniel Weber <25605184+fruggy83@users.noreply.github.com> Date: Sat, 20 Feb 2021 17:13:28 +0100 Subject: [PATCH] [enocean] Improved device discovery and added SMACK capability (#10157) * Added SMACK teach in * Teached in devices can be teach out on a repeated teach in * Improved detection of RPS devices, device types can be better distinguished now * Bugfixes for discovery fallback to GenericThings * Responses to message requests are send automatically now, no need for linking SEND_COMMAND channel Fixes #10156 Signed-off-by: Daniel Weber --- bundles/org.openhab.binding.enocean/README.md | 19 +- .../internal/EnOceanBindingConstants.java | 24 +-- .../internal/EnOceanHandlerFactory.java | 6 +- .../config/EnOceanActuatorConfig.java | 2 +- .../internal/config/EnOceanBaseConfig.java | 8 + .../internal/config/EnOceanBridgeConfig.java | 8 +- .../EnOceanChannelTransformationConfig.java | 9 +- .../EnOceanDeviceDiscoveryService.java | 169 +++++++++++++----- .../enocean/internal/eep/A5_02/A5_02.java | 4 - .../enocean/internal/eep/A5_04/A5_04.java | 3 - .../enocean/internal/eep/A5_07/A5_07.java | 3 - .../enocean/internal/eep/A5_08/A5_08.java | 3 - .../enocean/internal/eep/A5_10/A5_10.java | 3 - .../enocean/internal/eep/A5_20/A5_20_04.java | 2 +- .../internal/eep/Base/PTM200Message.java | 8 +- .../internal/eep/Base/UTEResponse.java | 5 +- .../Base/_4BSTeachInVariation3Response.java | 6 +- .../internal/eep/Base/_RPSMessage.java | 2 + .../enocean/internal/eep/D2_05/D2_05_00.java | 3 +- .../enocean/internal/eep/EEPFactory.java | 151 ++++++++++++---- .../binding/enocean/internal/eep/EEPType.java | 101 ++++++++--- .../enocean/internal/eep/F6_01/F6_01_01.java | 9 +- .../enocean/internal/eep/F6_02/F6_02_01.java | 26 ++- .../enocean/internal/eep/F6_02/F6_02_02.java | 13 +- .../enocean/internal/eep/F6_05/F6_05_02.java | 9 +- .../enocean/internal/eep/F6_10/F6_10_00.java | 8 +- .../eep/F6_10/F6_10_00_EltakoFPE.java | 6 + .../enocean/internal/eep/F6_10/F6_10_01.java | 9 +- .../internal/eep/Generic/GenericEEP.java | 5 +- .../handler/EnOceanBaseActuatorHandler.java | 73 ++++---- .../handler/EnOceanBaseSensorHandler.java | 20 ++- .../handler/EnOceanBaseThingHandler.java | 17 +- .../handler/EnOceanBridgeHandler.java | 156 ++++++++++++---- .../handler/EnOceanClassicDeviceHandler.java | 2 +- .../internal/messages/ESP3PacketFactory.java | 26 +++ .../internal/messages/EventMessage.java | 65 +++++++ .../enocean/internal/messages/Response.java | 2 +- .../{ => Responses}/BaseResponse.java | 3 +- .../{ => Responses}/RDBaseIdResponse.java | 3 +- .../Responses/RDLearnedClientsResponse.java | 61 +++++++ .../{ => Responses}/RDRepeaterResponse.java | 3 +- .../{ => Responses}/RDVersionResponse.java | 3 +- .../Responses/SMACKTeachInResponse.java | 65 +++++++ .../enocean/internal/messages/SAMessage.java | 64 +++++++ .../transceiver/EnOceanESP3Transceiver.java | 19 +- .../transceiver/EnOceanTransceiver.java | 90 +++++++--- .../internal/transceiver/EventListener.java | 23 +++ .../internal/transceiver/PacketListener.java | 2 +- .../internal/transceiver/TeachInListener.java | 21 +++ .../resources/OH-INF/thing/CentralCommand.xml | 2 +- .../resources/OH-INF/thing/ClassicDevice.xml | 2 +- .../resources/OH-INF/thing/GenericThing.xml | 2 +- .../OH-INF/thing/HeatRecoveryVentilation.xml | 2 +- .../OH-INF/thing/MeasurementSwitch.xml | 2 +- .../resources/OH-INF/thing/Rollershutter.xml | 2 +- .../resources/OH-INF/thing/Thermostat.xml | 2 +- .../main/resources/OH-INF/thing/bridge.xml | 15 +- .../main/resources/OH-INF/thing/channels.xml | 7 - 58 files changed, 1064 insertions(+), 314 deletions(-) create mode 100644 bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/EventMessage.java rename bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/{ => Responses}/BaseResponse.java (85%) rename bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/{ => Responses}/RDBaseIdResponse.java (91%) create mode 100644 bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDLearnedClientsResponse.java rename bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/{ => Responses}/RDRepeaterResponse.java (93%) rename bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/{ => Responses}/RDVersionResponse.java (94%) create mode 100644 bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/SMACKTeachInResponse.java create mode 100644 bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/SAMessage.java create mode 100644 bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EventListener.java create mode 100644 bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/TeachInListener.java diff --git a/bundles/org.openhab.binding.enocean/README.md b/bundles/org.openhab.binding.enocean/README.md index e6ff99b0a1686..38600c86d2e4b 100644 --- a/bundles/org.openhab.binding.enocean/README.md +++ b/bundles/org.openhab.binding.enocean/README.md @@ -126,7 +126,21 @@ The corresponding channels are created dynamically, too. If the actuator supports UTE teach-in, the corresponding thing can be created and paired automatically. First you have to **start the discovery scan for a gateway**. Then press the teach-in button of the actuator. -If the EEP of the actuator is known, the binding sends an UTE teach-in response with a new SenderId and creates a new thing with its channels. +If the EEP of the actuator is known, the binding sends an UTE teach-in response with a new SenderId and creates a new thing with its channels. + +This binding supports so called smart acknowlegde (SMACK) devices too. +Before you can pair a SMACK device you have to configure your gateway bridge as a SMACK postmaster. +If this option is enabled you can pair up to 20 SMACK devices with your gateway. + +Communication between your gateway and a SMACK device is handled through mailboxes. +A mailbox is created for each paired SMACK device and deleted after teach out. +You can see the paired SMACK devices and their mailbox index in the gateway properties. +SMACK devices send periodically status updates followed by a response request. +Whenever such a request is received a `requestAnswer` event is triggered for channel `statusRequestEvent`. +Afterwards you have 100ms time to recalculate your items states and update them. +A message with the updated item states is built, put into the corresponding mailbox and automatically sent upon request of the device. +Pairing and unpairing can be done through a discovery scan. +The corresponding thing of an unpaired device gets disabled, you have to delete it manually if you want to. If the actuator does not support UTE teach-ins, you have to create, configure and choose the right EEP of the thing manually. It is important to link the teach-in channel of this thing to a switch item. @@ -158,6 +172,8 @@ If you change the SenderId of your thing, you have to pair again the thing with | | espVersion | ESP Version of gateway | ESP3, ESP2 | | | rs485 | If gateway is directly connected to a RS485 bus the BaseId is set to 0x00 | true, false | | rs485BaseId | Override BaseId 0x00 if your bus contains a telegram duplicator (FTD14 for ex) | 4 byte hex value | +| | enableSmack | Enables SMACK pairing and handling of SMACK messages | true, false | +| | sendTeachOuts | Defines if a repeated teach in request should be answered with a learned in or teach out response | true, false | | pushButton | receivingEEPId | EEP used for receiving msg | F6_01_01, D2_03_0A | | | enoceanId | EnOceanId of device this thing belongs to | hex value as string | | rockerSwitch | receivingEEPId | | F6_02_01, F6_02_02 | @@ -300,6 +316,7 @@ The channels of a thing are determined automatically based on the chosen EEP. | rssi | Number | Received Signal Strength Indication (dBm) of last received message | | repeatCount | Number | Number of repeaters involved in the transmission of the telegram | | lastReceived | DateTime | Date and time the last telegram was received | +| statusRequestEvent | Trigger | Emits event 'requestAnswer' | Items linked to bi-directional actuators (actuator sends status messages back) should always disable the `autoupdate`. This is especially true for Eltako rollershutter, as their position is calculated out of the current position and the moving time. diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/EnOceanBindingConstants.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/EnOceanBindingConstants.java index 4ae4bf2240bca..c6b3814c5edbc 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/EnOceanBindingConstants.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/EnOceanBindingConstants.java @@ -177,7 +177,7 @@ public class EnOceanBindingConstants { public static final String CHANNEL_WAKEUPCYCLE = "wakeUpCycle"; public static final String CHANNEL_SERVICECOMMAND = "serviceCommand"; public static final String CHANNEL_STATUS_REQUEST_EVENT = "statusRequestEvent"; - public static final String CHANNEL_SEND_COMMAND = "sendCommand"; + public static final String VIRTUALCHANNEL_SEND_COMMAND = "sendCommand"; public static final String CHANNEL_VENTILATIONOPERATIONMODE = "ventilationOperationMode"; public static final String CHANNEL_FIREPLACESAFETYMODE = "fireplaceSafetyMode"; @@ -293,7 +293,8 @@ public class EnOceanBindingConstants { Map.entry(CHANNEL_INDOORAIRANALYSIS, new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_INDOORAIRANALYSIS), CoreItemFactory.STRING)), - Map.entry(CHANNEL_SETPOINT, + Map.entry( + CHANNEL_SETPOINT, new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_SETPOINT), CoreItemFactory.NUMBER)), Map.entry(CHANNEL_CONTACT, @@ -444,13 +445,6 @@ public class EnOceanBindingConstants { new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_SERVICECOMMAND), CoreItemFactory.NUMBER)), - Map.entry(CHANNEL_STATUS_REQUEST_EVENT, - new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_STATUS_REQUEST_EVENT), null, - "", false, true)), - Map.entry(CHANNEL_SEND_COMMAND, - new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_SEND_COMMAND), - CoreItemFactory.SWITCH)), - Map.entry(CHANNEL_VENTILATIONOPERATIONMODE, new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_VENTILATIONOPERATIONMODE), CoreItemFactory.STRING)), @@ -527,6 +521,10 @@ public class EnOceanBindingConstants { CoreItemFactory.NUMBER + ItemUtil.EXTENSION_SEPARATOR + Dimensionless.class.getSimpleName())), + Map.entry(CHANNEL_STATUS_REQUEST_EVENT, + new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_STATUS_REQUEST_EVENT), null, + "", false, true)), + Map.entry(CHANNEL_REPEATERMODE, new EnOceanChannelDescription( new ChannelTypeUID(BINDING_ID, CHANNEL_REPEATERMODE), CoreItemFactory.STRING))); @@ -536,11 +534,8 @@ public class EnOceanBindingConstants { public static final String REPEATERMODE_LEVEL_2 = "LEVEL2"; // Bridge config properties - public static final String SENDERID = "senderId"; public static final String PATH = "path"; - public static final String HOST = "host"; - public static final String RS485 = "rs485"; - public static final String NEXTSENDERID = "nextSenderId"; + public static final String PARAMETER_NEXT_SENDERID = "nextSenderId"; // Bridge properties public static final String PROPERTY_BASE_ID = "Base ID"; @@ -551,13 +546,12 @@ public class EnOceanBindingConstants { public static final String PROPERTY_DESCRIPTION = "Description"; // Thing properties - public static final String PROPERTY_ENOCEAN_ID = "enoceanId"; + public static final String PROPERTY_SENDINGENOCEAN_ID = "SendingEnoceanId"; // Thing config parameter public static final String PARAMETER_SENDERIDOFFSET = "senderIdOffset"; public static final String PARAMETER_SENDINGEEPID = "sendingEEPId"; public static final String PARAMETER_RECEIVINGEEPID = "receivingEEPId"; - public static final String PARAMETER_EEPID = "eepId"; public static final String PARAMETER_BROADCASTMESSAGES = "broadcastMessages"; public static final String PARAMETER_ENOCEANID = "enoceanId"; diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/EnOceanHandlerFactory.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/EnOceanHandlerFactory.java index 3ac9ea809cced..bbc82fff8d2b9 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/EnOceanHandlerFactory.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/EnOceanHandlerFactory.java @@ -28,6 +28,7 @@ import org.openhab.core.io.transport.serial.SerialPortManager; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingManager; import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.binding.BaseThingHandlerFactory; @@ -60,6 +61,9 @@ public class EnOceanHandlerFactory extends BaseThingHandlerFactory { @Reference ItemChannelLinkRegistry itemChannelLinkRegistry; + @Reference + ThingManager thingManager; + @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); @@ -96,7 +100,7 @@ protected void removeHandler(ThingHandler thingHandler) { } private void registerDeviceDiscoveryService(EnOceanBridgeHandler handler) { - EnOceanDeviceDiscoveryService discoveryService = new EnOceanDeviceDiscoveryService(handler); + EnOceanDeviceDiscoveryService discoveryService = new EnOceanDeviceDiscoveryService(handler, thingManager); discoveryService.activate(); this.discoveryServiceRegs.put(handler.getThing().getUID(), bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>())); diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanActuatorConfig.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanActuatorConfig.java index 461e2e0c95d5d..eb9291abcafca 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanActuatorConfig.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanActuatorConfig.java @@ -19,7 +19,7 @@ public class EnOceanActuatorConfig extends EnOceanBaseConfig { public int channel; - public int senderIdOffset = -1; + public Integer senderIdOffset = null; public String manufacturerId; public String teachInType; diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanBaseConfig.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanBaseConfig.java index 49a0cdae330df..100d83a3b8633 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanBaseConfig.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanBaseConfig.java @@ -17,15 +17,23 @@ import java.util.ArrayList; import java.util.List; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.util.HexUtils; /** * * @author Daniel Weber - Initial contribution */ +@NonNullByDefault public class EnOceanBaseConfig { + /** + * EnOceanId of the physical device + */ public String enoceanId; + /** + * EEP used/send by physical device + */ public List receivingEEPId = new ArrayList<>(); public boolean receivingSIGEEP = false; diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanBridgeConfig.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanBridgeConfig.java index baad63a36ed47..6e6bc671a4461 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanBridgeConfig.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanBridgeConfig.java @@ -46,10 +46,16 @@ public static ESPVersion getESPVersion(String espVersion) { public boolean rs485; public String rs485BaseId; - public int nextSenderId = 0; + public Integer nextSenderId; + + public boolean enableSmack; + public boolean sendTeachOuts; public EnOceanBridgeConfig() { espVersion = "ESP3"; + sendTeachOuts = false; + enableSmack = true; + nextSenderId = null; } public ESPVersion getESPVersion() { diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanChannelTransformationConfig.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanChannelTransformationConfig.java index 36471ec37be02..81c701f4579e4 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanChannelTransformationConfig.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanChannelTransformationConfig.java @@ -12,12 +12,19 @@ */ package org.openhab.binding.enocean.internal.config; +import org.openhab.core.config.core.Configuration; + /** * * @author Daniel Weber - Initial contribution */ -public class EnOceanChannelTransformationConfig { +public class EnOceanChannelTransformationConfig extends Configuration { public String transformationType; public String transformationFunction; + + public EnOceanChannelTransformationConfig() { + put("transformationType", ""); + put("transformationFunction", ""); + } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/discovery/EnOceanDeviceDiscoveryService.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/discovery/EnOceanDeviceDiscoveryService.java index 030e601e576bf..0373c474ba586 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/discovery/EnOceanDeviceDiscoveryService.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/discovery/EnOceanDeviceDiscoveryService.java @@ -25,9 +25,14 @@ import org.openhab.binding.enocean.internal.messages.BasePacket; import org.openhab.binding.enocean.internal.messages.ERP1Message; import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG; -import org.openhab.binding.enocean.internal.transceiver.PacketListener; +import org.openhab.binding.enocean.internal.messages.EventMessage; +import org.openhab.binding.enocean.internal.messages.EventMessage.EventMessageType; +import org.openhab.binding.enocean.internal.messages.Responses.SMACKTeachInResponse; +import org.openhab.binding.enocean.internal.transceiver.TeachInListener; import org.openhab.core.config.discovery.AbstractDiscoveryService; import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingManager; import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingUID; import org.openhab.core.util.HexUtils; @@ -39,15 +44,16 @@ * * @author Daniel Weber - Initial contribution */ - -public class EnOceanDeviceDiscoveryService extends AbstractDiscoveryService implements PacketListener { +public class EnOceanDeviceDiscoveryService extends AbstractDiscoveryService implements TeachInListener { private final Logger logger = LoggerFactory.getLogger(EnOceanDeviceDiscoveryService.class); private EnOceanBridgeHandler bridgeHandler; + private ThingManager thingManager; - public EnOceanDeviceDiscoveryService(EnOceanBridgeHandler bridgeHandler) { + public EnOceanDeviceDiscoveryService(EnOceanBridgeHandler bridgeHandler, ThingManager thingManager) { super(null, 60, false); this.bridgeHandler = bridgeHandler; + this.thingManager = thingManager; } /** @@ -102,72 +108,139 @@ public void packetReceived(BasePacket packet) { } String enoceanId = HexUtils.bytesToHex(eep.getSenderId()); - ThingTypeUID thingTypeUID = eep.getThingTypeUID(); - ThingUID thingUID = new ThingUID(thingTypeUID, bridgeHandler.getThing().getUID(), enoceanId); - - int senderIdOffset = 0; - boolean broadcastMessages = true; - - // check for bidirectional communication => do not use broadcast in this case - if (msg.getRORG() == RORG.UTE && (msg.getPayload(1, 1)[0] - & UTEResponse.CommunicationType_MASK) == UTEResponse.CommunicationType_MASK) { - broadcastMessages = false; - } - - // if ute => send response if needed - if (msg.getRORG() == RORG.UTE && (msg.getPayload(1, 1)[0] & UTEResponse.ResponseNeeded_MASK) == 0) { - logger.info("Sending UTE response to {}", enoceanId); - senderIdOffset = sendTeachInResponse(msg, enoceanId); - } - - // if 4BS teach in variation 3 => send response - if ((eep instanceof _4BSMessage) && ((_4BSMessage) eep).isTeachInVariation3Supported()) { - logger.info("Sending 4BS teach in variation 3 response to {}", enoceanId); - senderIdOffset = sendTeachInResponse(msg, enoceanId); - } - DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(thingUID) - .withRepresentationProperty(enoceanId).withBridge(bridgeHandler.getThing().getUID()); + bridgeHandler.getThing().getThings().stream() + .filter(t -> t.getConfiguration().getProperties().getOrDefault(PARAMETER_ENOCEANID, EMPTYENOCEANID) + .toString().equals(enoceanId)) + .findFirst().ifPresentOrElse(t -> { + // If repeated learn is not allowed => send teach out + // otherwise do nothing + if (bridgeHandler.sendTeachOuts()) { + sendTeachOutResponse(msg, enoceanId, t); + thingManager.setEnabled(t.getUID(), false); + } + }, () -> { + Integer senderIdOffset = null; + boolean broadcastMessages = true; + + // check for bidirectional communication => do not use broadcast in this case + if (msg.getRORG() == RORG.UTE && (msg.getPayload(1, 1)[0] + & UTEResponse.CommunicationType_MASK) == UTEResponse.CommunicationType_MASK) { + broadcastMessages = false; + } + + if (msg.getRORG() == RORG.UTE && (msg.getPayload(1, 1)[0] & UTEResponse.ResponseNeeded_MASK) == 0) { + // if ute => send response if needed + logger.debug("Sending UTE response to {}", enoceanId); + senderIdOffset = sendTeachInResponse(msg, enoceanId); + if (senderIdOffset == null) { + return; + } + } else if ((eep instanceof _4BSMessage) && ((_4BSMessage) eep).isTeachInVariation3Supported()) { + // if 4BS teach in variation 3 => send response + logger.debug("Sending 4BS teach in variation 3 response to {}", enoceanId); + senderIdOffset = sendTeachInResponse(msg, enoceanId); + if (senderIdOffset == null) { + return; + } + } + + createDiscoveryResult(eep, broadcastMessages, senderIdOffset); + }); + } - eep.addConfigPropertiesTo(discoveryResultBuilder); - discoveryResultBuilder.withProperty(PARAMETER_BROADCASTMESSAGES, broadcastMessages); - discoveryResultBuilder.withProperty(PARAMETER_ENOCEANID, enoceanId); + @Override + public void eventReceived(EventMessage event) { + if (event.getEventMessageType() == EventMessageType.SA_CONFIRM_LEARN) { + EEP eep = EEPFactory.buildEEPFromTeachInSMACKEvent(event); + if (eep == null) { + return; + } - if (senderIdOffset > 0) { - // advance config with new device id - discoveryResultBuilder.withProperty(PARAMETER_SENDERIDOFFSET, senderIdOffset); + SMACKTeachInResponse response = EEPFactory.buildResponseFromSMACKTeachIn(event, + bridgeHandler.sendTeachOuts()); + if (response != null) { + bridgeHandler.sendMessage(response, null); + + if (response.isTeachIn()) { + // SenderIdOffset will be determined during Thing init + createDiscoveryResult(eep, false, -1); + } else if (response.isTeachOut()) { + // disable already teached in thing + bridgeHandler.getThing().getThings().stream() + .filter(t -> t.getConfiguration().getProperties() + .getOrDefault(PARAMETER_ENOCEANID, EMPTYENOCEANID).toString() + .equals(HexUtils.bytesToHex(eep.getSenderId()))) + .findFirst().ifPresentOrElse(t -> { + thingManager.setEnabled(t.getUID(), false); + logger.info("Disable thing with id {}", t.getUID()); + }, () -> { + logger.info("Thing for EnOceanId {} already deleted", + HexUtils.bytesToHex(eep.getSenderId())); + }); + } + } } - - thingDiscovered(discoveryResultBuilder.build()); - - // As we only support sensors to be teached in, we do not need to send a teach in response => 4bs - // bidirectional teach in proc is not supported yet - // this is true except for UTE teach in => we always have to send a response here } - private int sendTeachInResponse(ERP1Message msg, String enoceanId) { - int offset; + private Integer sendTeachInResponse(ERP1Message msg, String enoceanId) { // get new sender Id - offset = bridgeHandler.getNextSenderId(enoceanId); - if (offset > 0) { + Integer offset = bridgeHandler.getNextSenderId(enoceanId); + if (offset != null) { byte[] newSenderId = bridgeHandler.getBaseId(); newSenderId[3] += offset; // send response - EEP response = EEPFactory.buildResponseEEPFromTeachInERP1(msg, newSenderId); + EEP response = EEPFactory.buildResponseEEPFromTeachInERP1(msg, newSenderId, true); if (response != null) { bridgeHandler.sendMessage(response.getERP1Message(), null); - logger.info("Teach in response for {} with new senderId {} (= offset {}) sent", enoceanId, + logger.debug("Teach in response for {} with new senderId {} (= offset {}) sent", enoceanId, HexUtils.bytesToHex(newSenderId), offset); } else { logger.warn("Teach in response for enoceanId {} not supported!", enoceanId); } + } else { + logger.warn("Could not get new SenderIdOffset"); } return offset; } + private void sendTeachOutResponse(ERP1Message msg, String enoceanId, Thing thing) { + byte[] senderId = bridgeHandler.getBaseId(); + senderId[3] += (byte) thing.getConfiguration().getProperties().getOrDefault(PARAMETER_SENDERIDOFFSET, 0); + + // send response + EEP response = EEPFactory.buildResponseEEPFromTeachInERP1(msg, senderId, false); + if (response != null) { + bridgeHandler.sendMessage(response.getERP1Message(), null); + logger.debug("Teach out response for thing {} with EnOceanId {} sent", thing.getUID().getId(), enoceanId); + } else { + logger.warn("Teach out response for enoceanId {} not supported!", enoceanId); + } + } + + protected void createDiscoveryResult(EEP eep, boolean broadcastMessages, Integer senderIdOffset) { + String enoceanId = HexUtils.bytesToHex(eep.getSenderId()); + ThingTypeUID thingTypeUID = eep.getThingTypeUID(); + ThingUID thingUID = new ThingUID(thingTypeUID, bridgeHandler.getThing().getUID(), enoceanId); + + DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(thingUID) + .withRepresentationProperty(PARAMETER_ENOCEANID).withProperty(PARAMETER_ENOCEANID, enoceanId) + .withProperty(PARAMETER_BROADCASTMESSAGES, broadcastMessages) + .withBridge(bridgeHandler.getThing().getUID()); + + eep.addConfigPropertiesTo(discoveryResultBuilder); + + if (senderIdOffset != null) { + // advance config with new device id + discoveryResultBuilder.withProperty(PARAMETER_SENDERIDOFFSET, senderIdOffset); + } + + thingDiscovered(discoveryResultBuilder.build()); + } + @Override - public long getSenderIdToListenTo() { + public long getEnOceanIdToListenTo() { // we just want teach in msg, so return zero here return 0; } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_02/A5_02.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_02/A5_02.java index 30c8522f7c2d2..957296c15f37f 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_02/A5_02.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_02/A5_02.java @@ -20,7 +20,6 @@ import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.unit.SIUnits; import org.openhab.core.types.State; -import org.openhab.core.types.UnDefType; /** * @@ -51,9 +50,6 @@ protected int getUnscaledTemperatureValue() { @Override protected State convertToStateImpl(String channelId, String channelTypeId, Function getCurrentStateFunc, Configuration config) { - if (!isValid()) { - return UnDefType.UNDEF; - } double scaledTemp = getScaledMin() - (((getUnscaledMin() - getUnscaledTemperatureValue()) * (getScaledMin() - getScaledMax())) diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_04/A5_04.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_04/A5_04.java index fa4241caf9548..355d6365b356e 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_04/A5_04.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_04/A5_04.java @@ -62,9 +62,6 @@ protected int getUnscaledHumidityValue() { @Override protected State convertToStateImpl(String channelId, String channelTypeId, Function getCurrentStateFunc, Configuration config) { - if (!isValid()) { - return UnDefType.UNDEF; - } if (channelId.equals(CHANNEL_TEMPERATURE)) { double scaledTemp = getScaledTemperatureMin() diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_07/A5_07.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_07/A5_07.java index ac78bbf02e832..56f074f799ae9 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_07/A5_07.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_07/A5_07.java @@ -53,9 +53,6 @@ protected State getSupplyVoltage(int value) { @Override protected State convertToStateImpl(String channelId, String channelTypeId, Function getCurrentStateFunc, Configuration config) { - if (!isValid()) { - return UnDefType.UNDEF; - } if (channelId.equals(CHANNEL_ILLUMINATION)) { return getIllumination(); diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_08/A5_08.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_08/A5_08.java index 5f6a54c806dd1..cf471e7fd7136 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_08/A5_08.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_08/A5_08.java @@ -71,9 +71,6 @@ protected int getUnscaledIlluminationValue() { @Override protected State convertToStateImpl(String channelId, String channelTypeId, Function getCurrentStateFunc, Configuration config) { - if (!isValid()) { - return UnDefType.UNDEF; - } if (channelId.equals(CHANNEL_TEMPERATURE)) { double scaledTemp = getScaledTemperatureMin() diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_10/A5_10.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_10/A5_10.java index 99def10649e15..465ce91d2528f 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_10/A5_10.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_10/A5_10.java @@ -40,9 +40,6 @@ public A5_10(ERP1Message packet) { @Override protected State convertToStateImpl(String channelId, String channelTypeId, Function getCurrentStateFunc, Configuration config) { - if (!isValid()) { - return UnDefType.UNDEF; - } switch (channelId) { case CHANNEL_FANSPEEDSTAGE: diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_20/A5_20_04.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_20/A5_20_04.java index 1c54b86f78c95..058a34b760163 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_20/A5_20_04.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_20/A5_20_04.java @@ -169,7 +169,7 @@ private byte getSer(Function getCurrentStateFunc) { @Override protected void convertFromCommandImpl(String channelId, String channelTypeId, Command command, Function getCurrentStateFunc, Configuration config) { - if (CHANNEL_SEND_COMMAND.equals(channelId) && (command.equals(OnOffType.ON))) { + if (VIRTUALCHANNEL_SEND_COMMAND.equals(channelId)) { byte db3 = getPos(getCurrentStateFunc); byte db2 = getTsp(getCurrentStateFunc); byte db1 = (byte) (0x00 | getMc(getCurrentStateFunc) | getWuc(getCurrentStateFunc)); diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/PTM200Message.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/PTM200Message.java index ce476f4f85237..ead041877797c 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/PTM200Message.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/PTM200Message.java @@ -55,9 +55,6 @@ protected void convertFromCommandImpl(String channelId, String channelTypeId, Co @Override protected State convertToStateImpl(String channelId, String channelTypeId, Function getCurrentStateFunc, Configuration config) { - if (!isValid()) { - return UnDefType.UNDEF; - } switch (channelId) { case CHANNEL_GENERAL_SWITCHING: @@ -77,4 +74,9 @@ protected State convertToStateImpl(String channelId, String channelTypeId, return UnDefType.UNDEF; } + + @Override + public boolean isValidForTeachIn() { + return false; + } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/UTEResponse.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/UTEResponse.java index 41cdcb1a88462..21dba840f65b3 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/UTEResponse.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/UTEResponse.java @@ -27,11 +27,12 @@ public class UTEResponse extends _VLDMessage { public static final byte ResponseNeeded_MASK = 0x40; public static final byte TeachIn_NotSpecified = 0x20; - public UTEResponse(ERP1Message packet) { + public UTEResponse(ERP1Message packet, boolean teachIn) { int dataLength = packet.getPayload().length - ESP3_SENDERID_LENGTH - ESP3_RORG_LENGTH - ESP3_STATUS_LENGTH; setData(packet.getPayload(ESP3_RORG_LENGTH, dataLength)); - bytes[0] = (byte) 0x91; // bidirectional communication, teach in accepted, teach in response + bytes[0] = (byte) (teachIn ? 0x91 : 0xA1); // bidirectional communication, teach in accepted or teach out, teach + // in response setStatus((byte) 0x80); setSuppressRepeating(true); diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/_4BSTeachInVariation3Response.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/_4BSTeachInVariation3Response.java index 96d1200b0c4eb..651a786af8d72 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/_4BSTeachInVariation3Response.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/_4BSTeachInVariation3Response.java @@ -23,11 +23,11 @@ */ public class _4BSTeachInVariation3Response extends _4BSMessage { - public _4BSTeachInVariation3Response(ERP1Message packet) { + public _4BSTeachInVariation3Response(ERP1Message packet, boolean teachIn) { byte[] payload = packet.getPayload(ESP3_RORG_LENGTH, RORG._4BS.getDataLength()); - payload[3] = (byte) 0xF0; // telegram with EEP number and Manufacturer ID, - // EEP supported, Sender ID stored, Response + payload[3] = (byte) (teachIn ? 0xF0 : 0xD0); // telegram with EEP number and Manufacturer ID, + // EEP supported, Sender ID stored or deleted, Response setData(payload); setDestinationId(packet.getSenderId()); diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/_RPSMessage.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/_RPSMessage.java index 4f6afd303d39e..3dd44acc26e6d 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/_RPSMessage.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/_RPSMessage.java @@ -51,4 +51,6 @@ public EEP setStatus(byte status) { return this; } + + public abstract boolean isValidForTeachIn(); } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/D2_05/D2_05_00.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/D2_05/D2_05_00.java index c6d54180a628a..018b3ea06335b 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/D2_05/D2_05_00.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/D2_05/D2_05_00.java @@ -98,7 +98,8 @@ protected byte getChannel() { @Override public void addConfigPropertiesTo(DiscoveryResultBuilder discoveredThingResultBuilder) { - discoveredThingResultBuilder.withProperty(PARAMETER_EEPID, getEEPType().getId()); + discoveredThingResultBuilder.withProperty(PARAMETER_SENDINGEEPID, getEEPType().getId()) + .withProperty(PARAMETER_RECEIVINGEEPID, getEEPType().getId()); } @Override diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/EEPFactory.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/EEPFactory.java index 63e9c0fcf3f43..c84a1a753f7f7 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/EEPFactory.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/EEPFactory.java @@ -15,18 +15,24 @@ import static org.openhab.binding.enocean.internal.messages.ESP3Packet.*; import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; import org.openhab.binding.enocean.internal.eep.Base.UTEResponse; import org.openhab.binding.enocean.internal.eep.Base._4BSMessage; import org.openhab.binding.enocean.internal.eep.Base._4BSTeachInVariation3Response; +import org.openhab.binding.enocean.internal.eep.Base._RPSMessage; import org.openhab.binding.enocean.internal.eep.D5_00.D5_00_01; import org.openhab.binding.enocean.internal.eep.F6_01.F6_01_01; import org.openhab.binding.enocean.internal.eep.F6_02.F6_02_01; +import org.openhab.binding.enocean.internal.eep.F6_05.F6_05_02; import org.openhab.binding.enocean.internal.eep.F6_10.F6_10_00; import org.openhab.binding.enocean.internal.eep.F6_10.F6_10_00_EltakoFPE; import org.openhab.binding.enocean.internal.eep.F6_10.F6_10_01; import org.openhab.binding.enocean.internal.messages.ERP1Message; import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG; +import org.openhab.binding.enocean.internal.messages.EventMessage; +import org.openhab.binding.enocean.internal.messages.EventMessage.EventMessageType; +import org.openhab.binding.enocean.internal.messages.Responses.SMACKTeachInResponse; import org.openhab.core.util.HexUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,8 +51,9 @@ public static EEP createEEP(EEPType eepType) { if (cl == null) { throw new IllegalArgumentException("Message " + eepType + " not implemented"); } - return cl.newInstance(); - } catch (IllegalAccessException | InstantiationException e) { + return cl.getDeclaredConstructor().newInstance(); + } catch (IllegalAccessException | InstantiationException | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException e) { throw new IllegalArgumentException(e); } } @@ -69,6 +76,21 @@ public static EEP buildEEP(EEPType eepType, ERP1Message packet) { } } + private static EEPType getGenericEEPTypeFor(byte rorg) { + logger.info("Received unsupported EEP teach in, trying to fallback to generic thing"); + RORG r = RORG.getRORG(rorg); + if (r == RORG._4BS) { + logger.info("Fallback to 4BS generic thing"); + return EEPType.Generic4BS; + } else if (r == RORG.VLD) { + logger.info("Fallback to VLD generic thing"); + return EEPType.GenericVLD; + } else { + logger.info("Fallback not possible"); + return null; + } + } + public static EEP buildEEPFromTeachInERP1(ERP1Message msg) { if (!msg.getIsTeachIn() && !(msg.getRORG() == RORG.RPS)) { return null; @@ -77,38 +99,48 @@ public static EEP buildEEPFromTeachInERP1(ERP1Message msg) { switch (msg.getRORG()) { case RPS: try { - EEP result = new F6_01_01(msg); - if (result.isValid()) { // check if t21 is set, nu not set, and data == 0x10 or 0x00 + _RPSMessage result = new F6_10_00(msg); + if (result.isValidForTeachIn()) { + return result; + } + } catch (Exception e) { + } + + try { + _RPSMessage result = new F6_10_01(msg); + if (result.isValidForTeachIn()) { return result; } } catch (Exception e) { } try { - EEP result = new F6_02_01(msg); - if (result.isValid()) { // check if highest bit is not set + _RPSMessage result = new F6_02_01(msg); + if (result.isValidForTeachIn()) { return result; } } catch (Exception e) { } try { - EEP result = new F6_10_00(msg); - if (result.isValid()) { + _RPSMessage result = new F6_05_02(msg); + if (result.isValidForTeachIn()) { return result; } } catch (Exception e) { } + try { - EEP result = new F6_10_00_EltakoFPE(msg); - if (result.isValid()) { // check if data == 0x10 or 0x00 + _RPSMessage result = new F6_01_01(msg); + if (result.isValidForTeachIn()) { return result; } } catch (Exception e) { } + try { - EEP result = new F6_10_01(msg); - if (result.isValid()) { + _RPSMessage result = new F6_10_00_EltakoFPE(msg); + if (result.isValidForTeachIn()) { return result; } } catch (Exception e) { @@ -120,8 +152,8 @@ public static EEP buildEEPFromTeachInERP1(ERP1Message msg) { case _4BS: { int db_0 = msg.getPayload()[4]; if ((db_0 & _4BSMessage.LRN_Type_Mask) == 0) { // Variation 1 - logger.info("Received 4BS Teach In variation 1 without EEP"); - return null; + logger.info("Received 4BS Teach In variation 1 without EEP, fallback to generic thing"); + return buildEEP(EEPType.Generic4BS, msg); } byte db_3 = msg.getPayload()[1]; @@ -132,19 +164,21 @@ public static EEP buildEEPFromTeachInERP1(ERP1Message msg) { int type = ((db_3 & 0b11) << 5) + ((db_2 & 0xFF) >>> 3); int manufId = ((db_2 & 0b111) << 8) + (db_1 & 0xff); - logger.info("Received 4BS Teach In with EEP A5-{}-{} and manufacturerID {}", + logger.debug("Received 4BS Teach In with EEP A5-{}-{} and manufacturerID {}", HexUtils.bytesToHex(new byte[] { (byte) func }), HexUtils.bytesToHex(new byte[] { (byte) type }), HexUtils.bytesToHex(new byte[] { (byte) manufId })); EEPType eepType = EEPType.getType(RORG._4BS, func, type, manufId); if (eepType == null) { - logger.debug("Received unsupported EEP teach in, fallback to generic thing"); - eepType = EEPType.Generic4BS; + eepType = getGenericEEPTypeFor(RORG._4BS.getValue()); } - return buildEEP(eepType, msg); + if (eepType != null) { + return buildEEP(eepType, msg); + } } + break; case UTE: { byte[] payload = msg.getPayload(); @@ -161,38 +195,58 @@ public static EEP buildEEPFromTeachInERP1(ERP1Message msg) { EEPType eepType = EEPType.getType(RORG.getRORG(rorg), func, type, manufId); if (eepType == null) { - logger.info("Received unsupported EEP teach in, fallback to generic thing"); - RORG r = RORG.getRORG(rorg); - if (r == RORG._4BS) { - eepType = EEPType.Generic4BS; - } else if (r == RORG.VLD) { - eepType = EEPType.GenericVLD; - } else { - return null; - } + eepType = getGenericEEPTypeFor(rorg); } - return buildEEP(eepType, msg); + if (eepType != null) { + return buildEEP(eepType, msg); + } } - case Unknown: - case VLD: - case MSC: - case SIG: + break; + default: return null; } return null; } - public static EEP buildResponseEEPFromTeachInERP1(ERP1Message msg, byte[] senderId) { + public static EEP buildEEPFromTeachInSMACKEvent(EventMessage event) { + if (event.getEventMessageType() != EventMessageType.SA_CONFIRM_LEARN) { + return null; + } + + byte[] payload = event.getPayload(); + byte manufIdMSB = payload[2]; + byte manufIdLSB = payload[3]; + int manufId = ((manufIdMSB & 0b111) << 8) + (manufIdLSB & 0xff); + + byte rorg = payload[4]; + int func = payload[5] & 0xFF; + int type = payload[6] & 0xFF; + + byte[] senderId = Arrays.copyOfRange(payload, 12, 12 + 4); + + logger.debug("Received SMACK Teach In with EEP {}-{}-{} and manufacturerID {}", + HexUtils.bytesToHex(new byte[] { (byte) rorg }), HexUtils.bytesToHex(new byte[] { (byte) func }), + HexUtils.bytesToHex(new byte[] { (byte) type }), HexUtils.bytesToHex(new byte[] { (byte) manufId })); + + EEPType eepType = EEPType.getType(RORG.getRORG(rorg), func, type, manufId); + if (eepType == null) { + eepType = getGenericEEPTypeFor(rorg); + } + + return createEEP(eepType).setSenderId(senderId); + } + + public static EEP buildResponseEEPFromTeachInERP1(ERP1Message msg, byte[] senderId, boolean teachIn) { switch (msg.getRORG()) { case UTE: - EEP result = new UTEResponse(msg); + EEP result = new UTEResponse(msg, teachIn); result.setSenderId(senderId); return result; case _4BS: - result = new _4BSTeachInVariation3Response(msg); + result = new _4BSTeachInVariation3Response(msg, teachIn); result.setSenderId(senderId); return result; @@ -200,4 +254,31 @@ public static EEP buildResponseEEPFromTeachInERP1(ERP1Message msg, byte[] sender return null; } } + + public static SMACKTeachInResponse buildResponseFromSMACKTeachIn(EventMessage event, boolean sendTeachOuts) { + SMACKTeachInResponse response = new SMACKTeachInResponse(); + + byte priority = event.getPayload()[1]; + if ((priority & 0b1001) == 0b1001) { + logger.debug("gtw is already postmaster"); + if (sendTeachOuts) { + logger.debug("Repeated learn is not allow hence send teach out"); + response.setTeachOutResponse(); + } else { + logger.debug("Send a repeated learn in"); + response.setRepeatedTeachInResponse(); + } + } else if ((priority & 0b100) == 0) { + logger.debug("no place for further mailbox"); + response.setNoPlaceForFurtherMailbox(); + } else if ((priority & 0b10) == 0) { + logger.debug("rssi is not good enough"); + response.setBadRSSI(); + } else if ((priority & 0b1) == 0b1) { + logger.debug("gtw is candidate for postmaster => teach in"); + response.setTeachIn(); + } + + return response; + } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/EEPType.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/EEPType.java index 451c853303fdd..26c461ae8257c 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/EEPType.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/EEPType.java @@ -20,6 +20,7 @@ import org.eclipse.jdt.annotation.NonNull; import org.openhab.binding.enocean.internal.EnOceanChannelDescription; +import org.openhab.binding.enocean.internal.config.EnOceanChannelTransformationConfig; import org.openhab.binding.enocean.internal.eep.A5_02.A5_02_01; import org.openhab.binding.enocean.internal.eep.A5_02.A5_02_02; import org.openhab.binding.enocean.internal.eep.A5_02.A5_02_03; @@ -164,15 +165,9 @@ public enum EEPType { UTEResponse(RORG.UTE, 0, 0, false, UTEResponse.class, null), _4BSTeachInVariation3Response(RORG._4BS, 0, 0, false, _4BSTeachInVariation3Response.class, null), - GenericRPS(RORG.RPS, 0xFF, 0xFF, false, GenericRPS.class, THING_TYPE_GENERICTHING, CHANNEL_GENERIC_SWITCH, - CHANNEL_GENERIC_ROLLERSHUTTER, CHANNEL_GENERIC_DIMMER, CHANNEL_GENERIC_NUMBER, CHANNEL_GENERIC_STRING, - CHANNEL_GENERIC_COLOR, CHANNEL_GENERIC_TEACHINCMD), - Generic4BS(RORG._4BS, 0xFF, 0xFF, false, Generic4BS.class, THING_TYPE_GENERICTHING, CHANNEL_GENERIC_SWITCH, - CHANNEL_GENERIC_ROLLERSHUTTER, CHANNEL_GENERIC_DIMMER, CHANNEL_GENERIC_NUMBER, CHANNEL_GENERIC_STRING, - CHANNEL_GENERIC_COLOR, CHANNEL_GENERIC_TEACHINCMD, CHANNEL_VIBRATION), - GenericVLD(RORG.VLD, 0xFF, 0xFF, false, GenericVLD.class, THING_TYPE_GENERICTHING, CHANNEL_GENERIC_SWITCH, - CHANNEL_GENERIC_ROLLERSHUTTER, CHANNEL_GENERIC_DIMMER, CHANNEL_GENERIC_NUMBER, CHANNEL_GENERIC_STRING, - CHANNEL_GENERIC_COLOR, CHANNEL_GENERIC_TEACHINCMD), + GenericRPS(RORG.RPS, 0xFF, 0xFF, false, GenericRPS.class, THING_TYPE_GENERICTHING), + Generic4BS(RORG._4BS, 0xFF, 0xFF, false, Generic4BS.class, THING_TYPE_GENERICTHING, CHANNEL_VIBRATION), + GenericVLD(RORG.VLD, 0xFF, 0xFF, false, GenericVLD.class, THING_TYPE_GENERICTHING), PTM200(RORG.RPS, 0x00, 0x00, false, PTM200Message.class, null, CHANNEL_GENERAL_SWITCHING, CHANNEL_ROLLERSHUTTER, CHANNEL_CONTACT), @@ -391,8 +386,8 @@ public enum EEPType { // UniversalCommand(RORG._4BS, 0x3f, 0x7f, false, A5_3F_7F_Universal.class, THING_TYPE_UNIVERSALACTUATOR, // CHANNEL_GENERIC_ROLLERSHUTTER, CHANNEL_GENERIC_LIGHT_SWITCHING, CHANNEL_GENERIC_DIMMER, CHANNEL_TEACHINCMD), - EltakoFSB(RORG._4BS, 0x3f, 0x7f, false, "EltakoFSB", 0, A5_3F_7F_EltakoFSB.class, THING_TYPE_ROLLERSHUTTER, 0, - new Hashtable() { + EltakoFSB(RORG._4BS, 0x3f, 0x7f, false, false, "EltakoFSB", 0, A5_3F_7F_EltakoFSB.class, THING_TYPE_ROLLERSHUTTER, + 0, new Hashtable() { private static final long serialVersionUID = 1L; { put(CHANNEL_ROLLERSHUTTER, new Configuration()); @@ -404,10 +399,10 @@ public enum EEPType { } }), - Thermostat(RORG._4BS, 0x20, 0x04, false, A5_20_04.class, THING_TYPE_THERMOSTAT, CHANNEL_VALVE_POSITION, + Thermostat(RORG._4BS, 0x20, 0x04, false, true, A5_20_04.class, THING_TYPE_THERMOSTAT, CHANNEL_VALVE_POSITION, CHANNEL_BUTTON_LOCK, CHANNEL_DISPLAY_ORIENTATION, CHANNEL_TEMPERATURE_SETPOINT, CHANNEL_TEMPERATURE, CHANNEL_FEED_TEMPERATURE, CHANNEL_MEASUREMENT_CONTROL, CHANNEL_FAILURE_CODE, CHANNEL_WAKEUPCYCLE, - CHANNEL_SERVICECOMMAND, CHANNEL_STATUS_REQUEST_EVENT, CHANNEL_SEND_COMMAND), + CHANNEL_SERVICECOMMAND), SwitchWithEnergyMeasurment_00(RORG.VLD, 0x01, 0x00, true, D2_01_00.class, THING_TYPE_MEASUREMENTSWITCH, CHANNEL_GENERAL_SWITCHING, CHANNEL_TOTALUSAGE), @@ -512,23 +507,36 @@ public enum EEPType { private boolean supportsRefresh; + private boolean requestsResponse; + EEPType(RORG rorg, int func, int type, boolean supportsRefresh, Class eepClass, ThingTypeUID thingTypeUID, String... channelIds) { this(rorg, func, type, supportsRefresh, eepClass, thingTypeUID, -1, channelIds); } + EEPType(RORG rorg, int func, int type, boolean supportsRefresh, boolean requestsResponse, + Class eepClass, ThingTypeUID thingTypeUID, String... channelIds) { + this(rorg, func, type, supportsRefresh, requestsResponse, eepClass, thingTypeUID, -1, channelIds); + } + EEPType(RORG rorg, int func, int type, boolean supportsRefresh, String manufactorSuffix, int manufId, Class eepClass, ThingTypeUID thingTypeUID, String... channelIds) { - this(rorg, func, type, supportsRefresh, manufactorSuffix, manufId, eepClass, thingTypeUID, 0, channelIds); + this(rorg, func, type, supportsRefresh, false, manufactorSuffix, manufId, eepClass, thingTypeUID, 0, + channelIds); } EEPType(RORG rorg, int func, int type, boolean supportsRefresh, Class eepClass, ThingTypeUID thingTypeUID, int command, String... channelIds) { - this(rorg, func, type, supportsRefresh, "", 0, eepClass, thingTypeUID, command, channelIds); + this(rorg, func, type, supportsRefresh, false, "", 0, eepClass, thingTypeUID, command, channelIds); } - EEPType(RORG rorg, int func, int type, boolean supportsRefresh, String manufactorSuffix, int manufId, + EEPType(RORG rorg, int func, int type, boolean supportsRefresh, boolean requestsResponse, Class eepClass, ThingTypeUID thingTypeUID, int command, String... channelIds) { + this(rorg, func, type, supportsRefresh, requestsResponse, "", 0, eepClass, thingTypeUID, command, channelIds); + } + + EEPType(RORG rorg, int func, int type, boolean supportsRefresh, boolean requestsResponse, String manufactorSuffix, + int manufId, Class eepClass, ThingTypeUID thingTypeUID, int command, String... channelIds) { this.rorg = rorg; this.func = func; this.type = type; @@ -538,24 +546,18 @@ public enum EEPType { this.manufactorSuffix = manufactorSuffix; this.manufactorId = manufId; this.supportsRefresh = supportsRefresh; + this.requestsResponse = requestsResponse; for (String id : channelIds) { this.channelIdsWithConfig.put(id, new Configuration()); this.supportedChannels.put(id, CHANNELID2CHANNELDESCRIPTION.get(id)); } - this.channelIdsWithConfig.put(CHANNEL_RSSI, new Configuration()); - this.supportedChannels.put(CHANNEL_RSSI, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_RSSI)); - - this.channelIdsWithConfig.put(CHANNEL_REPEATCOUNT, new Configuration()); - this.supportedChannels.put(CHANNEL_REPEATCOUNT, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_REPEATCOUNT)); - - this.channelIdsWithConfig.put(CHANNEL_LASTRECEIVED, new Configuration()); - this.supportedChannels.put(CHANNEL_LASTRECEIVED, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_LASTRECEIVED)); + addDefaultChannels(); } - EEPType(RORG rorg, int func, int type, boolean supportsRefresh, String manufactorSuffix, int manufId, - Class eepClass, ThingTypeUID thingTypeUID, int command, + EEPType(RORG rorg, int func, int type, boolean supportsRefresh, boolean requestsResponse, String manufactorSuffix, + int manufId, Class eepClass, ThingTypeUID thingTypeUID, int command, Hashtable channelConfigs) { this.rorg = rorg; this.func = func; @@ -567,11 +569,46 @@ public enum EEPType { this.manufactorSuffix = manufactorSuffix; this.manufactorId = manufId; this.supportsRefresh = supportsRefresh; + this.requestsResponse = requestsResponse; for (String id : channelConfigs.keySet()) { this.supportedChannels.put(id, CHANNELID2CHANNELDESCRIPTION.get(id)); } + addDefaultChannels(); + } + + private void addDefaultChannels() { + + if (THING_TYPE_GENERICTHING.equals(this.thingTypeUID)) { + this.channelIdsWithConfig.put(CHANNEL_GENERIC_SWITCH, new EnOceanChannelTransformationConfig()); + this.supportedChannels.put(CHANNEL_GENERIC_SWITCH, + CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_SWITCH)); + + this.channelIdsWithConfig.put(CHANNEL_GENERIC_ROLLERSHUTTER, new EnOceanChannelTransformationConfig()); + this.supportedChannels.put(CHANNEL_GENERIC_ROLLERSHUTTER, + CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_ROLLERSHUTTER)); + + this.channelIdsWithConfig.put(CHANNEL_GENERIC_DIMMER, new EnOceanChannelTransformationConfig()); + this.supportedChannels.put(CHANNEL_GENERIC_DIMMER, + CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_DIMMER)); + + this.channelIdsWithConfig.put(CHANNEL_GENERIC_NUMBER, new EnOceanChannelTransformationConfig()); + this.supportedChannels.put(CHANNEL_GENERIC_NUMBER, + CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_NUMBER)); + + this.channelIdsWithConfig.put(CHANNEL_GENERIC_STRING, new EnOceanChannelTransformationConfig()); + this.supportedChannels.put(CHANNEL_GENERIC_STRING, + CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_STRING)); + + this.channelIdsWithConfig.put(CHANNEL_GENERIC_COLOR, new EnOceanChannelTransformationConfig()); + this.supportedChannels.put(CHANNEL_GENERIC_COLOR, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_COLOR)); + + this.channelIdsWithConfig.put(CHANNEL_GENERIC_TEACHINCMD, new EnOceanChannelTransformationConfig()); + this.supportedChannels.put(CHANNEL_GENERIC_TEACHINCMD, + CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_TEACHINCMD)); + } + this.channelIdsWithConfig.put(CHANNEL_RSSI, new Configuration()); this.supportedChannels.put(CHANNEL_RSSI, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_RSSI)); @@ -580,6 +617,12 @@ public enum EEPType { this.channelIdsWithConfig.put(CHANNEL_LASTRECEIVED, new Configuration()); this.supportedChannels.put(CHANNEL_LASTRECEIVED, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_LASTRECEIVED)); + + if (requestsResponse) { + this.channelIdsWithConfig.put(CHANNEL_STATUS_REQUEST_EVENT, new Configuration()); + this.supportedChannels.put(CHANNEL_STATUS_REQUEST_EVENT, + CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_STATUS_REQUEST_EVENT)); + } } public Class getEEPClass() { @@ -602,6 +645,10 @@ public boolean getSupportsRefresh() { return supportsRefresh; } + public boolean getRequstesResponse() { + return requestsResponse; + } + public Map GetSupportedChannels() { return Collections.unmodifiableMap(supportedChannels); } @@ -614,7 +661,7 @@ public boolean isChannelSupported(Channel channel) { } public boolean isChannelSupported(String channelId, String channelTypeId) { - return supportedChannels.containsKey(channelId) + return supportedChannels.containsKey(channelId) || VIRTUALCHANNEL_SEND_COMMAND.equals(channelId) || supportedChannels.values().stream().anyMatch(c -> c.channelTypeUID.getId().equals(channelTypeId)); } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_01/F6_01_01.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_01/F6_01_01.java index ce19528af36f0..b69aa8d4d6386 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_01/F6_01_01.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_01/F6_01_01.java @@ -34,9 +34,6 @@ public F6_01_01(ERP1Message packet) { @Override protected String convertToEventImpl(String channelId, String channelTypeId, String lastEvent, Configuration config) { - if (!isValid()) { - return null; - } return getBit(bytes[0], 4) ? CommonTriggerEvents.PRESSED : CommonTriggerEvents.RELEASED; } @@ -45,4 +42,10 @@ protected String convertToEventImpl(String channelId, String channelTypeId, Stri protected boolean validateData(byte[] bytes) { return super.validateData(bytes) && !getBit(bytes[0], 7); } + + @Override + public boolean isValidForTeachIn() { + // just treat press as teach in, ignore release + return t21 && !nu && bytes[0] == 0x10; + } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_02/F6_02_01.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_02/F6_02_01.java index 2af97fdc06f67..8152c5e90a30a 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_02/F6_02_01.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_02/F6_02_01.java @@ -55,9 +55,6 @@ public F6_02_01(ERP1Message packet) { @Override protected String convertToEventImpl(String channelId, String channelTypeId, String lastEvent, Configuration config) { - if (!isValid()) { - return null; - } if (t21 && nu) { byte dir1 = channelId.equals(CHANNEL_ROCKERSWITCH_CHANNELA) ? A0 : B0; @@ -112,11 +109,6 @@ protected State convertToStateImpl(String channelId, String channelTypeId, // this method is used by the classic device listener channels to convert an rocker switch message into an // appropriate item update State currentState = getCurrentStateFunc.apply(channelId); - - if (!isValid()) { - return UnDefType.UNDEF; - } - if (t21 && nu) { EnOceanChannelVirtualRockerSwitchConfig c = config.as(EnOceanChannelVirtualRockerSwitchConfig.class); byte dir1 = c.getChannel() == Channel.ChannelA ? A0 : B0; @@ -179,4 +171,22 @@ private State inverse(UpDownType currentState) { protected boolean validateData(byte[] bytes) { return super.validateData(bytes) && !getBit(bytes[0], 7); } + + @Override + public boolean isValidForTeachIn() { + if (t21) { + // just treat press as teach in => DB0.4 has to be set + if (!getBit(bytes[0], 4)) { + return false; + } + // DB0.7 is never set for rocker switch message + if (getBit(bytes[0], 7)) { + return false; + } + } else { + return false; + } + + return true; + } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_02/F6_02_02.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_02/F6_02_02.java index 29b643cb01a1a..76e169785171a 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_02/F6_02_02.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_02/F6_02_02.java @@ -52,9 +52,6 @@ public F6_02_02(ERP1Message packet) { @Override protected String convertToEventImpl(String channelId, String channelTypeId, String lastEvent, Configuration config) { - if (!isValid()) { - return null; - } if (t21 && nu) { byte dir1 = channelId.equals(CHANNEL_ROCKERSWITCH_CHANNELA) ? AI : BI; @@ -109,11 +106,6 @@ protected State convertToStateImpl(String channelId, String channelTypeId, // this method is used by the classic device listener channels to convert an rocker switch message into an // appropriate item update State currentState = getCurrentStateFunc.apply(channelId); - - if (!isValid()) { - return UnDefType.UNDEF; - } - if (t21 && nu) { EnOceanChannelVirtualRockerSwitchConfig c = config.as(EnOceanChannelVirtualRockerSwitchConfig.class); byte dir1 = c.getChannel() == Channel.ChannelA ? AI : BI; @@ -171,4 +163,9 @@ private State inverse(OnOffType currentState) { private State inverse(UpDownType currentState) { return currentState == UpDownType.UP ? UpDownType.DOWN : UpDownType.UP; } + + @Override + public boolean isValidForTeachIn() { + return false; // Never treat a message as F6-02-02, let user decide which orientation of rocker switch is used + } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_05/F6_05_02.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_05/F6_05_02.java index 50855ab2e92cc..4fafc5b68ac6e 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_05/F6_05_02.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_05/F6_05_02.java @@ -44,9 +44,6 @@ public F6_05_02(ERP1Message packet) { @Override protected State convertToStateImpl(String channelId, String channelTypeId, Function getCurrentStateFunc, Configuration config) { - if (!isValid()) { - return UnDefType.UNDEF; - } switch (channelId) { case CHANNEL_SMOKEDETECTION: @@ -62,4 +59,10 @@ protected State convertToStateImpl(String channelId, String channelTypeId, protected boolean validateData(byte[] bytes) { return super.validateData(bytes) && (bytes[0] == ALARM_OFF || bytes[0] == ALARM_ON || bytes[0] == ENERGY_LOW); } + + @Override + public boolean isValidForTeachIn() { + // just treat the first message with ALARM_ON as teach in + return !t21 && !nu && bytes[0] == ALARM_ON; + } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_00.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_00.java index 43fc0f75344d4..498a807458fdb 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_00.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_00.java @@ -47,9 +47,6 @@ public F6_10_00(ERP1Message packet) { @Override protected State convertToStateImpl(String channelId, String channelTypeId, Function getCurrentStateFunc, Configuration config) { - if (!isValid()) { - return UnDefType.UNDEF; - } byte data = (byte) (bytes[0] & 0xF0); @@ -82,4 +79,9 @@ protected State convertToStateImpl(String channelId, String channelTypeId, protected boolean validateData(byte[] bytes) { return super.validateData(bytes) && getBit(bytes[0], 7) && getBit(bytes[0], 6); } + + @Override + public boolean isValidForTeachIn() { + return t21 && !nu && getBit(bytes[0], 7) && getBit(bytes[0], 6); + } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_00_EltakoFPE.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_00_EltakoFPE.java index 8a381ea9e1600..ee9cd485f9c9b 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_00_EltakoFPE.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_00_EltakoFPE.java @@ -61,4 +61,10 @@ protected boolean validateData(byte[] bytes) { // FPE just sends 0b00010000 or 0b00000000 value, so we apply mask 0b11101111 return super.validateData(bytes) && ((bytes[0] & (byte) 0xEF) == (byte) 0x00); } + + @Override + public boolean isValidForTeachIn() { + // just treat CLOSED as teach in + return bytes[0] == CLOSED; + } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_01.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_01.java index 51ed9306212f0..3773cafe49c8d 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_01.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_01.java @@ -47,9 +47,6 @@ public F6_10_01(ERP1Message packet) { @Override protected State convertToStateImpl(String channelId, String channelTypeId, Function getCurrentStateFunc, Configuration config) { - if (!isValid()) { - return UnDefType.UNDEF; - } byte data = (byte) (bytes[0] & 0x0F); @@ -82,4 +79,10 @@ protected State convertToStateImpl(String channelId, String channelTypeId, protected boolean validateData(byte[] bytes) { return super.validateData(bytes) && getBit(bytes[0], 6) && getBit(bytes[0], 3) && getBit(bytes[0], 2); } + + @Override + public boolean isValidForTeachIn() { + return !getBit(bytes[0], 7) && getBit(bytes[0], 6) && !getBit(bytes[0], 5) && !getBit(bytes[0], 4) + && getBit(bytes[0], 3) && getBit(bytes[0], 2); + } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Generic/GenericEEP.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Generic/GenericEEP.java index 178ae890b8689..d930e8206400b 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Generic/GenericEEP.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Generic/GenericEEP.java @@ -12,7 +12,7 @@ */ package org.openhab.binding.enocean.internal.eep.Generic; -import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.PARAMETER_EEPID; +import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*; import static org.openhab.binding.enocean.internal.messages.ESP3Packet.*; import java.lang.reflect.InvocationTargetException; @@ -161,6 +161,7 @@ protected boolean validateData(byte[] bytes) { @Override public void addConfigPropertiesTo(DiscoveryResultBuilder discoveredThingResultBuilder) { - discoveredThingResultBuilder.withProperty(PARAMETER_EEPID, getEEPType().getId()); + discoveredThingResultBuilder.withProperty(PARAMETER_SENDINGEEPID, getEEPType().getId()) + .withProperty(PARAMETER_RECEIVINGEEPID, getEEPType().getId()); } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseActuatorHandler.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseActuatorHandler.java index 590769153791d..be2398ac0cf6c 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseActuatorHandler.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseActuatorHandler.java @@ -28,6 +28,7 @@ import org.openhab.binding.enocean.internal.eep.EEPType; import org.openhab.binding.enocean.internal.messages.BasePacket; import org.openhab.core.config.core.Configuration; +import org.openhab.core.library.types.OnOffType; import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; @@ -68,8 +69,8 @@ public EnOceanBaseActuatorHandler(Thing thing, ItemChannelLinkRegistry itemChann * @param senderIdOffset to be validated * @return true if senderIdOffset is between ]0;128[ and is not used yet */ - private boolean validateSenderIdOffset(int senderIdOffset) { - if (senderIdOffset == -1) { + private boolean validateSenderIdOffset(Integer senderIdOffset) { + if (senderIdOffset == null) { return true; } @@ -157,26 +158,24 @@ boolean validateConfig() { } private boolean initializeIdForSending() { - // Generic things are treated as actuator things, however to support also generic sensors one can define a - // senderIdOffset of -1 - // TODO: seperate generic actuators from generic sensors? - String thingTypeId = this.getThing().getThingTypeUID().getId(); - String genericThingTypeId = THING_TYPE_GENERICTHING.getId(); - - if (getConfiguration().senderIdOffset == -1 && thingTypeId.equals(genericThingTypeId)) { - return true; - } - EnOceanBridgeHandler bridgeHandler = getBridgeHandler(); if (bridgeHandler == null) { return false; } - // if senderIdOffset is not set (=> defaults to -1) or set to -1, the next free senderIdOffset is determined - if (getConfiguration().senderIdOffset == -1) { + // Generic things are treated as actuator things, however to support also generic sensors one can omit + // senderIdOffset + // TODO: seperate generic actuators from generic sensors? + if ((getConfiguration().senderIdOffset == null + && THING_TYPE_GENERICTHING.equals(this.getThing().getThingTypeUID()))) { + return true; + } + + // if senderIdOffset is not set, the next free senderIdOffset is determined + if (getConfiguration().senderIdOffset == null) { Configuration updateConfig = editConfiguration(); getConfiguration().senderIdOffset = bridgeHandler.getNextSenderId(thing); - if (getConfiguration().senderIdOffset == -1) { + if (getConfiguration().senderIdOffset == null) { configurationErrorDescription = "Could not get a free sender Id from Bridge"; return false; } @@ -185,12 +184,10 @@ private boolean initializeIdForSending() { } byte[] baseId = bridgeHandler.getBaseId(); - baseId[3] = (byte) ((baseId[3] & 0xFF) + getConfiguration().senderIdOffset); + baseId[3] = (byte) ((baseId[3] + getConfiguration().senderIdOffset) & 0xFF); this.senderId = baseId; - - this.updateProperty(PROPERTY_ENOCEAN_ID, HexUtils.bytesToHex(this.senderId)); + this.updateProperty(PROPERTY_SENDINGENOCEAN_ID, HexUtils.bytesToHex(this.senderId)); bridgeHandler.addSender(getConfiguration().senderIdOffset, thing); - return true; } @@ -203,6 +200,22 @@ private void refreshStates() { } } + @Override + protected void sendRequestResponse() { + sendMessage(VIRTUALCHANNEL_SEND_COMMAND, VIRTUALCHANNEL_SEND_COMMAND, OnOffType.ON, null); + } + + protected void sendMessage(String channelId, String channelTypeId, Command command, Configuration channelConfig) { + EEP eep = EEPFactory.createEEP(sendingEEPType); + if (eep.convertFromCommand(channelId, channelTypeId, command, id -> getCurrentState(id), channelConfig) + .hasData()) { + BasePacket msg = eep.setSenderId(senderId).setDestinationId(destinationId) + .setSuppressRepeating(getConfiguration().suppressRepeating).getERP1Message(); + + getBridgeHandler().sendMessage(msg, null); + } + } + @Override public void handleCommand(ChannelUID channelUID, Command command) { // We must have a valid sendingEEPType and sender id to send commands @@ -237,16 +250,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { try { Configuration channelConfig = channel.getConfiguration(); - - EEP eep = EEPFactory.createEEP(sendingEEPType); - if (eep.convertFromCommand(channelId, channelTypeId, command, id -> getCurrentState(id), channelConfig) - .hasData()) { - BasePacket msg = eep.setSenderId(senderId).setDestinationId(destinationId) - .setSuppressRepeating(getConfiguration().suppressRepeating).getERP1Message(); - - getBridgeHandler().sendMessage(msg, null); - } - + sendMessage(channelId, channelTypeId, command, channelConfig); } catch (IllegalArgumentException e) { logger.warn("Exception while sending telegram!", e); } @@ -254,11 +258,16 @@ public void handleCommand(ChannelUID channelUID, Command command) { @Override public void handleRemoval() { - if (getConfiguration().senderIdOffset > 0) { - EnOceanBridgeHandler bridgeHandler = getBridgeHandler(); - if (bridgeHandler != null) { + + EnOceanBridgeHandler bridgeHandler = getBridgeHandler(); + if (bridgeHandler != null) { + if (getConfiguration().senderIdOffset != null && getConfiguration().senderIdOffset > 0) { bridgeHandler.removeSender(getConfiguration().senderIdOffset); } + + if (bridgeHandler.isSmackClient(this.thing)) { + logger.warn("Removing smack client (ThingId: {}) without teach out!", this.thing.getUID().getId()); + } } super.handleRemoval(); diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseSensorHandler.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseSensorHandler.java index d533c1e558c4a..b4fed2ccb4ad7 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseSensorHandler.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseSensorHandler.java @@ -19,8 +19,11 @@ import java.util.Comparator; import java.util.Hashtable; import java.util.Set; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; import java.util.function.Predicate; +import org.apache.commons.lang3.NotImplementedException; import org.openhab.binding.enocean.internal.config.EnOceanBaseConfig; import org.openhab.binding.enocean.internal.eep.EEP; import org.openhab.binding.enocean.internal.eep.EEPFactory; @@ -57,6 +60,8 @@ public class EnOceanBaseSensorHandler extends EnOceanBaseThingHandler implements protected final Hashtable receivingEEPTypes = new Hashtable<>(); + protected ScheduledFuture responseFuture = null; + public EnOceanBaseSensorHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) { super(thing, itemChannelLinkRegistry); } @@ -104,7 +109,7 @@ boolean validateConfig() { } @Override - public long getSenderIdToListenTo() { + public long getEnOceanIdToListenTo() { return Long.parseLong(config.enoceanId, 16); } @@ -129,6 +134,10 @@ protected Predicate channelFilter(EEPType eepType, byte[] senderId) { }; } + protected void sendRequestResponse() { + throw new NotImplementedException("Sensor cannot send responses"); + } + @Override public void packetReceived(BasePacket packet) { ERP1Message msg = (ERP1Message) packet; @@ -175,6 +184,15 @@ public void packetReceived(BasePacket packet) { break; } }); + + if (receivingEEPType.getRequstesResponse()) { + // fire trigger for receive + triggerChannel(prepareAnswer, "requestAnswer"); + // Send response after 100ms + if (responseFuture == null || responseFuture.isDone()) { + responseFuture = scheduler.schedule(this::sendRequestResponse, 100, TimeUnit.MILLISECONDS); + } + } } } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseThingHandler.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseThingHandler.java index 20ccfa80b5062..f561db01859cf 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseThingHandler.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseThingHandler.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.enocean.internal.handler; +import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*; + import java.util.AbstractMap.SimpleEntry; import java.util.Collection; import java.util.Hashtable; @@ -63,18 +65,25 @@ public abstract class EnOceanBaseThingHandler extends ConfigStatusThingHandler { private ItemChannelLinkRegistry itemChannelLinkRegistry; + protected @NonNull ChannelUID prepareAnswer; + public EnOceanBaseThingHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) { super(thing); this.itemChannelLinkRegistry = itemChannelLinkRegistry; + prepareAnswer = new ChannelUID(thing.getUID(), CHANNEL_STATUS_REQUEST_EVENT); } - @SuppressWarnings("null") @Override public void initialize() { logger.debug("Initializing enocean base thing handler."); this.gateway = null; // reset gateway in case we change the bridge this.config = null; - initializeThing((getBridge() == null) ? null : getBridge().getStatus()); + Bridge bridge = getBridge(); + if (bridge == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "A bridge is required"); + } else { + initializeThing(bridge.getStatus()); + } } private void initializeThing(ThingStatus bridgeStatus) { @@ -143,6 +152,10 @@ protected void updateChannels() { String channelId = entry.getKey(); EnOceanChannelDescription cd = entry.getValue().GetSupportedChannels().get(channelId); + if (cd == null) { + return; + } + // if we do not need to auto create channel => skip if (!cd.autoCreate) { return; diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBridgeHandler.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBridgeHandler.java index c888f6a60be2d..e793366f26698 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBridgeHandler.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBridgeHandler.java @@ -15,30 +15,35 @@ import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*; import java.io.IOException; -import java.math.BigDecimal; +import java.util.Arrays; import java.util.Collection; import java.util.LinkedList; import java.util.Set; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import org.openhab.binding.enocean.internal.EnOceanConfigStatusMessage; import org.openhab.binding.enocean.internal.config.EnOceanBaseConfig; import org.openhab.binding.enocean.internal.config.EnOceanBridgeConfig; +import org.openhab.binding.enocean.internal.config.EnOceanBridgeConfig.ESPVersion; import org.openhab.binding.enocean.internal.messages.BasePacket; -import org.openhab.binding.enocean.internal.messages.BaseResponse; import org.openhab.binding.enocean.internal.messages.ESP3PacketFactory; -import org.openhab.binding.enocean.internal.messages.RDBaseIdResponse; -import org.openhab.binding.enocean.internal.messages.RDRepeaterResponse; -import org.openhab.binding.enocean.internal.messages.RDVersionResponse; import org.openhab.binding.enocean.internal.messages.Response; import org.openhab.binding.enocean.internal.messages.Response.ResponseType; +import org.openhab.binding.enocean.internal.messages.Responses.BaseResponse; +import org.openhab.binding.enocean.internal.messages.Responses.RDBaseIdResponse; +import org.openhab.binding.enocean.internal.messages.Responses.RDLearnedClientsResponse; +import org.openhab.binding.enocean.internal.messages.Responses.RDLearnedClientsResponse.LearnedClient; +import org.openhab.binding.enocean.internal.messages.Responses.RDRepeaterResponse; +import org.openhab.binding.enocean.internal.messages.Responses.RDVersionResponse; import org.openhab.binding.enocean.internal.transceiver.EnOceanESP2Transceiver; import org.openhab.binding.enocean.internal.transceiver.EnOceanESP3Transceiver; import org.openhab.binding.enocean.internal.transceiver.EnOceanTransceiver; import org.openhab.binding.enocean.internal.transceiver.PacketListener; import org.openhab.binding.enocean.internal.transceiver.ResponseListener; import org.openhab.binding.enocean.internal.transceiver.ResponseListenerIgnoringTimeouts; +import org.openhab.binding.enocean.internal.transceiver.TeachInListener; import org.openhab.binding.enocean.internal.transceiver.TransceiverErrorListener; import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.status.ConfigStatusMessage; @@ -76,9 +81,12 @@ public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T private byte[] baseId = null; private Thing[] sendingThings = new Thing[128]; - private int nextSenderId = 0; private SerialPortManager serialPortManager; + private boolean smackAvailable = false; + private boolean sendTeachOuts = true; + private Set smackClients = Set.of(); + public EnOceanBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) { super(bridge); this.serialPortManager = serialPortManager; @@ -157,13 +165,6 @@ public void initialize() { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "SerialPortManager could not be found"); } else { - Object devId = getConfig().get(NEXTSENDERID); - if (devId != null) { - nextSenderId = ((BigDecimal) devId).intValue(); - } else { - nextSenderId = 0; - } - if (connectorTask == null || connectorTask.isDone()) { connectorTask = scheduler.scheduleWithFixedDelay(new Runnable() { @Override @@ -187,9 +188,12 @@ private synchronized void initTransceiver() { switch (c.getESPVersion()) { case ESP2: transceiver = new EnOceanESP2Transceiver(c.path, this, scheduler, serialPortManager); + smackAvailable = false; + sendTeachOuts = false; break; case ESP3: transceiver = new EnOceanESP3Transceiver(c.path, this, scheduler, serialPortManager); + sendTeachOuts = c.sendTeachOuts; break; default: break; @@ -200,6 +204,7 @@ private synchronized void initTransceiver() { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "starting rx thread..."); transceiver.StartReceiving(scheduler); + logger.info("EnOceanSerialTransceiver RX thread up and running"); if (c.rs485) { if (c.rs485BaseId != null && !c.rs485BaseId.isEmpty()) { @@ -238,6 +243,28 @@ public void responseReceived(RDBaseIdResponse response) { } } }); + + if (c.getESPVersion() == ESPVersion.ESP3) { + logger.debug("set postmaster mailboxes"); + transceiver.sendBasePacket(ESP3PacketFactory.SA_WR_POSTMASTER((byte) (c.enableSmack ? 20 : 0)), + new ResponseListenerIgnoringTimeouts() { + + @Override + public void responseReceived(BaseResponse response) { + + logger.debug("received response for postmaster mailboxes"); + if (response.isOK()) { + updateProperty("Postmaster mailboxes:", + Integer.toString(c.enableSmack ? 20 : 0)); + smackAvailable = c.enableSmack; + refreshProperties(); + } else { + updateProperty("Postmaster mailboxes:", "Not supported"); + smackAvailable = false; + } + } + }); + } } logger.debug("request version info"); @@ -283,7 +310,7 @@ public Collection getConfigStatus() { Collection configStatusMessages = new LinkedList<>(); // The serial port must be provided - String path = (String) getThing().getConfiguration().get(PATH); + String path = getThing().getConfiguration().as(EnOceanBridgeConfig.class).path; if (path == null || path.isEmpty()) { configStatusMessages.add(ConfigStatusMessage.Builder.error(PATH) .withMessageKeySuffix(EnOceanConfigStatusMessage.PORT_MISSING.getMessageKey()).withArguments(PATH) @@ -297,30 +324,33 @@ public byte[] getBaseId() { return baseId.clone(); } - public int getNextSenderId(Thing sender) { - // TODO: change id to enoceanId + public boolean isSmackClient(Thing sender) { + return smackClients.contains(sender.getConfiguration().as(EnOceanBaseConfig.class).enoceanId); + } + + public Integer getNextSenderId(Thing sender) { return getNextSenderId(sender.getConfiguration().as(EnOceanBaseConfig.class).enoceanId); } - public int getNextSenderId(String senderId) { - if (nextSenderId != 0 && sendingThings[nextSenderId] == null) { - int result = nextSenderId; - Configuration config = getConfig(); - config.put(NEXTSENDERID, null); - updateConfiguration(config); - nextSenderId = 0; + public Integer getNextSenderId(String enoceanId) { + EnOceanBridgeConfig config = getConfigAs(EnOceanBridgeConfig.class); - return result; + if (config.nextSenderId != null && sendingThings[config.nextSenderId] == null) { + Configuration c = this.editConfiguration(); + c.put(PARAMETER_NEXT_SENDERID, null); + updateConfiguration(c); + + return config.nextSenderId; } - for (byte i = 1; i < sendingThings.length; i++) { + for (int i = 1; i < sendingThings.length; i++) { if (sendingThings[i] == null || sendingThings[i].getConfiguration().as(EnOceanBaseConfig.class).enoceanId - .equalsIgnoreCase(senderId)) { + .equalsIgnoreCase(enoceanId)) { return i; } } - return -1; + return null; } public boolean existsSender(int id, Thing sender) { @@ -345,7 +375,7 @@ public void sendMessage(BasePacket message, ResponseListene } public void addPacketListener(PacketListener listener) { - addPacketListener(listener, listener.getSenderIdToListenTo()); + addPacketListener(listener, listener.getEnOceanIdToListenTo()); } public void addPacketListener(PacketListener listener, long senderIdToListenTo) { @@ -355,7 +385,7 @@ public void addPacketListener(PacketListener listener, long senderIdToListenTo) } public void removePacketListener(PacketListener listener) { - removePacketListener(listener, listener.getSenderIdToListenTo()); + removePacketListener(listener, listener.getEnOceanIdToListenTo()); } public void removePacketListener(PacketListener listener, long senderIdToListenTo) { @@ -364,12 +394,74 @@ public void removePacketListener(PacketListener listener, long senderIdToListenT } } - public void startDiscovery(PacketListener teachInListener) { + public void startDiscovery(TeachInListener teachInListener) { transceiver.startDiscovery(teachInListener); + + if (smackAvailable) { + // activate smack teach in + logger.debug("activate smack teach in"); + try { + transceiver.sendBasePacket(ESP3PacketFactory.SA_WR_LEARNMODE(true), + new ResponseListenerIgnoringTimeouts() { + + @Override + public void responseReceived(BaseResponse response) { + + if (response.isOK()) { + logger.debug("Smack teach in activated"); + } + } + }); + } catch (IOException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "Smack packet could not be send: " + e.getMessage()); + } + } } public void stopDiscovery() { transceiver.stopDiscovery(); + + try { + transceiver.sendBasePacket(ESP3PacketFactory.SA_WR_LEARNMODE(false), null); + refreshProperties(); + } catch (IOException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "Smack packet could not be send: " + e.getMessage()); + } + } + + private void refreshProperties() { + if (getThing().getStatus() == ThingStatus.ONLINE && smackAvailable) { + + logger.debug("request learned smack clients"); + try { + transceiver.sendBasePacket(ESP3PacketFactory.SA_RD_LEARNEDCLIENTS, + new ResponseListenerIgnoringTimeouts() { + + @Override + public void responseReceived(RDLearnedClientsResponse response) { + + logger.debug("received response for learned smack clients"); + if (response.isValid() && response.isOK()) { + LearnedClient[] clients = response.getLearnedClients(); + updateProperty("Learned smart ack clients", Integer.toString(clients.length)); + updateProperty("Smart ack clients", + Arrays.stream(clients) + .map(x -> String.format("%s (MB Idx: %d)", + HexUtils.bytesToHex(x.clientId), x.mailboxIndex)) + .collect(Collectors.joining(", "))); + smackClients = Arrays.stream(clients).map(x -> HexUtils.bytesToHex(x.clientId)) + .collect(Collectors.toSet()); + } + } + }); + } catch (IOException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "Smack packet could not be send: " + e.getMessage()); + + } + } } @Override @@ -378,4 +470,8 @@ public void ErrorOccured(Throwable exception) { transceiver = null; updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, exception.getMessage()); } + + public boolean sendTeachOuts() { + return sendTeachOuts; + } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanClassicDeviceHandler.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanClassicDeviceHandler.java index 412cd9ad8d8fe..08a1e499e82d4 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanClassicDeviceHandler.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanClassicDeviceHandler.java @@ -71,7 +71,7 @@ void initializeConfig() { } @Override - public long getSenderIdToListenTo() { + public long getEnOceanIdToListenTo() { return 0; } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/ESP3PacketFactory.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/ESP3PacketFactory.java index 73551cdc83bbe..f410d6e1aba8a 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/ESP3PacketFactory.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/ESP3PacketFactory.java @@ -13,8 +13,10 @@ package org.openhab.binding.enocean.internal.messages; import org.openhab.binding.enocean.internal.EnOceanBindingConstants; +import org.openhab.binding.enocean.internal.Helper; import org.openhab.binding.enocean.internal.messages.BasePacket.ESPPacketType; import org.openhab.binding.enocean.internal.messages.CCMessage.CCMessageType; +import org.openhab.binding.enocean.internal.messages.SAMessage.SAMessageType; import org.openhab.core.library.types.StringType; /** @@ -42,6 +44,28 @@ public static BasePacket CO_WR_REPEATER(StringType level) { } } + public static BasePacket SA_WR_LEARNMODE(boolean activate) { + return new SAMessage(SAMessageType.SA_WR_LEARNMODE, + new byte[] { SAMessageType.SA_WR_LEARNMODE.getValue(), (byte) (activate ? 1 : 0), 0, 0, 0, 0, 0 }); + } + + public final static BasePacket SA_RD_LEARNEDCLIENTS = new SAMessage(SAMessageType.SA_RD_LEARNEDCLIENTS); + + public static BasePacket SA_RD_MAILBOX_STATUS(byte[] clientId, byte[] controllerId) { + return new SAMessage(SAMessageType.SA_RD_MAILBOX_STATUS, + Helper.concatAll(new byte[] { SAMessageType.SA_RD_MAILBOX_STATUS.getValue() }, clientId, controllerId)); + } + + public static BasePacket SA_WR_POSTMASTER(byte mailboxes) { + return new SAMessage(SAMessageType.SA_WR_POSTMASTER, + new byte[] { SAMessageType.SA_WR_POSTMASTER.getValue(), mailboxes }); + } + + public static BasePacket SA_WR_CLIENTLEARNRQ(byte manu1, byte manu2, byte rorg, byte func, byte type) { + return new SAMessage(SAMessageType.SA_WR_CLIENTLEARNRQ, + new byte[] { SAMessageType.SA_WR_CLIENTLEARNRQ.getValue(), manu1, manu2, rorg, func, type }); + } + public static BasePacket BuildPacket(int dataLength, int optionalDataLength, byte packetType, byte[] payload) { ESPPacketType type = ESPPacketType.getPacketType(packetType); @@ -50,6 +74,8 @@ public static BasePacket BuildPacket(int dataLength, int optionalDataLength, byt return new Response(dataLength, optionalDataLength, payload); case RADIO_ERP1: return new ERP1Message(dataLength, optionalDataLength, payload); + case EVENT: + return new EventMessage(dataLength, optionalDataLength, payload); default: return null; } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/EventMessage.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/EventMessage.java new file mode 100644 index 0000000000000..4cacceba47052 --- /dev/null +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/EventMessage.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.enocean.internal.messages; + +import java.util.stream.Stream; + +/** + * + * @author Daniel Weber - Initial contribution + */ +public class EventMessage extends BasePacket { + + public enum EventMessageType { + UNKNOWN((byte) 0x00, 1), + SA_RECLAIM_NOT_SUCCESSFUL((byte) 0x01, 1), + SA_CONFIRM_LEARN((byte) 0x02, 17), + SA_LEARN_ACK((byte) 0x03, 4), + CO_READY((byte) 0x04, 2), + CO_EVENT_SECUREDEVICES((byte) 0x05, 6), + CO_DUTYCYCLE_LIMIT((byte) 0x06, 2), + CO_TRANSMIT_FAILED((byte) 0x07, 2); + + private byte value; + private int dataLength; + + EventMessageType(byte value, int dataLength) { + this.value = value; + this.dataLength = dataLength; + } + + public byte getValue() { + return this.value; + } + + public int getDataLength() { + return dataLength; + } + + public static EventMessageType getEventMessageType(byte value) { + return Stream.of(EventMessageType.values()).filter(t -> t.value == value).findFirst().orElse(UNKNOWN); + } + } + + private EventMessageType type; + + EventMessage(int dataLength, int optionalDataLength, byte[] payload) { + super(dataLength, optionalDataLength, ESPPacketType.EVENT, payload); + + type = EventMessageType.getEventMessageType(payload[0]); + } + + public EventMessageType getEventMessageType() { + return type; + } +} diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Response.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Response.java index 2c8a0699fe366..e7b8f2094d30a 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Response.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Response.java @@ -57,7 +57,7 @@ public static ResponseType getResponsetype(byte value) { protected ResponseType responseType; protected boolean _isValid = false; - protected Response(int dataLength, int optionalDataLength, byte[] payload) { + public Response(int dataLength, int optionalDataLength, byte[] payload) { super(dataLength, optionalDataLength, ESPPacketType.RESPONSE, payload); try { diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/BaseResponse.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/BaseResponse.java similarity index 85% rename from bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/BaseResponse.java rename to bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/BaseResponse.java index 63de3f5cdd984..56a4ccbbf869d 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/BaseResponse.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/BaseResponse.java @@ -10,9 +10,10 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.enocean.internal.messages; +package org.openhab.binding.enocean.internal.messages.Responses; import org.openhab.binding.enocean.internal.Helper; +import org.openhab.binding.enocean.internal.messages.Response; /** * diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/RDBaseIdResponse.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDBaseIdResponse.java similarity index 91% rename from bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/RDBaseIdResponse.java rename to bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDBaseIdResponse.java index e2220c053f863..b2a8a0da72ea2 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/RDBaseIdResponse.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDBaseIdResponse.java @@ -10,9 +10,10 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.enocean.internal.messages; +package org.openhab.binding.enocean.internal.messages.Responses; import org.openhab.binding.enocean.internal.Helper; +import org.openhab.binding.enocean.internal.messages.Response; /** * diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDLearnedClientsResponse.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDLearnedClientsResponse.java new file mode 100644 index 0000000000000..481915212bd31 --- /dev/null +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDLearnedClientsResponse.java @@ -0,0 +1,61 @@ +/** + * 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.enocean.internal.messages.Responses; + +import java.util.Arrays; + +import org.openhab.binding.enocean.internal.Helper; +import org.openhab.binding.enocean.internal.messages.Response; + +/** + * + * @author Daniel Weber - Initial contribution + */ +public class RDLearnedClientsResponse extends Response { + + public class LearnedClient { + public byte[] clientId; + public byte[] controllerId; + public int mailboxIndex; + } + + LearnedClient[] learnedClients; + + public RDLearnedClientsResponse(Response response) { + this(response.getPayload().length, response.getOptionalPayload().length, + Helper.concatAll(response.getPayload(), response.getOptionalPayload())); + } + + RDLearnedClientsResponse(int dataLength, int optionalDataLength, byte[] payload) { + super(dataLength, optionalDataLength, payload); + + if (payload == null || ((payload.length - 1) % 9) != 0) { + return; + } else { + _isValid = true; + } + + learnedClients = new LearnedClient[(payload.length - 1) / 9]; + for (int i = 0; i < learnedClients.length; i++) { + LearnedClient client = new LearnedClient(); + client.clientId = Arrays.copyOfRange(payload, 1 + i * 9, 1 + i * 9 + 4); + client.controllerId = Arrays.copyOfRange(payload, 5 + i * 9, 5 + i * 9 + 4); + client.mailboxIndex = payload[9 + i * 9] & 0xFF; + learnedClients[i] = client; + } + } + + public LearnedClient[] getLearnedClients() { + return learnedClients; + } +} diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/RDRepeaterResponse.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDRepeaterResponse.java similarity index 93% rename from bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/RDRepeaterResponse.java rename to bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDRepeaterResponse.java index cecc623b86dcb..440068ba4dcfd 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/RDRepeaterResponse.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDRepeaterResponse.java @@ -10,11 +10,12 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.enocean.internal.messages; +package org.openhab.binding.enocean.internal.messages.Responses; import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*; import org.eclipse.jdt.annotation.NonNull; +import org.openhab.binding.enocean.internal.messages.Response; import org.openhab.core.library.types.StringType; /** diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/RDVersionResponse.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDVersionResponse.java similarity index 94% rename from bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/RDVersionResponse.java rename to bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDVersionResponse.java index 1374c56e01970..daedc9aa9ae01 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/RDVersionResponse.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDVersionResponse.java @@ -10,11 +10,12 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.enocean.internal.messages; +package org.openhab.binding.enocean.internal.messages.Responses; import java.util.Arrays; import org.eclipse.jdt.annotation.NonNull; +import org.openhab.binding.enocean.internal.messages.Response; import org.openhab.core.util.HexUtils; /** diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/SMACKTeachInResponse.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/SMACKTeachInResponse.java new file mode 100644 index 0000000000000..a827922d300fb --- /dev/null +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/SMACKTeachInResponse.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.enocean.internal.messages.Responses; + +import org.openhab.binding.enocean.internal.messages.Response; + +/** + * + * @author Daniel Weber - Initial contribution + */ +public class SMACKTeachInResponse extends Response { + + // set response time to 250ms + static final byte RESPONSE_TIME_HVALUE = 0; + static final byte RESPONSE_TIME_LVALUE = (byte) 0xFA; + + static final byte TEACH_IN = 0x00; + static final byte TEACH_OUT = 0x20; + static final byte REPEATED_TEACH_IN = 0x01; + static final byte NOPLACE_FOR_MAILBOX = 0x12; + static final byte BAD_RSSI = 0x14; + + public SMACKTeachInResponse() { + super(4, 0, new byte[] { Response.ResponseType.RET_OK.getValue(), RESPONSE_TIME_HVALUE, RESPONSE_TIME_LVALUE, + TEACH_IN }); + } + + public void setTeachOutResponse() { + data[3] = TEACH_OUT; + } + + public boolean isTeachOut() { + return data[3] == TEACH_OUT; + } + + public void setRepeatedTeachInResponse() { + data[3] = REPEATED_TEACH_IN; + } + + public void setNoPlaceForFurtherMailbox() { + data[3] = NOPLACE_FOR_MAILBOX; + } + + public void setBadRSSI() { + data[3] = BAD_RSSI; + } + + public void setTeachIn() { + data[3] = TEACH_IN; + } + + public boolean isTeachIn() { + return data[3] == TEACH_IN; + } +} diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/SAMessage.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/SAMessage.java new file mode 100644 index 0000000000000..e63087ee58717 --- /dev/null +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/SAMessage.java @@ -0,0 +1,64 @@ +/** + * 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.enocean.internal.messages; + +/** + * + * @author Daniel Weber - Initial contribution + */ +public class SAMessage extends BasePacket { + + public enum SAMessageType { + SA_WR_LEARNMODE((byte) 0x01, 7), + SA_RD_LEARNMODE((byte) 0x02, 1), + SA_WR_LEARNCONFIRM((byte) 0x03, 1), + SA_WR_CLIENTLEARNRQ((byte) 0x04, 6), + SA_WR_RESET((byte) 0x05, 1), + SA_RD_LEARNEDCLIENTS((byte) 0x06, 1), + SA_WR_RECLAIMS((byte) 0x07, 1), + SA_WR_POSTMASTER((byte) 0x08, 2), + SA_RD_MAILBOX_STATUS((byte) 0x09, 9); + + private byte value; + private int dataLength; + + SAMessageType(byte value, int dataLength) { + this.value = value; + this.dataLength = dataLength; + } + + public byte getValue() { + return this.value; + } + + public int getDataLength() { + return dataLength; + } + } + + private SAMessageType type; + + public SAMessage(SAMessageType type) { + this(type, new byte[] { type.getValue() }); + } + + public SAMessage(SAMessageType type, byte[] payload) { + super(type.getDataLength(), 0, ESPPacketType.SMART_ACK_COMMAND, payload); + + this.type = type; + } + + public SAMessageType getSAMessageType() { + return type; + } +} diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EnOceanESP3Transceiver.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EnOceanESP3Transceiver.java index db15a3fec0c5f..28e8b04209630 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EnOceanESP3Transceiver.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EnOceanESP3Transceiver.java @@ -18,8 +18,6 @@ import org.openhab.binding.enocean.internal.EnOceanException; import org.openhab.binding.enocean.internal.messages.BasePacket; -import org.openhab.binding.enocean.internal.messages.ERP1Message; -import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG; import org.openhab.binding.enocean.internal.messages.ESP3Packet; import org.openhab.binding.enocean.internal.messages.ESP3PacketFactory; import org.openhab.binding.enocean.internal.messages.Response; @@ -138,21 +136,8 @@ protected void processMessage(byte firstByte) { HexUtils.bytesToHex(packet.getPayload())); break; case EVENT: - logger.debug("Event occured: {}", HexUtils.bytesToHex(packet.getPayload())); - break; - case RADIO_ERP1: { - ERP1Message msg = (ERP1Message) packet; - logger.debug("{} with RORG {} for {} payload {} received", - packet.getPacketType().name(), msg.getRORG().name(), - HexUtils.bytesToHex(msg.getSenderId()), HexUtils.bytesToHex( - Arrays.copyOf(dataBuffer, dataLength + optionalLength))); - - if (msg.getRORG() != RORG.Unknown) { - informListeners(msg); - } else { - logger.debug("Received unknown RORG"); - } - } + case RADIO_ERP1: + informListeners(packet); break; case RADIO_ERP2: break; diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EnOceanTransceiver.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EnOceanTransceiver.java index df729a1dbc0e2..55be4988b0259 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EnOceanTransceiver.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EnOceanTransceiver.java @@ -27,9 +27,13 @@ import org.openhab.binding.enocean.internal.EnOceanBindingConstants; import org.openhab.binding.enocean.internal.EnOceanException; +import org.openhab.binding.enocean.internal.Helper; import org.openhab.binding.enocean.internal.messages.BasePacket; +import org.openhab.binding.enocean.internal.messages.BasePacket.ESPPacketType; import org.openhab.binding.enocean.internal.messages.ERP1Message; import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG; +import org.openhab.binding.enocean.internal.messages.EventMessage; +import org.openhab.binding.enocean.internal.messages.EventMessage.EventMessageType; import org.openhab.binding.enocean.internal.messages.Response; import org.openhab.core.io.transport.serial.PortInUseException; import org.openhab.core.io.transport.serial.SerialPort; @@ -138,7 +142,8 @@ private synchronized void send() throws IOException { Request currentRequest = null; protected Map> listeners; - protected PacketListener teachInListener; + protected HashSet eventListeners; + protected TeachInListener teachInListener; protected InputStream inputStream; protected OutputStream outputStream; @@ -151,6 +156,7 @@ public EnOceanTransceiver(String path, TransceiverErrorListener errorListener, S requestQueue = new RequestQueue(scheduler); listeners = new HashMap<>(); + eventListeners = new HashSet<>(); teachInListener = null; this.errorListener = errorListener; @@ -192,6 +198,7 @@ public void run() { } }); } + logger.info("EnOceanSerialTransceiver RX thread started"); } public void ShutDown() { @@ -266,36 +273,65 @@ protected int read(byte[] buffer, int length) { } } - protected void informListeners(ERP1Message msg) { + protected void informListeners(BasePacket packet) { try { - byte[] senderId = msg.getSenderId(); + if (packet.getPacketType() == ESPPacketType.RADIO_ERP1) { + ERP1Message msg = (ERP1Message) packet; + byte[] senderId = msg.getSenderId(); + byte[] d = Helper.concatAll(msg.getPayload(), msg.getOptionalPayload()); + + logger.debug("{} with RORG {} for {} payload {} received", packet.getPacketType().name(), + msg.getRORG().name(), HexUtils.bytesToHex(msg.getSenderId()), HexUtils.bytesToHex(d)); + + if (msg.getRORG() != RORG.Unknown) { + if (senderId != null) { + if (filteredDeviceId != null && senderId[0] == filteredDeviceId[0] + && senderId[1] == filteredDeviceId[1] && senderId[2] == filteredDeviceId[2]) { + // filter away own messages which are received through a repeater + return; + } - if (senderId != null) { - if (filteredDeviceId != null && senderId[0] == filteredDeviceId[0] && senderId[1] == filteredDeviceId[1] - && senderId[2] == filteredDeviceId[2]) { - // filter away own messages which are received through a repeater - return; - } + if (teachInListener != null && (msg.getIsTeachIn() || msg.getRORG() == RORG.RPS)) { + logger.info("Received teach in message from {}", HexUtils.bytesToHex(msg.getSenderId())); + teachInListener.packetReceived(msg); + return; + } else if (teachInListener == null && msg.getIsTeachIn()) { + logger.info("Discard message because this is a teach-in telegram from {}!", + HexUtils.bytesToHex(msg.getSenderId())); + return; + } - if (teachInListener != null) { - if (msg.getIsTeachIn() || (msg.getRORG() == RORG.RPS)) { - logger.info("Received teach in message from {}", HexUtils.bytesToHex(msg.getSenderId())); - teachInListener.packetReceived(msg); - return; + long s = Long.parseLong(HexUtils.bytesToHex(senderId), 16); + HashSet pl = listeners.get(s); + if (pl != null) { + pl.forEach(l -> l.packetReceived(msg)); + } } } else { - if (msg.getIsTeachIn()) { - logger.info("Discard message because this is a teach-in telegram from {}!", - HexUtils.bytesToHex(msg.getSenderId())); + logger.debug("Received unknown RORG"); + } + } else if (packet.getPacketType() == ESPPacketType.EVENT) { + EventMessage event = (EventMessage) packet; + + byte[] d = Helper.concatAll(packet.getPayload(), packet.getOptionalPayload()); + logger.debug("{} with type {} payload {} received", ESPPacketType.EVENT.name(), + event.getEventMessageType().name(), HexUtils.bytesToHex(d)); + + if (event.getEventMessageType() == EventMessageType.SA_CONFIRM_LEARN) { + byte[] senderId = event.getPayload(EventMessageType.SA_CONFIRM_LEARN.getDataLength() - 5, 4); + + if (teachInListener != null) { + logger.info("Received smart teach in from {}", HexUtils.bytesToHex(senderId)); + teachInListener.eventReceived(event); + return; + } else { + logger.info("Discard message because this is a smart teach-in telegram from {}!", + HexUtils.bytesToHex(senderId)); return; } } - long s = Long.parseLong(HexUtils.bytesToHex(senderId), 16); - HashSet pl = listeners.get(s); - if (pl != null) { - pl.forEach(l -> l.packetReceived(msg)); - } + eventListeners.forEach(l -> l.eventReceived(event)); } } catch (Exception e) { logger.error("Exception in informListeners", e); @@ -354,7 +390,15 @@ public void removePacketListener(PacketListener listener, long senderIdToListenT } } - public void startDiscovery(PacketListener teachInListener) { + public void addEventMessageListener(EventListener listener) { + eventListeners.add(listener); + } + + public void removeEventMessageListener(EventListener listener) { + eventListeners.remove(listener); + } + + public void startDiscovery(TeachInListener teachInListener) { this.teachInListener = teachInListener; } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EventListener.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EventListener.java new file mode 100644 index 0000000000000..2a27bfb14f15b --- /dev/null +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EventListener.java @@ -0,0 +1,23 @@ +/** + * 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.enocean.internal.transceiver; + +import org.openhab.binding.enocean.internal.messages.EventMessage; + +/** + * + * @author Daniel Weber - Initial contribution + */ +public interface EventListener { + public void eventReceived(EventMessage event); +} diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/PacketListener.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/PacketListener.java index 04e55fdba857b..da161441aa87a 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/PacketListener.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/PacketListener.java @@ -22,5 +22,5 @@ public interface PacketListener { public void packetReceived(BasePacket packet); - public long getSenderIdToListenTo(); + public long getEnOceanIdToListenTo(); } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/TeachInListener.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/TeachInListener.java new file mode 100644 index 0000000000000..06a26c41800c0 --- /dev/null +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/TeachInListener.java @@ -0,0 +1,21 @@ +/** + * 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.enocean.internal.transceiver; + +/** + * + * @author Daniel Weber - Initial contribution + */ +public interface TeachInListener extends PacketListener, EventListener { + +} diff --git a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/CentralCommand.xml b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/CentralCommand.xml index 7115374ab58f8..855048b00add7 100644 --- a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/CentralCommand.xml +++ b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/CentralCommand.xml @@ -18,7 +18,7 @@ EnOceanId of device this thing belongs to true - + Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be determined by bridge diff --git a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/ClassicDevice.xml b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/ClassicDevice.xml index 6d24ab2084156..93b135f8a779f 100644 --- a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/ClassicDevice.xml +++ b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/ClassicDevice.xml @@ -20,7 +20,7 @@ - + Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be determined by bridge diff --git a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/GenericThing.xml b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/GenericThing.xml index 2fa60566e40e8..b42b6b76202a7 100644 --- a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/GenericThing.xml +++ b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/GenericThing.xml @@ -18,7 +18,7 @@ EnOceanId of device this thing belongs to true - + Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be determined by bridge diff --git a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/HeatRecoveryVentilation.xml b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/HeatRecoveryVentilation.xml index cd58af2cbf2fa..f7430123b677a 100644 --- a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/HeatRecoveryVentilation.xml +++ b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/HeatRecoveryVentilation.xml @@ -18,7 +18,7 @@ EnOceanId of device this thing belongs to true - + Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be determined by bridge diff --git a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/MeasurementSwitch.xml b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/MeasurementSwitch.xml index 0f095d5ed6e18..08e75f162e9f9 100644 --- a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/MeasurementSwitch.xml +++ b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/MeasurementSwitch.xml @@ -18,7 +18,7 @@ EnOceanId of device this thing belongs to true - + Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be determined by bridge diff --git a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/Rollershutter.xml b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/Rollershutter.xml index 486ceebf0e2e4..4a51f1614bf98 100644 --- a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/Rollershutter.xml +++ b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/Rollershutter.xml @@ -18,7 +18,7 @@ EnOceanId of device this thing belongs to true - + Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be determined by bridge diff --git a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/Thermostat.xml b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/Thermostat.xml index 2be75d8e93033..5f1d44fe7e84a 100644 --- a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/Thermostat.xml +++ b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/Thermostat.xml @@ -18,7 +18,7 @@ EnOceanId of device this thing belongs to true - + Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be determined by bridge diff --git a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/bridge.xml b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/bridge.xml index afa99f504c7f8..c712af8cd2d90 100644 --- a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/bridge.xml +++ b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/bridge.xml @@ -31,6 +31,11 @@ true ESP3 + + + Declare Gateway as a SMACK Postmaster and handle SMACK messages + true + true @@ -41,9 +46,15 @@ 00000000 - + + true + + Should a learned in or teach out response been send on a repeated smack teach in request + false + + true - + Defines the next device Id, if empty, the next device id is automatically determined diff --git a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/channels.xml index 9469d48b8c9af..941eb0d76487a 100644 --- a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/channels.xml @@ -488,13 +488,6 @@ - - Switch - - You can send telegrams to the device by switching this channel on - thermostat - - Switch