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.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.enocean.internal.eep.Base.UTEResponse;
23 import org.openhab.binding.enocean.internal.eep.Base._4BSMessage;
24 import org.openhab.binding.enocean.internal.eep.EEP;
25 import org.openhab.binding.enocean.internal.eep.EEPFactory;
26 import org.openhab.binding.enocean.internal.handler.EnOceanBridgeHandler;
27 import org.openhab.binding.enocean.internal.messages.BasePacket;
28 import org.openhab.binding.enocean.internal.messages.ERP1Message;
29 import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG;
30 import org.openhab.binding.enocean.internal.messages.EventMessage;
31 import org.openhab.binding.enocean.internal.messages.EventMessage.EventMessageType;
32 import org.openhab.binding.enocean.internal.messages.responses.SMACKTeachInResponse;
33 import org.openhab.binding.enocean.internal.transceiver.TeachInListener;
34 import org.openhab.core.config.discovery.AbstractDiscoveryService;
35 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
36 import org.openhab.core.thing.Thing;
37 import org.openhab.core.thing.ThingManager;
38 import org.openhab.core.thing.ThingTypeUID;
39 import org.openhab.core.thing.ThingUID;
40 import org.openhab.core.util.HexUtils;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
45 * The {@link EnOceanDeviceDiscoveryService} is used to discover Enocean devices and to accept teach in requests.
47 * @author Daniel Weber - Initial contribution
50 public class EnOceanDeviceDiscoveryService extends AbstractDiscoveryService implements TeachInListener {
51 private final Logger logger = LoggerFactory.getLogger(EnOceanDeviceDiscoveryService.class);
53 private EnOceanBridgeHandler bridgeHandler;
54 private ThingManager thingManager;
56 public EnOceanDeviceDiscoveryService(EnOceanBridgeHandler bridgeHandler, ThingManager thingManager) {
57 super(null, 60, false);
58 this.bridgeHandler = bridgeHandler;
59 this.thingManager = thingManager;
63 * Called on component activation.
65 public void activate() {
70 public void deactivate() {
75 protected void startScan() {
76 logger.info("Starting EnOcean discovery and accepting teach in requests");
77 bridgeHandler.startDiscovery(this);
81 public synchronized void stopScan() {
82 logger.info("Stopping EnOcean discovery scan");
83 bridgeHandler.stopDiscovery();
88 public Set<@NonNull ThingTypeUID> getSupportedThingTypes() {
89 return SUPPORTED_DEVICE_THING_TYPES_UIDS;
93 public void packetReceived(BasePacket packet) {
94 ERP1Message msg = (ERP1Message) packet;
96 logger.info("EnOcean Package discovered, RORG {}, payload {}, additional {}", msg.getRORG().name(),
97 HexUtils.bytesToHex(msg.getPayload()), HexUtils.bytesToHex(msg.getOptionalPayload()));
99 EEP eep = EEPFactory.buildEEPFromTeachInERP1(msg);
101 logger.debug("Could not build EEP for received package");
105 String enoceanId = HexUtils.bytesToHex(eep.getSenderId());
108 .getThing().getThings().stream().filter(t -> t.getConfiguration().getProperties()
109 .getOrDefault(PARAMETER_ENOCEANID, EMPTYENOCEANID).toString().equals(enoceanId))
110 .findFirst().ifPresentOrElse(t -> {
111 // If repeated learn is not allowed => send teach out
112 // otherwise do nothing
113 if (bridgeHandler.sendTeachOuts()) {
114 sendTeachOutResponse(msg, enoceanId, t);
115 thingManager.setEnabled(t.getUID(), false);
118 Integer senderIdOffset = null;
119 boolean broadcastMessages = true;
121 // check for bidirectional communication => do not use broadcast in this case
122 if (msg.getRORG() == RORG.UTE && (msg.getPayload(1, 1)[0]
123 & UTEResponse.COMMUNICATION_TYPE_MASK) == UTEResponse.COMMUNICATION_TYPE_MASK) {
124 broadcastMessages = false;
127 if (msg.getRORG() == RORG.UTE
128 && (msg.getPayload(1, 1)[0] & UTEResponse.RESPONSE_NEEDED_MASK) == 0) {
129 // if ute => send response if needed
130 logger.debug("Sending UTE response to {}", enoceanId);
131 senderIdOffset = sendTeachInResponse(msg, enoceanId);
132 if (senderIdOffset == null) {
135 } else if (eep instanceof _4BSMessage message && message.isTeachInVariation3Supported()) {
136 // if 4BS teach in variation 3 => send response
137 logger.debug("Sending 4BS teach in variation 3 response to {}", enoceanId);
138 senderIdOffset = sendTeachInResponse(msg, enoceanId);
139 if (senderIdOffset == null) {
144 createDiscoveryResult(eep, broadcastMessages, senderIdOffset);
149 public void eventReceived(EventMessage event) {
150 if (event.getEventMessageType() == EventMessageType.SA_CONFIRM_LEARN) {
151 EEP eep = EEPFactory.buildEEPFromTeachInSMACKEvent(event);
156 SMACKTeachInResponse response = EEPFactory.buildResponseFromSMACKTeachIn(event,
157 bridgeHandler.sendTeachOuts());
158 if (response != null) {
159 bridgeHandler.sendMessage(response, null);
161 if (response.isTeachIn()) {
162 // SenderIdOffset will be determined during Thing init
163 createDiscoveryResult(eep, false, -1);
164 } else if (response.isTeachOut()) {
165 // disable already teached in thing
166 bridgeHandler.getThing().getThings().stream()
167 .filter(t -> t.getConfiguration().getProperties()
168 .getOrDefault(PARAMETER_ENOCEANID, EMPTYENOCEANID).toString()
169 .equals(HexUtils.bytesToHex(eep.getSenderId())))
170 .findFirst().ifPresentOrElse(t -> {
171 thingManager.setEnabled(t.getUID(), false);
172 logger.info("Disable thing with id {}", t.getUID());
174 logger.info("Thing for EnOceanId {} already deleted",
175 HexUtils.bytesToHex(eep.getSenderId()));
182 private @Nullable Integer sendTeachInResponse(ERP1Message msg, String enoceanId) {
184 Integer offset = bridgeHandler.getNextSenderId(enoceanId);
185 if (offset != null) {
186 byte[] newSenderId = bridgeHandler.getBaseId();
187 newSenderId[3] += offset;
190 EEP response = EEPFactory.buildResponseEEPFromTeachInERP1(msg, newSenderId, true);
191 if (response != null) {
192 BasePacket bPacket = response.getERP1Message();
193 if (bPacket != null) {
194 bridgeHandler.sendMessage(bPacket, null);
195 logger.debug("Teach in response for {} with new senderId {} (= offset {}) sent", enoceanId,
196 HexUtils.bytesToHex(newSenderId), offset);
199 logger.warn("Teach in response for enoceanId {} not supported!", enoceanId);
202 logger.warn("Could not get new SenderIdOffset");
207 private void sendTeachOutResponse(ERP1Message msg, String enoceanId, Thing thing) {
208 byte[] senderId = bridgeHandler.getBaseId();
209 senderId[3] += (byte) thing.getConfiguration().getProperties().getOrDefault(PARAMETER_SENDERIDOFFSET, 0);
212 EEP response = EEPFactory.buildResponseEEPFromTeachInERP1(msg, senderId, false);
213 if (response != null) {
214 ERP1Message message = response.getERP1Message();
215 if (message != null) {
216 bridgeHandler.sendMessage(message, null);
217 logger.debug("Teach out response for thing {} with EnOceanId {} sent", thing.getUID().getId(),
221 logger.warn("Teach out response for enoceanId {} not supported!", enoceanId);
225 protected void createDiscoveryResult(EEP eep, boolean broadcastMessages, @Nullable Integer senderIdOffset) {
226 String enoceanId = HexUtils.bytesToHex(eep.getSenderId());
227 ThingTypeUID thingTypeUID = eep.getThingTypeUID();
228 if (thingTypeUID == null) {
229 logger.debug("Discovery failed, could not get ThingTypeUID for EnOceanId {}", enoceanId);
232 ThingUID thingUID = new ThingUID(thingTypeUID, bridgeHandler.getThing().getUID(), enoceanId);
234 DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(thingUID)
235 .withRepresentationProperty(PARAMETER_ENOCEANID).withProperty(PARAMETER_ENOCEANID, enoceanId)
236 .withProperty(PARAMETER_BROADCASTMESSAGES, broadcastMessages)
237 .withBridge(bridgeHandler.getThing().getUID());
239 eep.addConfigPropertiesTo(discoveryResultBuilder);
241 if (senderIdOffset != null) {
242 // advance config with new device id
243 discoveryResultBuilder.withProperty(PARAMETER_SENDERIDOFFSET, senderIdOffset);
246 thingDiscovered(discoveryResultBuilder.build());
250 public long getEnOceanIdToListenTo() {
251 // we just want teach in msg, so return zero here