]> git.basschouten.com Git - openhab-addons.git/commitdiff
[enocean] Improved device discovery and added SMACK capability (#10157)
authorDaniel Weber <25605184+fruggy83@users.noreply.github.com>
Sat, 20 Feb 2021 16:13:28 +0000 (17:13 +0100)
committerGitHub <noreply@github.com>
Sat, 20 Feb 2021 16:13:28 +0000 (17:13 +0100)
 * 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 <uni@fruggy.de>
62 files changed:
bundles/org.openhab.binding.enocean/README.md
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/EnOceanBindingConstants.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/EnOceanHandlerFactory.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanActuatorConfig.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanBaseConfig.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanBridgeConfig.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanChannelTransformationConfig.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/discovery/EnOceanDeviceDiscoveryService.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_02/A5_02.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_04/A5_04.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_07/A5_07.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_08/A5_08.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_10/A5_10.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_20/A5_20_04.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/PTM200Message.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/UTEResponse.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/_4BSTeachInVariation3Response.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/_RPSMessage.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/D2_05/D2_05_00.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/EEPFactory.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/EEPType.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_01/F6_01_01.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_02/F6_02_01.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_02/F6_02_02.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_05/F6_05_02.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_00.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_00_EltakoFPE.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_01.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Generic/GenericEEP.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseActuatorHandler.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseSensorHandler.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseThingHandler.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBridgeHandler.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanClassicDeviceHandler.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/BaseResponse.java [deleted file]
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/ESP3PacketFactory.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/EventMessage.java [new file with mode: 0644]
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/RDBaseIdResponse.java [deleted file]
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/RDRepeaterResponse.java [deleted file]
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/RDVersionResponse.java [deleted file]
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Response.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/BaseResponse.java [new file with mode: 0644]
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDBaseIdResponse.java [new file with mode: 0644]
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDLearnedClientsResponse.java [new file with mode: 0644]
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDRepeaterResponse.java [new file with mode: 0644]
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDVersionResponse.java [new file with mode: 0644]
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/SMACKTeachInResponse.java [new file with mode: 0644]
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/SAMessage.java [new file with mode: 0644]
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EnOceanESP3Transceiver.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EnOceanTransceiver.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EventListener.java [new file with mode: 0644]
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/PacketListener.java
bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/TeachInListener.java [new file with mode: 0644]
bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/CentralCommand.xml
bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/ClassicDevice.xml
bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/GenericThing.xml
bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/HeatRecoveryVentilation.xml
bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/MeasurementSwitch.xml
bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/Rollershutter.xml
bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/Thermostat.xml
bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/bridge.xml
bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/channels.xml

index e6ff99b0a1686b2cc2c61a5e3c8b9923a0f1011f..38600c86d2e4b74195ce9e948bd7850692787a93 100644 (file)
@@ -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.
index 4ae4bf2240bcac6ae1601c88a1f8cde39467991b..c6b3814c5edbc10ae9fe740016c130ca58a01339 100644 (file)
@@ -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";
index 3ac9ea809ccedab5a18f5363eb407edba3195129..bbc82fff8d2b916f13f2ef8600c520e8906f2df4 100644 (file)
@@ -28,6 +28,7 @@ import org.openhab.core.config.discovery.DiscoveryService;
 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 @@ public class EnOceanHandlerFactory extends BaseThingHandlerFactory {
     }
 
     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<>()));
index 461e2e0c95d5da19ffbd78ba59f5017de80e07c2..eb9291abcafca564be604c512eeb2c4de202e028 100644 (file)
@@ -19,7 +19,7 @@ package org.openhab.binding.enocean.internal.config;
 public class EnOceanActuatorConfig extends EnOceanBaseConfig {
 
     public int channel;
-    public int senderIdOffset = -1;
+    public Integer senderIdOffset = null;
     public String manufacturerId;
     public String teachInType;
 
index 49a0cdae330dfb1388d6a1383605f42972bf0172..100d83a3b86338bfbfbe02bb3930125fe8db495f 100644 (file)
@@ -17,15 +17,23 @@ import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.EMPTY
 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<String> receivingEEPId = new ArrayList<>();
     public boolean receivingSIGEEP = false;
 
index baad63a36ed47914c1f0460fae63e36ece531688..6e6bc671a4461bb0690b39f0b647a84f29d2940f 100644 (file)
@@ -46,10 +46,16 @@ public class EnOceanBridgeConfig {
     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() {
index 36471ec37be02649cdd5baea86eae2fe4e838ead..81c701f4579e46fb85074ec6d2f8a494ba5a5923 100644 (file)
  */
 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", "");
+    }
 }
index 030e601e576bf2c824d0bada080adbf13bb9e482..0373c474ba586ce5861f9cae1287b48b36160535 100644 (file)
@@ -25,9 +25,14 @@ import org.openhab.binding.enocean.internal.handler.EnOceanBridgeHandler;
 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 @@ import org.slf4j.LoggerFactory;
  *
  * @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 class EnOceanDeviceDiscoveryService extends AbstractDiscoveryService impl
         }
 
         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;
     }
index 30c8522f7c2d2145ef8e25e0ef3c3379af97dbc8..957296c15f37f819bb7376148deed04dd11280dc 100644 (file)
@@ -20,7 +20,6 @@ import org.openhab.core.config.core.Configuration;
 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 @@ public abstract class A5_02 extends _4BSMessage {
     @Override
     protected State convertToStateImpl(String channelId, String channelTypeId,
             Function<String, State> getCurrentStateFunc, Configuration config) {
-        if (!isValid()) {
-            return UnDefType.UNDEF;
-        }
 
         double scaledTemp = getScaledMin()
                 - (((getUnscaledMin() - getUnscaledTemperatureValue()) * (getScaledMin() - getScaledMax()))
index fa4241caf95482c6f593531a98217ddd62313c5a..355d6365b356ebb2fa37ca9b7a5280a6405148cb 100644 (file)
@@ -62,9 +62,6 @@ public abstract class A5_04 extends _4BSMessage {
     @Override
     protected State convertToStateImpl(String channelId, String channelTypeId,
             Function<String, State> getCurrentStateFunc, Configuration config) {
-        if (!isValid()) {
-            return UnDefType.UNDEF;
-        }
 
         if (channelId.equals(CHANNEL_TEMPERATURE)) {
             double scaledTemp = getScaledTemperatureMin()
index ac78bbf02e832e9b05a829b45b74dba8ea322f04..56f074f799ae90c8e53bafc1b99bf5e013cc5241 100644 (file)
@@ -53,9 +53,6 @@ public abstract class A5_07 extends _4BSMessage {
     @Override
     protected State convertToStateImpl(String channelId, String channelTypeId,
             Function<String, State> getCurrentStateFunc, Configuration config) {
-        if (!isValid()) {
-            return UnDefType.UNDEF;
-        }
 
         if (channelId.equals(CHANNEL_ILLUMINATION)) {
             return getIllumination();
index 5f6a54c806dd126bd39942d0cb0fe6ef77ca5995..cf471e7fd713617bbc67aea1e5dd5cfa2dc8a663 100644 (file)
@@ -71,9 +71,6 @@ public abstract class A5_08 extends _4BSMessage {
     @Override
     protected State convertToStateImpl(String channelId, String channelTypeId,
             Function<String, State> getCurrentStateFunc, Configuration config) {
-        if (!isValid()) {
-            return UnDefType.UNDEF;
-        }
 
         if (channelId.equals(CHANNEL_TEMPERATURE)) {
             double scaledTemp = getScaledTemperatureMin()
index 99def10649e1568ffd0e0b9fc4fb7ee1b0c3506e..465ce91d2528f6b98bfdb30f1c9b59c8ab065f4b 100644 (file)
@@ -40,9 +40,6 @@ public abstract class A5_10 extends _4BSMessage {
     @Override
     protected State convertToStateImpl(String channelId, String channelTypeId,
             Function<String, State> getCurrentStateFunc, Configuration config) {
-        if (!isValid()) {
-            return UnDefType.UNDEF;
-        }
 
         switch (channelId) {
             case CHANNEL_FANSPEEDSTAGE:
index 1c54b86f78c95e5a0368fab1e42bb67a0549afa7..058a34b7601637f40e79d96488325a5dda2a5aa7 100644 (file)
@@ -169,7 +169,7 @@ public class A5_20_04 extends A5_20 {
     @Override
     protected void convertFromCommandImpl(String channelId, String channelTypeId, Command command,
             Function<String, State> 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));
index ce476f4f852379d9b369b1df15ce3610deb72022..ead041877797ca387b0c275f745d4a6f13d7253e 100644 (file)
@@ -55,9 +55,6 @@ public class PTM200Message extends _RPSMessage {
     @Override
     protected State convertToStateImpl(String channelId, String channelTypeId,
             Function<String, State> getCurrentStateFunc, Configuration config) {
-        if (!isValid()) {
-            return UnDefType.UNDEF;
-        }
 
         switch (channelId) {
             case CHANNEL_GENERAL_SWITCHING:
@@ -77,4 +74,9 @@ public class PTM200Message extends _RPSMessage {
 
         return UnDefType.UNDEF;
     }
+
+    @Override
+    public boolean isValidForTeachIn() {
+        return false;
+    }
 }
index 41cdcb1a88462ff2334a0f5c692fc5cc5256c061..21dba840f65b3540b112694dc0ed1311c325f148 100644 (file)
@@ -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);
index 96d1200b0c4eb4af2b632d448dc449a35b3a8529..651a786af8d72e413fd3251dab3a06fcfc8a895f 100644 (file)
@@ -23,11 +23,11 @@ import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG;
  */
 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());
index c6d54180a628aa3d8b523973cb0b83fe573a7705..018b3ea06335bdd6e78492f8c1cf25b46dca113d 100644 (file)
@@ -98,7 +98,8 @@ public class D2_05_00 extends _VLDMessage {
 
     @Override
     public void addConfigPropertiesTo(DiscoveryResultBuilder discoveredThingResultBuilder) {
-        discoveredThingResultBuilder.withProperty(PARAMETER_EEPID, getEEPType().getId());
+        discoveredThingResultBuilder.withProperty(PARAMETER_SENDINGEEPID, getEEPType().getId())
+                .withProperty(PARAMETER_RECEIVINGEEPID, getEEPType().getId());
     }
 
     @Override
index 63e9c0fcf3f433042a90678c8ef24d9d2d200d13..c84a1a753f7f7315f7b0c08de6d609e1ea817a50 100644 (file)
@@ -15,18 +15,24 @@ package org.openhab.binding.enocean.internal.eep;
 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 class EEPFactory {
             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 class EEPFactory {
         }
     }
 
+    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 class EEPFactory {
         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 class EEPFactory {
             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 class EEPFactory {
                 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 class EEPFactory {
 
                 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 class EEPFactory {
                 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;
+    }
 }
index 451c853303fddc3eb9e282ac869a8e6ff51e3bc2..26c461ae8257c5ca6fd8779b1c7ebb9813597454 100644 (file)
@@ -20,6 +20,7 @@ import java.util.Map;
 
 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<String, Configuration>() {
+    EltakoFSB(RORG._4BS, 0x3f, 0x7f, false, false, "EltakoFSB", 0, A5_3F_7F_EltakoFSB.class, THING_TYPE_ROLLERSHUTTER,
+            0, new Hashtable<String, Configuration>() {
                 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<? extends EEP> 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<? extends EEP> 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<? extends EEP> 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<? extends EEP> 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<? extends EEP> 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<? extends EEP> 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<? extends EEP> eepClass, ThingTypeUID thingTypeUID, int command,
+    EEPType(RORG rorg, int func, int type, boolean supportsRefresh, boolean requestsResponse, String manufactorSuffix,
+            int manufId, Class<? extends EEP> eepClass, ThingTypeUID thingTypeUID, int command,
             Hashtable<String, Configuration> 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<? extends EEP> getEEPClass() {
@@ -602,6 +645,10 @@ public enum EEPType {
         return supportsRefresh;
     }
 
+    public boolean getRequstesResponse() {
+        return requestsResponse;
+    }
+
     public Map<String, EnOceanChannelDescription> GetSupportedChannels() {
         return Collections.unmodifiableMap(supportedChannels);
     }
@@ -614,7 +661,7 @@ public enum EEPType {
     }
 
     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));
     }
 
index ce19528af36f08a07aa0d09819ed37a83d055645..b69aa8d4d63864f1e699a251e3a7881cbf90abf1 100644 (file)
@@ -34,9 +34,6 @@ public class F6_01_01 extends _RPSMessage {
     @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 @@ public class F6_01_01 extends _RPSMessage {
     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;
+    }
 }
index 2af97fdc06f672efc8077266291399a777e2202b..8152c5e90a30ab10b72656f97b8c5571319de2b4 100644 (file)
@@ -55,9 +55,6 @@ public class F6_02_01 extends _RPSMessage {
     @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 @@ public class F6_02_01 extends _RPSMessage {
         // 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 @@ public class F6_02_01 extends _RPSMessage {
     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;
+    }
 }
index 29b643cb01a1a77fecf805a9da7061aa069a716c..76e169785171af219d194de8dd6b7d12601c8a8b 100644 (file)
@@ -52,9 +52,6 @@ public class F6_02_02 extends _RPSMessage {
     @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 @@ public class F6_02_02 extends _RPSMessage {
         // 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 @@ public class F6_02_02 extends _RPSMessage {
     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
+    }
 }
index 50855ab2e92ccd0c18ef3598993637ad44bd864a..4fafc5b68ac6e71d1d78ef3b2c064f0b9e17ba3f 100644 (file)
@@ -44,9 +44,6 @@ public class F6_05_02 extends _RPSMessage {
     @Override
     protected State convertToStateImpl(String channelId, String channelTypeId,
             Function<String, State> getCurrentStateFunc, Configuration config) {
-        if (!isValid()) {
-            return UnDefType.UNDEF;
-        }
 
         switch (channelId) {
             case CHANNEL_SMOKEDETECTION:
@@ -62,4 +59,10 @@ public class F6_05_02 extends _RPSMessage {
     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;
+    }
 }
index 43fc0f75344d4cfced19c5f7297260893839e172..498a807458fdb62dd71a36355f4df92b44e20bc7 100644 (file)
@@ -47,9 +47,6 @@ public class F6_10_00 extends _RPSMessage {
     @Override
     protected State convertToStateImpl(String channelId, String channelTypeId,
             Function<String, State> getCurrentStateFunc, Configuration config) {
-        if (!isValid()) {
-            return UnDefType.UNDEF;
-        }
 
         byte data = (byte) (bytes[0] & 0xF0);
 
@@ -82,4 +79,9 @@ public class F6_10_00 extends _RPSMessage {
     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);
+    }
 }
index 8a381ea9e1600e652a457edf3d96e6400ae06b2b..ee9cd485f9c9bdc76e301302bd958693e00782d2 100644 (file)
@@ -61,4 +61,10 @@ public class F6_10_00_EltakoFPE extends _RPSMessage {
         // 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;
+    }
 }
index 51ed9306212f08447f3827e11ec756a6a6f9d0c4..3773cafe49c8d15ed9b202a9d9ab3e2357e6efe6 100644 (file)
@@ -47,9 +47,6 @@ public class F6_10_01 extends _RPSMessage {
     @Override
     protected State convertToStateImpl(String channelId, String channelTypeId,
             Function<String, State> getCurrentStateFunc, Configuration config) {
-        if (!isValid()) {
-            return UnDefType.UNDEF;
-        }
 
         byte data = (byte) (bytes[0] & 0x0F);
 
@@ -82,4 +79,10 @@ public class F6_10_01 extends _RPSMessage {
     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);
+    }
 }
index 178ae890b86898c75bf5cab1b81f17cc2bacdf1c..d930e8206400bba0f2b8ed4464e66b899ac3e561 100644 (file)
@@ -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 @@ public class GenericEEP extends EEP {
 
     @Override
     public void addConfigPropertiesTo(DiscoveryResultBuilder discoveredThingResultBuilder) {
-        discoveredThingResultBuilder.withProperty(PARAMETER_EEPID, getEEPType().getId());
+        discoveredThingResultBuilder.withProperty(PARAMETER_SENDINGEEPID, getEEPType().getId())
+                .withProperty(PARAMETER_RECEIVINGEEPID, getEEPType().getId());
     }
 }
index 590769153791d619303f73610ff121a0db5fef4e..be2398ac0cf6cd71b9134c5e0b7a31d8ce54f3b3 100644 (file)
@@ -28,6 +28,7 @@ import org.openhab.binding.enocean.internal.eep.EEPFactory;
 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 class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
      * @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 @@ public class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
     }
 
     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 @@ public class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
         }
 
         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 @@ public class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
         }
     }
 
+    @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 class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
 
         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 class EnOceanBaseActuatorHandler extends EnOceanBaseSensorHandler {
 
     @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();
index d533c1e558c4abd491e9c3f2032349e58bbc33bb..b4fed2ccb4ad76504bdac77f3f21b1c7db2e8b28 100644 (file)
@@ -19,8 +19,11 @@ import java.util.Collections;
 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<RORG, EEPType> receivingEEPTypes = new Hashtable<>();
 
+    protected ScheduledFuture<?> responseFuture = null;
+
     public EnOceanBaseSensorHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) {
         super(thing, itemChannelLinkRegistry);
     }
@@ -104,7 +109,7 @@ public class EnOceanBaseSensorHandler extends EnOceanBaseThingHandler implements
     }
 
     @Override
-    public long getSenderIdToListenTo() {
+    public long getEnOceanIdToListenTo() {
         return Long.parseLong(config.enoceanId, 16);
     }
 
@@ -129,6 +134,10 @@ public class EnOceanBaseSensorHandler extends EnOceanBaseThingHandler implements
         };
     }
 
+    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 class EnOceanBaseSensorHandler extends EnOceanBaseThingHandler implements
                                 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);
+                }
+            }
         }
     }
 }
index 20ccfa80b50628b0c4653ca46fe43d769e6452f7..f561db01859cf651359dfc78ad4373a78d014974 100644 (file)
@@ -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 @@ public abstract class EnOceanBaseThingHandler extends ConfigStatusThingHandler {
                     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;
index c888f6a60be2d0ad2a7c93f4441b929ded7f64ce..e793366f266980e0b000fb35ba563dcda08bfc6e 100644 (file)
@@ -15,30 +15,35 @@ package org.openhab.binding.enocean.internal.handler;
 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<String> smackClients = Set.of();
+
     public EnOceanBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) {
         super(bridge);
         this.serialPortManager = serialPortManager;
@@ -157,13 +165,6 @@ public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
             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 @@ public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
             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 @@ public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
 
             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 class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
                                 }
                             }
                         });
+
+                if (c.getESPVersion() == ESPVersion.ESP3) {
+                    logger.debug("set postmaster mailboxes");
+                    transceiver.sendBasePacket(ESP3PacketFactory.SA_WR_POSTMASTER((byte) (c.enableSmack ? 20 : 0)),
+                            new ResponseListenerIgnoringTimeouts<BaseResponse>() {
+
+                                @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 class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
         Collection<ConfigStatusMessage> 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 class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
         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 class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
     }
 
     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 class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
     }
 
     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 class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
         }
     }
 
-    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<BaseResponse>() {
+
+                            @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<RDLearnedClientsResponse>() {
+
+                            @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 class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T
         transceiver = null;
         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, exception.getMessage());
     }
+
+    public boolean sendTeachOuts() {
+        return sendTeachOuts;
+    }
 }
index 412cd9ad8d8fec82e3213d77c49de342ab407ae0..08a1e499e82d4bc0f2e7f8d102a4a16337aa2138 100644 (file)
@@ -71,7 +71,7 @@ public class EnOceanClassicDeviceHandler extends EnOceanBaseActuatorHandler {
     }
 
     @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/BaseResponse.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/BaseResponse.java
deleted file mode 100644 (file)
index 63de3f5..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
- * 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 org.openhab.binding.enocean.internal.Helper;
-
-/**
- *
- * @author Daniel Weber - Initial contribution
- */
-public class BaseResponse extends Response {
-
-    public BaseResponse(Response response) {
-        super(response.getPayload().length + response.getOptionalPayload().length, 0,
-                Helper.concatAll(response.getPayload(), response.getOptionalPayload()));
-    }
-}
index 73551cdc83bbede9ad0134b676b65d72a8683c05..f410d6e1aba8aba75a8e348e5cba86c3a2470900 100644 (file)
 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 class ESP3PacketFactory {
         }
     }
 
+    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 class ESP3PacketFactory {
                 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 (file)
index 0000000..4cacceb
--- /dev/null
@@ -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/RDBaseIdResponse.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/RDBaseIdResponse.java
deleted file mode 100644 (file)
index e2220c0..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-/**
- * 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 org.openhab.binding.enocean.internal.Helper;
-
-/**
- *
- * @author Daniel Weber - Initial contribution
- */
-public class RDBaseIdResponse extends Response {
-
-    private byte[] baseId = null;
-    private int remainingWriteCycles = 0;
-
-    public RDBaseIdResponse(Response response) {
-        this(response.getPayload().length, response.getOptionalPayload().length,
-                Helper.concatAll(response.getPayload(), response.getOptionalPayload()));
-    }
-
-    RDBaseIdResponse(int dataLength, int optionalDataLength, byte[] payload) {
-        super(dataLength, optionalDataLength, payload);
-
-        if (this.data == null || this.data.length != 5 || this.optionalData == null || this.optionalData.length != 1) {
-            return;
-        }
-
-        baseId = getPayload(1, 4);
-        remainingWriteCycles = optionalData[0] & 0xFF;
-
-        _isValid = true;
-    }
-
-    public final byte[] getBaseId() {
-        return baseId;
-    }
-
-    public int getRemainingWriteCycles() {
-        return remainingWriteCycles;
-    }
-}
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/RDRepeaterResponse.java
deleted file mode 100644 (file)
index cecc623..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-/**
- * 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 static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*;
-
-import org.eclipse.jdt.annotation.NonNull;
-import org.openhab.core.library.types.StringType;
-
-/**
- *
- * @author Daniel Weber - Initial contribution
- */
-public class RDRepeaterResponse extends Response {
-
-    protected String repeaterLevel;
-
-    public RDRepeaterResponse(Response response) {
-        this(response.getPayload().length, 0, response.getPayload());
-    }
-
-    RDRepeaterResponse(int dataLength, int optionalDataLength, byte[] payload) {
-        super(dataLength, optionalDataLength, payload);
-
-        if (payload == null || payload.length < 3) {
-            return;
-        }
-
-        if (payload[1] == 0) {
-            repeaterLevel = REPEATERMODE_OFF;
-        } else if (payload[1] == 1 || payload[1] == 2) {
-            switch (payload[2]) {
-                case 1:
-                    repeaterLevel = REPEATERMODE_LEVEL_1;
-                    break;
-                case 2:
-                    repeaterLevel = REPEATERMODE_LEVEL_2;
-                    break;
-                case 0:
-                    repeaterLevel = REPEATERMODE_OFF;
-                    break;
-                default:
-                    return;
-            }
-
-            _isValid = true;
-        }
-    }
-
-    @NonNull
-    public StringType getRepeaterLevel() {
-        return StringType.valueOf(repeaterLevel);
-    }
-}
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/RDVersionResponse.java
deleted file mode 100644 (file)
index 1374c56..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-/**
- * 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.Arrays;
-
-import org.eclipse.jdt.annotation.NonNull;
-import org.openhab.core.util.HexUtils;
-
-/**
- *
- * @author Daniel Weber - Initial contribution
- */
-public class RDVersionResponse extends Response {
-
-    protected String appVersion = "";
-    protected String apiVersion = "";
-    protected String chipId = "";
-    protected String description = "";
-
-    public RDVersionResponse(Response response) {
-        this(response.getPayload().length, 0, response.getPayload());
-    }
-
-    RDVersionResponse(int dataLength, int optionalDataLength, byte[] payload) {
-        super(dataLength, optionalDataLength, payload);
-
-        if (payload.length < 33) {
-            return;
-        }
-
-        try {
-            appVersion = String.format("%d.%d.%d.%d", payload[1] & 0xff, payload[2] & 0xff, payload[3] & 0xff,
-                    payload[4] & 0xff);
-            apiVersion = String.format("%d.%d.%d.%d", payload[5] & 0xff, payload[6] & 0xff, payload[7] & 0xff,
-                    payload[8] & 0xff);
-
-            chipId = HexUtils.bytesToHex(Arrays.copyOfRange(payload, 9, 13));
-
-            StringBuffer sb = new StringBuffer();
-            for (int i = 17; i < payload.length; i++) {
-                sb.append((char) (payload[i] & 0xff));
-            }
-            description = sb.toString();
-            _isValid = true;
-
-        } catch (Exception e) {
-            responseType = ResponseType.RET_ERROR;
-        }
-    }
-
-    @NonNull
-    public String getAPPVersion() {
-        return appVersion;
-    }
-
-    @NonNull
-    public String getAPIVersion() {
-        return apiVersion;
-    }
-
-    @NonNull
-    public String getChipID() {
-        return chipId;
-    }
-
-    @NonNull
-    public String getDescription() {
-        return description;
-    }
-}
index 2c8a0699fe366a4a7b47dca423a0e838e2c3a02c..e7b8f2094d30a459cb70333e0dba4bc85825ed37 100644 (file)
@@ -57,7 +57,7 @@ public class Response extends BasePacket {
     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/Responses/BaseResponse.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/BaseResponse.java
new file mode 100644 (file)
index 0000000..56a4ccb
--- /dev/null
@@ -0,0 +1,28 @@
+/**
+ * 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.Helper;
+import org.openhab.binding.enocean.internal.messages.Response;
+
+/**
+ *
+ * @author Daniel Weber - Initial contribution
+ */
+public class BaseResponse extends Response {
+
+    public BaseResponse(Response response) {
+        super(response.getPayload().length + response.getOptionalPayload().length, 0,
+                Helper.concatAll(response.getPayload(), response.getOptionalPayload()));
+    }
+}
diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDBaseIdResponse.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDBaseIdResponse.java
new file mode 100644 (file)
index 0000000..b2a8a0d
--- /dev/null
@@ -0,0 +1,52 @@
+/**
+ * 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.Helper;
+import org.openhab.binding.enocean.internal.messages.Response;
+
+/**
+ *
+ * @author Daniel Weber - Initial contribution
+ */
+public class RDBaseIdResponse extends Response {
+
+    private byte[] baseId = null;
+    private int remainingWriteCycles = 0;
+
+    public RDBaseIdResponse(Response response) {
+        this(response.getPayload().length, response.getOptionalPayload().length,
+                Helper.concatAll(response.getPayload(), response.getOptionalPayload()));
+    }
+
+    RDBaseIdResponse(int dataLength, int optionalDataLength, byte[] payload) {
+        super(dataLength, optionalDataLength, payload);
+
+        if (this.data == null || this.data.length != 5 || this.optionalData == null || this.optionalData.length != 1) {
+            return;
+        }
+
+        baseId = getPayload(1, 4);
+        remainingWriteCycles = optionalData[0] & 0xFF;
+
+        _isValid = true;
+    }
+
+    public final byte[] getBaseId() {
+        return baseId;
+    }
+
+    public int getRemainingWriteCycles() {
+        return remainingWriteCycles;
+    }
+}
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 (file)
index 0000000..4819152
--- /dev/null
@@ -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/Responses/RDRepeaterResponse.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDRepeaterResponse.java
new file mode 100644 (file)
index 0000000..440068b
--- /dev/null
@@ -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 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;
+
+/**
+ *
+ * @author Daniel Weber - Initial contribution
+ */
+public class RDRepeaterResponse extends Response {
+
+    protected String repeaterLevel;
+
+    public RDRepeaterResponse(Response response) {
+        this(response.getPayload().length, 0, response.getPayload());
+    }
+
+    RDRepeaterResponse(int dataLength, int optionalDataLength, byte[] payload) {
+        super(dataLength, optionalDataLength, payload);
+
+        if (payload == null || payload.length < 3) {
+            return;
+        }
+
+        if (payload[1] == 0) {
+            repeaterLevel = REPEATERMODE_OFF;
+        } else if (payload[1] == 1 || payload[1] == 2) {
+            switch (payload[2]) {
+                case 1:
+                    repeaterLevel = REPEATERMODE_LEVEL_1;
+                    break;
+                case 2:
+                    repeaterLevel = REPEATERMODE_LEVEL_2;
+                    break;
+                case 0:
+                    repeaterLevel = REPEATERMODE_OFF;
+                    break;
+                default:
+                    return;
+            }
+
+            _isValid = true;
+        }
+    }
+
+    @NonNull
+    public StringType getRepeaterLevel() {
+        return StringType.valueOf(repeaterLevel);
+    }
+}
diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDVersionResponse.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDVersionResponse.java
new file mode 100644 (file)
index 0000000..daedc9a
--- /dev/null
@@ -0,0 +1,82 @@
+/**
+ * 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.eclipse.jdt.annotation.NonNull;
+import org.openhab.binding.enocean.internal.messages.Response;
+import org.openhab.core.util.HexUtils;
+
+/**
+ *
+ * @author Daniel Weber - Initial contribution
+ */
+public class RDVersionResponse extends Response {
+
+    protected String appVersion = "";
+    protected String apiVersion = "";
+    protected String chipId = "";
+    protected String description = "";
+
+    public RDVersionResponse(Response response) {
+        this(response.getPayload().length, 0, response.getPayload());
+    }
+
+    RDVersionResponse(int dataLength, int optionalDataLength, byte[] payload) {
+        super(dataLength, optionalDataLength, payload);
+
+        if (payload.length < 33) {
+            return;
+        }
+
+        try {
+            appVersion = String.format("%d.%d.%d.%d", payload[1] & 0xff, payload[2] & 0xff, payload[3] & 0xff,
+                    payload[4] & 0xff);
+            apiVersion = String.format("%d.%d.%d.%d", payload[5] & 0xff, payload[6] & 0xff, payload[7] & 0xff,
+                    payload[8] & 0xff);
+
+            chipId = HexUtils.bytesToHex(Arrays.copyOfRange(payload, 9, 13));
+
+            StringBuffer sb = new StringBuffer();
+            for (int i = 17; i < payload.length; i++) {
+                sb.append((char) (payload[i] & 0xff));
+            }
+            description = sb.toString();
+            _isValid = true;
+
+        } catch (Exception e) {
+            responseType = ResponseType.RET_ERROR;
+        }
+    }
+
+    @NonNull
+    public String getAPPVersion() {
+        return appVersion;
+    }
+
+    @NonNull
+    public String getAPIVersion() {
+        return apiVersion;
+    }
+
+    @NonNull
+    public String getChipID() {
+        return chipId;
+    }
+
+    @NonNull
+    public String getDescription() {
+        return description;
+    }
+}
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 (file)
index 0000000..a827922
--- /dev/null
@@ -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 (file)
index 0000000..e63087e
--- /dev/null
@@ -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;
+    }
+}
index db15a3fec0c5f02400954e8ff1898011ccd93d34..28e8b042096300008ecc0fe861cafe47040164ff 100644 (file)
@@ -18,8 +18,6 @@ import java.util.concurrent.ScheduledExecutorService;
 
 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 @@ public class EnOceanESP3Transceiver extends EnOceanTransceiver {
                                                     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;
index df729a1dbc0e2c3afcdfb2a646fad7aeaab22a00..55be4988b02598bd094cae4e0fe60e2493cae465 100644 (file)
@@ -27,9 +27,13 @@ import java.util.concurrent.TimeUnit;
 
 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 @@ public abstract class EnOceanTransceiver implements SerialPortEventListener {
     Request currentRequest = null;
 
     protected Map<Long, HashSet<PacketListener>> listeners;
-    protected PacketListener teachInListener;
+    protected HashSet<EventListener> eventListeners;
+    protected TeachInListener teachInListener;
 
     protected InputStream inputStream;
     protected OutputStream outputStream;
@@ -151,6 +156,7 @@ public abstract class EnOceanTransceiver implements SerialPortEventListener {
         requestQueue = new RequestQueue(scheduler);
 
         listeners = new HashMap<>();
+        eventListeners = new HashSet<>();
         teachInListener = null;
 
         this.errorListener = errorListener;
@@ -192,6 +198,7 @@ public abstract class EnOceanTransceiver implements SerialPortEventListener {
                 }
             });
         }
+        logger.info("EnOceanSerialTransceiver RX thread started");
     }
 
     public void ShutDown() {
@@ -266,36 +273,65 @@ public abstract class EnOceanTransceiver implements SerialPortEventListener {
         }
     }
 
-    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<PacketListener> 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<PacketListener> 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 abstract class EnOceanTransceiver implements SerialPortEventListener {
         }
     }
 
-    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 (file)
index 0000000..2a27bfb
--- /dev/null
@@ -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);
+}
index 04e55fdba857bb3584c63ce4068262e3450c084b..da161441aa87a5b921ab4eb0a7ae4f198e701d03 100644 (file)
@@ -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 (file)
index 0000000..06a26c4
--- /dev/null
@@ -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 {
+
+}
index 7115374ab58f83404a49ddd18bb55b8a1de94602..855048b00add7b0a8ea8336187f050dd1ab6f154 100644 (file)
@@ -18,7 +18,7 @@
                                <description>EnOceanId of device this thing belongs to</description>
                                <required>true</required>
                        </parameter>
-                       <parameter name="senderIdOffset" type="integer">
+                       <parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
                                <label>Sender Id</label>
                                <description>Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be
                                        determined by bridge</description>
index 6d24ab20841561db165a58d3f96215b5dc7aa4e6..93b135f8a779f4d6387f87c23998c92c73447571 100644 (file)
@@ -20,7 +20,7 @@
                </channels>
 
                <config-description>
-                       <parameter name="senderIdOffset" type="integer">
+                       <parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
                                <label>Sender Id</label>
                                <description>Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be
                                        determined by bridge</description>
index 2fa60566e40e8b0bc380cbcb727e52067020fc05..b42b6b76202a760a96894dc2c6b3019c57c46b00 100644 (file)
@@ -18,7 +18,7 @@
                                <description>EnOceanId of device this thing belongs to</description>
                                <required>true</required>
                        </parameter>
-                       <parameter name="senderIdOffset" type="integer">
+                       <parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
                                <label>Sender Id</label>
                                <description>Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be
                                        determined by bridge</description>
index cd58af2cbf2fa88bac8d9b41e0e94261c7171750..f7430123b677aa7ad3e2e6b5edb1cdd7830bcd8d 100644 (file)
@@ -18,7 +18,7 @@
                                <description>EnOceanId of device this thing belongs to</description>
                                <required>true</required>
                        </parameter>
-                       <parameter name="senderIdOffset" type="integer">
+                       <parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
                                <label>Sender Id</label>
                                <description>Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be
                                        determined by bridge</description>
index 0f095d5ed6e1807728973e07587bcfde6058a7ed..08e75f162e9f99b020f6291494160c089726d5ec 100644 (file)
@@ -18,7 +18,7 @@
                                <description>EnOceanId of device this thing belongs to</description>
                                <required>true</required>
                        </parameter>
-                       <parameter name="senderIdOffset" type="integer">
+                       <parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
                                <label>Sender Id</label>
                                <description>Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be
                                        determined by bridge</description>
index 486ceebf0e2e4c3713a560e68e850fec215ae03e..4a51f1614bf982534f5fa47541597dee61e5ff0d 100644 (file)
@@ -18,7 +18,7 @@
                                <description>EnOceanId of device this thing belongs to</description>
                                <required>true</required>
                        </parameter>
-                       <parameter name="senderIdOffset" type="integer">
+                       <parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
                                <label>Sender Id</label>
                                <description>Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be
                                        determined by bridge</description>
index 2be75d8e930338d0ac419b5bc90b2525c7cf1a0a..5f1d44fe7e84a6df012d54e4fb79bfe2bd3df97c 100644 (file)
@@ -18,7 +18,7 @@
                                <description>EnOceanId of device this thing belongs to</description>
                                <required>true</required>
                        </parameter>
-                       <parameter name="senderIdOffset" type="integer">
+                       <parameter name="senderIdOffset" type="integer" required="false" min="1" max="127">
                                <label>Sender Id</label>
                                <description>Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be
                                        determined by bridge</description>
index afa99f504c7f89ac2b09e67034bc966ec1569498..c712af8cd2d90019acf14cf331f7d5ff76ff6817 100644 (file)
                                <required>true</required>
                                <default>ESP3</default>
                        </parameter>
+                       <parameter name="enableSmack" type="boolean">
+                               <label>Handle SMACK Messages</label>
+                               <description>Declare Gateway as a SMACK Postmaster and handle SMACK messages</description>
+                               <default>true</default>
+                       </parameter>
                        <parameter name="rs485" type="boolean">
                                <advanced>true</advanced>
                                <label>Gateway Connected Directly to RS485 BUS</label>
                                <label>Use following BaseId when connected directly to RS485 BUS</label>
                                <default>00000000</default>
                        </parameter>
-                       <parameter name="nextSenderId" type="integer">
+                       <parameter name="sendTeachOuts" type="boolean">
+                               <advanced>true</advanced>
+                               <label>Send TeachOuts</label>
+                               <description>Should a learned in or teach out response been send on a repeated smack teach in request</description>
+                               <default>false</default>
+                       </parameter>
+                       <parameter name="nextSenderId" type="integer" min="1" max="127">
                                <advanced>true</advanced>
-                               <label>Next Device Id</label>
+                               <label>Next Sender Id Offset</label>
                                <description>Defines the next device Id, if empty, the next device id is automatically determined</description>
                        </parameter>
                </config-description>
index 9469d48b8c9af9207e3a122bd503ef3bd62b8c88..941eb0d76487a98fe0a4cc35da5abbb764a22e81 100644 (file)
                <event/>
        </channel-type>
 
-       <channel-type id="sendCommand">
-               <item-type>Switch</item-type>
-               <label>Send Command</label>
-               <description>You can send telegrams to the device by switching this channel on</description>
-               <category>thermostat</category>
-       </channel-type>
-
        <channel-type id="smokeDetection">
                <item-type>Switch</item-type>
                <label>Smoke Detected</label>