]> git.basschouten.com Git - openhab-addons.git/blob
b526f6d0423e78075a563a7b4acf4baf7ec611e5
[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.echonetlite.internal;
14
15 import static java.util.Objects.requireNonNull;
16
17 import java.io.IOException;
18 import java.net.InetSocketAddress;
19 import java.net.SocketAddress;
20 import java.nio.ByteBuffer;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.Map;
25 import java.util.concurrent.ArrayBlockingQueue;
26 import java.util.concurrent.TimeUnit;
27
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.openhab.core.thing.Bridge;
31 import org.openhab.core.thing.ChannelUID;
32 import org.openhab.core.thing.ThingStatus;
33 import org.openhab.core.thing.ThingStatusDetail;
34 import org.openhab.core.thing.binding.BaseBridgeHandler;
35 import org.openhab.core.thing.binding.ThingHandlerService;
36 import org.openhab.core.types.Command;
37 import org.openhab.core.types.State;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 /**
42  * Bridge handler for echonet lite devices. By default, all messages (inbound and outbound) happen on port 3610, so
43  * we can only have a single listener for echonet lite messages. Hence, using a bridge model to handle communications
44  * and discovery.
45  *
46  * @author Michael Barker - Initial contribution
47  */
48 @NonNullByDefault
49 public class EchonetLiteBridgeHandler extends BaseBridgeHandler {
50
51     private final Logger logger = LoggerFactory.getLogger(EchonetLiteBridgeHandler.class);
52     private final ArrayBlockingQueue<Message> requests = new ArrayBlockingQueue<>(1024);
53     private final Map<InstanceKey, EchonetObject> devicesByKey = new HashMap<>();
54     private final EchonetMessageBuilder messageBuilder = new EchonetMessageBuilder();
55     private final Thread networkingThread = new Thread(this::poll);
56     private final EchonetMessage echonetMessage = new EchonetMessage();
57     private final MonotonicClock clock = new MonotonicClock();
58
59     @Nullable
60     private EchonetChannel echonetChannel;
61
62     @Nullable
63     private InstanceKey managementControllerKey;
64
65     @Nullable
66     private InstanceKey discoveryKey;
67
68     public EchonetLiteBridgeHandler(Bridge bridge) {
69         super(bridge);
70     }
71
72     private void start(final InstanceKey managementControllerKey, InstanceKey discoveryKey) throws IOException {
73         this.managementControllerKey = managementControllerKey;
74         this.discoveryKey = discoveryKey;
75
76         logger.debug("Binding echonet channel");
77         echonetChannel = new EchonetChannel(discoveryKey.address);
78         logger.debug("Starting networking thread");
79
80         networkingThread.setName("OH-binding-" + EchonetLiteBindingConstants.BINDING_ID);
81         networkingThread.setDaemon(true);
82         networkingThread.start();
83     }
84
85     public void newDevice(InstanceKey instanceKey, long pollIntervalMs, long retryTimeoutMs,
86             final EchonetDeviceListener echonetDeviceListener) {
87         requests.add(new NewDeviceMessage(instanceKey, pollIntervalMs, retryTimeoutMs, echonetDeviceListener));
88     }
89
90     private void newDeviceInternal(final NewDeviceMessage message) {
91         final EchonetObject echonetObject = devicesByKey.get(message.instanceKey);
92         if (null != echonetObject) {
93             if (echonetObject instanceof EchonetDevice) {
94                 logger.debug("Update item: {} already discovered", message.instanceKey);
95                 EchonetDevice device = (EchonetDevice) echonetObject;
96                 device.setTimeouts(message.pollIntervalMs, message.retryTimeoutMs);
97                 device.setListener(message.echonetDeviceListener);
98             } else {
99                 logger.debug("Item: {} already discovered, but was not a device", message.instanceKey);
100             }
101         } else {
102             logger.debug("New Device: {}", message.instanceKey);
103             final EchonetDevice device = new EchonetDevice(message.instanceKey, message.echonetDeviceListener);
104             device.setTimeouts(message.pollIntervalMs, message.retryTimeoutMs);
105             devicesByKey.put(message.instanceKey, device);
106         }
107     }
108
109     public void refreshDevice(final InstanceKey instanceKey, final String channelId) {
110         requests.add(new RefreshMessage(instanceKey, channelId));
111     }
112
113     private void refreshDeviceInternal(final RefreshMessage refreshMessage) {
114         final EchonetObject item = devicesByKey.get(refreshMessage.instanceKey);
115         if (null != item) {
116             item.refresh(refreshMessage.channelId);
117         }
118     }
119
120     public void removeDevice(final InstanceKey instanceKey) {
121         requests.add(new RemoveDevice(instanceKey));
122     }
123
124     private void removeDeviceInternal(final RemoveDevice removeDevice) {
125         final EchonetObject remove = devicesByKey.remove(removeDevice.instanceKey);
126
127         logger.debug("Removing device: {}, {}", removeDevice.instanceKey, remove);
128         if (null != remove) {
129             remove.removed();
130         }
131     }
132
133     public void updateDevice(final InstanceKey instanceKey, final String id, final State command) {
134         requests.add(new UpdateDevice(instanceKey, id, command));
135     }
136
137     public void updateDeviceInternal(UpdateDevice updateDevice) {
138         final EchonetObject echonetObject = devicesByKey.get(updateDevice.instanceKey);
139
140         if (null == echonetObject) {
141             logger.warn("Device not found for update: {}", updateDevice);
142             return;
143         }
144
145         echonetObject.update(updateDevice.channelId, updateDevice.state);
146     }
147
148     public void startDiscovery(EchonetDiscoveryListener echonetDiscoveryListener) {
149         requests.offer(new StartDiscoveryMessage(echonetDiscoveryListener, requireNonNull(discoveryKey)));
150     }
151
152     public void startDiscoveryInternal(StartDiscoveryMessage startDiscovery) {
153         devicesByKey.put(startDiscovery.instanceKey, new EchonetProfileNode(startDiscovery.instanceKey,
154                 this::onDiscoveredInstanceKey, startDiscovery.echonetDiscoveryListener));
155     }
156
157     public void stopDiscovery() {
158         requests.offer(new StopDiscoveryMessage(requireNonNull(discoveryKey)));
159     }
160
161     private void stopDiscoveryInternal(StopDiscoveryMessage stopDiscovery) {
162         devicesByKey.remove(stopDiscovery.instanceKey);
163     }
164
165     private void onDiscoveredInstanceKey(EchonetDevice device) {
166         if (null == devicesByKey.putIfAbsent(device.instanceKey(), device)) {
167             logger.debug("New device discovered: {}", device.instanceKey);
168         }
169     }
170
171     private void pollDevices(long nowMs, EchonetChannel echonetChannel) {
172         for (EchonetObject echonetObject : devicesByKey.values()) {
173             if (echonetObject.buildUpdateMessage(messageBuilder, echonetChannel::nextTid, nowMs,
174                     requireNonNull(managementControllerKey))) {
175                 try {
176                     echonetChannel.sendMessage(messageBuilder);
177                 } catch (IOException e) {
178                     logger.warn("Failed to send echonet message", e);
179                 }
180             }
181
182             echonetObject.refreshAll(nowMs);
183
184             if (echonetObject.buildPollMessage(messageBuilder, echonetChannel::nextTid, nowMs,
185                     requireNonNull(managementControllerKey))) {
186                 try {
187                     echonetChannel.sendMessage(messageBuilder);
188                 } catch (IOException e) {
189                     logger.warn("Failed to send echonet message", e);
190                 }
191             } else {
192                 echonetObject.checkTimeouts();
193             }
194         }
195     }
196
197     private void pollRequests() {
198         Message message;
199         while (null != (message = requestsPoll())) {
200             logger.debug("Received request: {}", message);
201             if (message instanceof NewDeviceMessage) {
202                 newDeviceInternal((NewDeviceMessage) message);
203             } else if (message instanceof RefreshMessage) {
204                 refreshDeviceInternal((RefreshMessage) message);
205             } else if (message instanceof RemoveDevice) {
206                 removeDeviceInternal((RemoveDevice) message);
207             } else if (message instanceof UpdateDevice) {
208                 updateDeviceInternal((UpdateDevice) message);
209             } else if (message instanceof StartDiscoveryMessage) {
210                 startDiscoveryInternal((StartDiscoveryMessage) message);
211             } else if (message instanceof StopDiscoveryMessage) {
212                 stopDiscoveryInternal((StopDiscoveryMessage) message);
213             }
214         }
215     }
216
217     private @Nullable Message requestsPoll() {
218         return requests.poll();
219     }
220
221     private void pollNetwork(EchonetChannel echonetChannel) {
222         try {
223             echonetChannel.pollMessages(echonetMessage, this::onMessage,
224                     EchonetLiteBindingConstants.NETWORK_WAIT_TIMEOUT);
225         } catch (IOException e) {
226             logger.warn("Failed to poll for messages", e);
227         }
228     }
229
230     private void onMessage(final EchonetMessage echonetMessage, final SocketAddress sourceAddress) {
231         final EchonetClass echonetClass = echonetMessage.sourceClass();
232         if (null == echonetClass) {
233             logger.warn("Unable to find echonetClass for message: {}, from: {}", echonetMessage.toDebug(),
234                     sourceAddress);
235             return;
236         }
237
238         final InstanceKey instanceKey = new InstanceKey((InetSocketAddress) sourceAddress, echonetClass,
239                 echonetMessage.instance());
240         final Esv esv = echonetMessage.esv();
241
242         EchonetObject echonetObject = devicesByKey.get(instanceKey);
243         if (null == echonetObject) {
244             echonetObject = devicesByKey.get(discoveryKey);
245         }
246
247         logger.debug("Message {} for: {}", esv, echonetObject);
248         if (null != echonetObject) {
249             echonetObject.applyHeader(esv, echonetMessage.tid(), clock.timeMs());
250             while (echonetMessage.moveNext()) {
251                 final int epc = echonetMessage.currentEpc();
252                 final int pdc = echonetMessage.currentPdc();
253                 ByteBuffer edt = echonetMessage.currentEdt();
254                 echonetObject.applyProperty(instanceKey, esv, epc, pdc, edt);
255             }
256         }
257     }
258
259     private void poll() {
260         try {
261             doPoll();
262             updateStatus(ThingStatus.ONLINE);
263
264             while (!Thread.currentThread().isInterrupted()) {
265                 doPoll();
266             }
267         } catch (Exception e) {
268             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
269         }
270     }
271
272     private void doPoll() {
273         final long nowMs = clock.timeMs();
274         pollRequests();
275         pollDevices(nowMs, requireNonNull(echonetChannel));
276         pollNetwork(requireNonNull(echonetChannel));
277     }
278
279     @Override
280     public void initialize() {
281         final EchonetBridgeConfig bridgeConfig = getConfigAs(EchonetBridgeConfig.class);
282
283         final InstanceKey managementControllerKey = new InstanceKey(new InetSocketAddress(bridgeConfig.port),
284                 EchonetClass.MANAGEMENT_CONTROLLER, (byte) 0x01);
285         final InstanceKey discoveryKey = new InstanceKey(
286                 new InetSocketAddress(requireNonNull(bridgeConfig.multicastAddress), bridgeConfig.port),
287                 EchonetClass.NODE_PROFILE, (byte) 0x01);
288
289         updateStatus(ThingStatus.UNKNOWN);
290
291         try {
292             start(managementControllerKey, discoveryKey);
293         } catch (IOException e) {
294             throw new IllegalStateException("Unable to start networking thread", e);
295         }
296     }
297
298     @Override
299     public void dispose() {
300         if (networkingThread.isAlive()) {
301             networkingThread.interrupt();
302             try {
303                 networkingThread.join(TimeUnit.SECONDS.toMillis(5));
304             } catch (InterruptedException e) {
305                 logger.debug("Interrupted while closing", e);
306             }
307         }
308
309         @Nullable
310         final EchonetChannel echonetChannel = this.echonetChannel;
311         if (null != echonetChannel) {
312             echonetChannel.close();
313         }
314     }
315
316     @Override
317     public void handleCommand(ChannelUID channelUID, Command command) {
318     }
319
320     @Override
321     public Collection<Class<? extends ThingHandlerService>> getServices() {
322         return Collections.singletonList(EchonetDiscoveryService.class);
323     }
324
325     private abstract static class Message {
326         final InstanceKey instanceKey;
327
328         public Message(InstanceKey instanceKey) {
329             this.instanceKey = instanceKey;
330         }
331     }
332
333     private static final class NewDeviceMessage extends Message {
334         final long pollIntervalMs;
335         final long retryTimeoutMs;
336         final EchonetDeviceListener echonetDeviceListener;
337
338         public NewDeviceMessage(final InstanceKey instanceKey, long pollIntervalMs, long retryTimeoutMs,
339                 final EchonetDeviceListener echonetDeviceListener) {
340             super(instanceKey);
341             this.pollIntervalMs = pollIntervalMs;
342             this.retryTimeoutMs = retryTimeoutMs;
343             this.echonetDeviceListener = echonetDeviceListener;
344         }
345
346         @Override
347         public String toString() {
348             return "NewDeviceMessage{" + "instanceKey=" + instanceKey + ", pollIntervalMs=" + pollIntervalMs
349                     + ", retryTimeoutMs=" + retryTimeoutMs + "} " + super.toString();
350         }
351     }
352
353     private static class RefreshMessage extends Message {
354         private final String channelId;
355
356         public RefreshMessage(InstanceKey instanceKey, String channelId) {
357             super(instanceKey);
358             this.channelId = channelId;
359         }
360     }
361
362     private static class RemoveDevice extends Message {
363         public RemoveDevice(final InstanceKey instanceKey) {
364             super(instanceKey);
365         }
366     }
367
368     private static class StartDiscoveryMessage extends Message {
369         private final EchonetDiscoveryListener echonetDiscoveryListener;
370
371         public StartDiscoveryMessage(EchonetDiscoveryListener echonetDiscoveryListener, InstanceKey discoveryKey) {
372             super(discoveryKey);
373             this.echonetDiscoveryListener = echonetDiscoveryListener;
374         }
375     }
376
377     private static class StopDiscoveryMessage extends Message {
378         public StopDiscoveryMessage(InstanceKey discoveryKey) {
379             super(discoveryKey);
380         }
381     }
382
383     private static class UpdateDevice extends Message {
384         private final String channelId;
385         private final State state;
386
387         public UpdateDevice(final InstanceKey instanceKey, final String channelId, final State state) {
388             super(instanceKey);
389             this.channelId = channelId;
390             this.state = state;
391         }
392
393         public String toString() {
394             return "UpdateDevice{" + "instanceKey=" + instanceKey + ", channelId='" + channelId + '\'' + ", state="
395                     + state + "} " + super.toString();
396         }
397     }
398 }