]> git.basschouten.com Git - openhab-addons.git/blob
6d0af65ece358565a4b02b5e80c861a9a014412b
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.enocean.internal.discovery;
14
15 import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*;
16
17 import java.util.Set;
18
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;
43
44 /**
45  * The {@link EnOceanDeviceDiscoveryService} is used to discover Enocean devices and to accept teach in requests.
46  *
47  * @author Daniel Weber - Initial contribution
48  */
49 @NonNullByDefault
50 public class EnOceanDeviceDiscoveryService extends AbstractDiscoveryService implements TeachInListener {
51     private final Logger logger = LoggerFactory.getLogger(EnOceanDeviceDiscoveryService.class);
52
53     private EnOceanBridgeHandler bridgeHandler;
54     private ThingManager thingManager;
55
56     public EnOceanDeviceDiscoveryService(EnOceanBridgeHandler bridgeHandler, ThingManager thingManager) {
57         super(null, 60, false);
58         this.bridgeHandler = bridgeHandler;
59         this.thingManager = thingManager;
60     }
61
62     /**
63      * Called on component activation.
64      */
65     public void activate() {
66         super.activate(null);
67     }
68
69     @Override
70     public void deactivate() {
71         super.deactivate();
72     }
73
74     @Override
75     protected void startScan() {
76         logger.info("Starting EnOcean discovery and accepting teach in requests");
77         bridgeHandler.startDiscovery(this);
78     }
79
80     @Override
81     public synchronized void stopScan() {
82         logger.info("Stopping EnOcean discovery scan");
83         bridgeHandler.stopDiscovery();
84         super.stopScan();
85     }
86
87     @Override
88     public Set<@NonNull ThingTypeUID> getSupportedThingTypes() {
89         return SUPPORTED_DEVICE_THING_TYPES_UIDS;
90     }
91
92     @Override
93     public void packetReceived(BasePacket packet) {
94         ERP1Message msg = (ERP1Message) packet;
95
96         logger.info("EnOcean Package discovered, RORG {}, payload {}, additional {}", msg.getRORG().name(),
97                 HexUtils.bytesToHex(msg.getPayload()), HexUtils.bytesToHex(msg.getOptionalPayload()));
98
99         EEP eep = EEPFactory.buildEEPFromTeachInERP1(msg);
100         if (eep == null) {
101             logger.debug("Could not build EEP for received package");
102             return;
103         }
104
105         String enoceanId = HexUtils.bytesToHex(eep.getSenderId());
106
107         bridgeHandler
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);
116                     }
117                 }, () -> {
118                     Integer senderIdOffset = null;
119                     boolean broadcastMessages = true;
120
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;
125                     }
126
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) {
133                             return;
134                         }
135                     } else if ((eep instanceof _4BSMessage) && ((_4BSMessage) eep).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) {
140                             return;
141                         }
142                     }
143
144                     createDiscoveryResult(eep, broadcastMessages, senderIdOffset);
145                 });
146     }
147
148     @Override
149     public void eventReceived(EventMessage event) {
150         if (event.getEventMessageType() == EventMessageType.SA_CONFIRM_LEARN) {
151             EEP eep = EEPFactory.buildEEPFromTeachInSMACKEvent(event);
152             if (eep == null) {
153                 return;
154             }
155
156             SMACKTeachInResponse response = EEPFactory.buildResponseFromSMACKTeachIn(event,
157                     bridgeHandler.sendTeachOuts());
158             if (response != null) {
159                 bridgeHandler.sendMessage(response, null);
160
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());
173                             }, () -> {
174                                 logger.info("Thing for EnOceanId {} already deleted",
175                                         HexUtils.bytesToHex(eep.getSenderId()));
176                             });
177                 }
178             }
179         }
180     }
181
182     private @Nullable Integer sendTeachInResponse(ERP1Message msg, String enoceanId) {
183         // get new sender Id
184         Integer offset = bridgeHandler.getNextSenderId(enoceanId);
185         if (offset != null) {
186             byte[] newSenderId = bridgeHandler.getBaseId();
187             newSenderId[3] += offset;
188
189             // send response
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);
197                 }
198             } else {
199                 logger.warn("Teach in response for enoceanId {} not supported!", enoceanId);
200             }
201         } else {
202             logger.warn("Could not get new SenderIdOffset");
203         }
204         return offset;
205     }
206
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);
210
211         // send response
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(),
218                         enoceanId);
219             }
220         } else {
221             logger.warn("Teach out response for enoceanId {} not supported!", enoceanId);
222         }
223     }
224
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);
230             return;
231         }
232         ThingUID thingUID = new ThingUID(thingTypeUID, bridgeHandler.getThing().getUID(), enoceanId);
233
234         DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(thingUID)
235                 .withRepresentationProperty(PARAMETER_ENOCEANID).withProperty(PARAMETER_ENOCEANID, enoceanId)
236                 .withProperty(PARAMETER_BROADCASTMESSAGES, broadcastMessages)
237                 .withBridge(bridgeHandler.getThing().getUID());
238
239         eep.addConfigPropertiesTo(discoveryResultBuilder);
240
241         if (senderIdOffset != null) {
242             // advance config with new device id
243             discoveryResultBuilder.withProperty(PARAMETER_SENDERIDOFFSET, senderIdOffset);
244         }
245
246         thingDiscovered(discoveryResultBuilder.build());
247     }
248
249     @Override
250     public long getEnOceanIdToListenTo() {
251         // we just want teach in msg, so return zero here
252         return 0;
253     }
254 }