]> git.basschouten.com Git - openhab-addons.git/blob
52546501aca8a64de61ee6f62ca94361c58fe087
[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.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;
41
42 /**
43  * The {@link EnOceanDeviceDiscoveryService} is used to discover Enocean devices and to accept teach in requests.
44  *
45  * @author Daniel Weber - Initial contribution
46  */
47 public class EnOceanDeviceDiscoveryService extends AbstractDiscoveryService implements TeachInListener {
48     private final Logger logger = LoggerFactory.getLogger(EnOceanDeviceDiscoveryService.class);
49
50     private EnOceanBridgeHandler bridgeHandler;
51     private ThingManager thingManager;
52
53     public EnOceanDeviceDiscoveryService(EnOceanBridgeHandler bridgeHandler, ThingManager thingManager) {
54         super(null, 60, false);
55         this.bridgeHandler = bridgeHandler;
56         this.thingManager = thingManager;
57     }
58
59     /**
60      * Called on component activation.
61      */
62     public void activate() {
63         super.activate(null);
64     }
65
66     @Override
67     public void deactivate() {
68         super.deactivate();
69     }
70
71     @Override
72     protected void startScan() {
73         if (bridgeHandler == null) {
74             return;
75         }
76
77         logger.info("Starting EnOcean discovery and accepting teach in requests");
78         bridgeHandler.startDiscovery(this);
79     }
80
81     @Override
82     public synchronized void stopScan() {
83         if (bridgeHandler == null) {
84             return;
85         }
86
87         logger.info("Stopping EnOcean discovery scan");
88         bridgeHandler.stopDiscovery();
89         super.stopScan();
90     }
91
92     @Override
93     public Set<@NonNull ThingTypeUID> getSupportedThingTypes() {
94         return SUPPORTED_DEVICE_THING_TYPES_UIDS;
95     }
96
97     @Override
98     public void packetReceived(BasePacket packet) {
99         ERP1Message msg = (ERP1Message) packet;
100
101         logger.info("EnOcean Package discovered, RORG {}, payload {}, additional {}", msg.getRORG().name(),
102                 HexUtils.bytesToHex(msg.getPayload()), HexUtils.bytesToHex(msg.getOptionalPayload()));
103
104         EEP eep = EEPFactory.buildEEPFromTeachInERP1(msg);
105         if (eep == null) {
106             logger.debug("Could not build EEP for received package");
107             return;
108         }
109
110         String enoceanId = HexUtils.bytesToHex(eep.getSenderId());
111
112         bridgeHandler
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);
121                     }
122                 }, () -> {
123                     Integer senderIdOffset = null;
124                     boolean broadcastMessages = true;
125
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;
130                     }
131
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) {
137                             return;
138                         }
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) {
144                             return;
145                         }
146                     }
147
148                     createDiscoveryResult(eep, broadcastMessages, senderIdOffset);
149                 });
150     }
151
152     @Override
153     public void eventReceived(EventMessage event) {
154         if (event.getEventMessageType() == EventMessageType.SA_CONFIRM_LEARN) {
155             EEP eep = EEPFactory.buildEEPFromTeachInSMACKEvent(event);
156             if (eep == null) {
157                 return;
158             }
159
160             SMACKTeachInResponse response = EEPFactory.buildResponseFromSMACKTeachIn(event,
161                     bridgeHandler.sendTeachOuts());
162             if (response != null) {
163                 bridgeHandler.sendMessage(response, null);
164
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());
177                             }, () -> {
178                                 logger.info("Thing for EnOceanId {} already deleted",
179                                         HexUtils.bytesToHex(eep.getSenderId()));
180                             });
181                 }
182             }
183         }
184     }
185
186     private Integer sendTeachInResponse(ERP1Message msg, String enoceanId) {
187         // get new sender Id
188         Integer offset = bridgeHandler.getNextSenderId(enoceanId);
189         if (offset != null) {
190             byte[] newSenderId = bridgeHandler.getBaseId();
191             newSenderId[3] += offset;
192
193             // send response
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);
199             } else {
200                 logger.warn("Teach in response for enoceanId {} not supported!", enoceanId);
201             }
202         } else {
203             logger.warn("Could not get new SenderIdOffset");
204         }
205         return offset;
206     }
207
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);
211
212         // send response
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);
217         } else {
218             logger.warn("Teach out response for enoceanId {} not supported!", enoceanId);
219         }
220     }
221
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);
226
227         DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(thingUID)
228                 .withRepresentationProperty(PARAMETER_ENOCEANID).withProperty(PARAMETER_ENOCEANID, enoceanId)
229                 .withProperty(PARAMETER_BROADCASTMESSAGES, broadcastMessages)
230                 .withBridge(bridgeHandler.getThing().getUID());
231
232         eep.addConfigPropertiesTo(discoveryResultBuilder);
233
234         if (senderIdOffset != null) {
235             // advance config with new device id
236             discoveryResultBuilder.withProperty(PARAMETER_SENDERIDOFFSET, senderIdOffset);
237         }
238
239         thingDiscovered(discoveryResultBuilder.build());
240     }
241
242     @Override
243     public long getEnOceanIdToListenTo() {
244         // we just want teach in msg, so return zero here
245         return 0;
246     }
247 }