2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.enocean.internal.discovery;
15 import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*;
19 import org.eclipse.jdt.annotation.NonNull;
20 import org.openhab.binding.enocean.internal.eep.Base.UTEResponse;
21 import org.openhab.binding.enocean.internal.eep.Base._4BSMessage;
22 import org.openhab.binding.enocean.internal.eep.EEP;
23 import org.openhab.binding.enocean.internal.eep.EEPFactory;
24 import org.openhab.binding.enocean.internal.handler.EnOceanBridgeHandler;
25 import org.openhab.binding.enocean.internal.messages.BasePacket;
26 import org.openhab.binding.enocean.internal.messages.ERP1Message;
27 import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG;
28 import org.openhab.binding.enocean.internal.messages.EventMessage;
29 import org.openhab.binding.enocean.internal.messages.EventMessage.EventMessageType;
30 import org.openhab.binding.enocean.internal.messages.Responses.SMACKTeachInResponse;
31 import org.openhab.binding.enocean.internal.transceiver.TeachInListener;
32 import org.openhab.core.config.discovery.AbstractDiscoveryService;
33 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.thing.ThingManager;
36 import org.openhab.core.thing.ThingTypeUID;
37 import org.openhab.core.thing.ThingUID;
38 import org.openhab.core.util.HexUtils;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
43 * The {@link EnOceanDeviceDiscoveryService} is used to discover Enocean devices and to accept teach in requests.
45 * @author Daniel Weber - Initial contribution
47 public class EnOceanDeviceDiscoveryService extends AbstractDiscoveryService implements TeachInListener {
48 private final Logger logger = LoggerFactory.getLogger(EnOceanDeviceDiscoveryService.class);
50 private EnOceanBridgeHandler bridgeHandler;
51 private ThingManager thingManager;
53 public EnOceanDeviceDiscoveryService(EnOceanBridgeHandler bridgeHandler, ThingManager thingManager) {
54 super(null, 60, false);
55 this.bridgeHandler = bridgeHandler;
56 this.thingManager = thingManager;
60 * Called on component activation.
62 public void activate() {
67 public void deactivate() {
72 protected void startScan() {
73 if (bridgeHandler == null) {
77 logger.info("Starting EnOcean discovery and accepting teach in requests");
78 bridgeHandler.startDiscovery(this);
82 public synchronized void stopScan() {
83 if (bridgeHandler == null) {
87 logger.info("Stopping EnOcean discovery scan");
88 bridgeHandler.stopDiscovery();
93 public Set<@NonNull ThingTypeUID> getSupportedThingTypes() {
94 return SUPPORTED_DEVICE_THING_TYPES_UIDS;
98 public void packetReceived(BasePacket packet) {
99 ERP1Message msg = (ERP1Message) packet;
101 logger.info("EnOcean Package discovered, RORG {}, payload {}, additional {}", msg.getRORG().name(),
102 HexUtils.bytesToHex(msg.getPayload()), HexUtils.bytesToHex(msg.getOptionalPayload()));
104 EEP eep = EEPFactory.buildEEPFromTeachInERP1(msg);
106 logger.debug("Could not build EEP for received package");
110 String enoceanId = HexUtils.bytesToHex(eep.getSenderId());
113 .getThing().getThings().stream().filter(t -> t.getConfiguration().getProperties()
114 .getOrDefault(PARAMETER_ENOCEANID, EMPTYENOCEANID).toString().equals(enoceanId))
115 .findFirst().ifPresentOrElse(t -> {
116 // If repeated learn is not allowed => send teach out
117 // otherwise do nothing
118 if (bridgeHandler.sendTeachOuts()) {
119 sendTeachOutResponse(msg, enoceanId, t);
120 thingManager.setEnabled(t.getUID(), false);
123 Integer senderIdOffset = null;
124 boolean broadcastMessages = true;
126 // check for bidirectional communication => do not use broadcast in this case
127 if (msg.getRORG() == RORG.UTE && (msg.getPayload(1, 1)[0]
128 & UTEResponse.CommunicationType_MASK) == UTEResponse.CommunicationType_MASK) {
129 broadcastMessages = false;
132 if (msg.getRORG() == RORG.UTE && (msg.getPayload(1, 1)[0] & UTEResponse.ResponseNeeded_MASK) == 0) {
133 // if ute => send response if needed
134 logger.debug("Sending UTE response to {}", enoceanId);
135 senderIdOffset = sendTeachInResponse(msg, enoceanId);
136 if (senderIdOffset == null) {
139 } else if ((eep instanceof _4BSMessage) && ((_4BSMessage) eep).isTeachInVariation3Supported()) {
140 // if 4BS teach in variation 3 => send response
141 logger.debug("Sending 4BS teach in variation 3 response to {}", enoceanId);
142 senderIdOffset = sendTeachInResponse(msg, enoceanId);
143 if (senderIdOffset == null) {
148 createDiscoveryResult(eep, broadcastMessages, senderIdOffset);
153 public void eventReceived(EventMessage event) {
154 if (event.getEventMessageType() == EventMessageType.SA_CONFIRM_LEARN) {
155 EEP eep = EEPFactory.buildEEPFromTeachInSMACKEvent(event);
160 SMACKTeachInResponse response = EEPFactory.buildResponseFromSMACKTeachIn(event,
161 bridgeHandler.sendTeachOuts());
162 if (response != null) {
163 bridgeHandler.sendMessage(response, null);
165 if (response.isTeachIn()) {
166 // SenderIdOffset will be determined during Thing init
167 createDiscoveryResult(eep, false, -1);
168 } else if (response.isTeachOut()) {
169 // disable already teached in thing
170 bridgeHandler.getThing().getThings().stream()
171 .filter(t -> t.getConfiguration().getProperties()
172 .getOrDefault(PARAMETER_ENOCEANID, EMPTYENOCEANID).toString()
173 .equals(HexUtils.bytesToHex(eep.getSenderId())))
174 .findFirst().ifPresentOrElse(t -> {
175 thingManager.setEnabled(t.getUID(), false);
176 logger.info("Disable thing with id {}", t.getUID());
178 logger.info("Thing for EnOceanId {} already deleted",
179 HexUtils.bytesToHex(eep.getSenderId()));
186 private Integer sendTeachInResponse(ERP1Message msg, String enoceanId) {
188 Integer offset = bridgeHandler.getNextSenderId(enoceanId);
189 if (offset != null) {
190 byte[] newSenderId = bridgeHandler.getBaseId();
191 newSenderId[3] += offset;
194 EEP response = EEPFactory.buildResponseEEPFromTeachInERP1(msg, newSenderId, true);
195 if (response != null) {
196 bridgeHandler.sendMessage(response.getERP1Message(), null);
197 logger.debug("Teach in response for {} with new senderId {} (= offset {}) sent", enoceanId,
198 HexUtils.bytesToHex(newSenderId), offset);
200 logger.warn("Teach in response for enoceanId {} not supported!", enoceanId);
203 logger.warn("Could not get new SenderIdOffset");
208 private void sendTeachOutResponse(ERP1Message msg, String enoceanId, Thing thing) {
209 byte[] senderId = bridgeHandler.getBaseId();
210 senderId[3] += (byte) thing.getConfiguration().getProperties().getOrDefault(PARAMETER_SENDERIDOFFSET, 0);
213 EEP response = EEPFactory.buildResponseEEPFromTeachInERP1(msg, senderId, false);
214 if (response != null) {
215 bridgeHandler.sendMessage(response.getERP1Message(), null);
216 logger.debug("Teach out response for thing {} with EnOceanId {} sent", thing.getUID().getId(), enoceanId);
218 logger.warn("Teach out response for enoceanId {} not supported!", enoceanId);
222 protected void createDiscoveryResult(EEP eep, boolean broadcastMessages, Integer senderIdOffset) {
223 String enoceanId = HexUtils.bytesToHex(eep.getSenderId());
224 ThingTypeUID thingTypeUID = eep.getThingTypeUID();
225 ThingUID thingUID = new ThingUID(thingTypeUID, bridgeHandler.getThing().getUID(), enoceanId);
227 DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(thingUID)
228 .withRepresentationProperty(PARAMETER_ENOCEANID).withProperty(PARAMETER_ENOCEANID, enoceanId)
229 .withProperty(PARAMETER_BROADCASTMESSAGES, broadcastMessages)
230 .withBridge(bridgeHandler.getThing().getUID());
232 eep.addConfigPropertiesTo(discoveryResultBuilder);
234 if (senderIdOffset != null) {
235 // advance config with new device id
236 discoveryResultBuilder.withProperty(PARAMETER_SENDERIDOFFSET, senderIdOffset);
239 thingDiscovered(discoveryResultBuilder.build());
243 public long getEnOceanIdToListenTo() {
244 // we just want teach in msg, so return zero here