]> git.basschouten.com Git - openhab-addons.git/blob
8fb51e7eb9fce8d257bb50959858d8b6018992fe
[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.HashMap;
23 import java.util.List;
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 device) {
94                 logger.debug("Update item: {} already discovered", message.instanceKey);
95                 device.setTimeouts(message.pollIntervalMs, message.retryTimeoutMs);
96                 device.setListener(message.echonetDeviceListener);
97             } else {
98                 logger.debug("Item: {} already discovered, but was not a device", message.instanceKey);
99             }
100         } else {
101             logger.debug("New Device: {}", message.instanceKey);
102             final EchonetDevice device = new EchonetDevice(message.instanceKey, message.echonetDeviceListener);
103             device.setTimeouts(message.pollIntervalMs, message.retryTimeoutMs);
104             devicesByKey.put(message.instanceKey, device);
105         }
106     }
107
108     public void refreshDevice(final InstanceKey instanceKey, final String channelId) {
109         requests.add(new RefreshMessage(instanceKey, channelId));
110     }
111
112     private void refreshDeviceInternal(final RefreshMessage refreshMessage) {
113         final EchonetObject item = devicesByKey.get(refreshMessage.instanceKey);
114         if (null != item) {
115             item.refresh(refreshMessage.channelId);
116         }
117     }
118
119     public void removeDevice(final InstanceKey instanceKey) {
120         requests.add(new RemoveDevice(instanceKey));
121     }
122
123     private void removeDeviceInternal(final RemoveDevice removeDevice) {
124         final EchonetObject remove = devicesByKey.remove(removeDevice.instanceKey);
125
126         logger.debug("Removing device: {}, {}", removeDevice.instanceKey, remove);
127         if (null != remove) {
128             remove.removed();
129         }
130     }
131
132     public void updateDevice(final InstanceKey instanceKey, final String id, final State command) {
133         requests.add(new UpdateDevice(instanceKey, id, command));
134     }
135
136     public void updateDeviceInternal(UpdateDevice updateDevice) {
137         final EchonetObject echonetObject = devicesByKey.get(updateDevice.instanceKey);
138
139         if (null == echonetObject) {
140             logger.warn("Device not found for update: {}", updateDevice);
141             return;
142         }
143
144         echonetObject.update(updateDevice.channelId, updateDevice.state);
145     }
146
147     public void startDiscovery(EchonetDiscoveryListener echonetDiscoveryListener) {
148         requests.offer(new StartDiscoveryMessage(echonetDiscoveryListener, requireNonNull(discoveryKey)));
149     }
150
151     public void startDiscoveryInternal(StartDiscoveryMessage startDiscovery) {
152         devicesByKey.put(startDiscovery.instanceKey, new EchonetProfileNode(startDiscovery.instanceKey,
153                 this::onDiscoveredInstanceKey, startDiscovery.echonetDiscoveryListener));
154     }
155
156     public void stopDiscovery() {
157         requests.offer(new StopDiscoveryMessage(requireNonNull(discoveryKey)));
158     }
159
160     private void stopDiscoveryInternal(StopDiscoveryMessage stopDiscovery) {
161         devicesByKey.remove(stopDiscovery.instanceKey);
162     }
163
164     private void onDiscoveredInstanceKey(EchonetDevice device) {
165         if (null == devicesByKey.putIfAbsent(device.instanceKey(), device)) {
166             logger.debug("New device discovered: {}", device.instanceKey);
167         }
168     }
169
170     private void pollDevices(long nowMs, EchonetChannel echonetChannel) {
171         for (EchonetObject echonetObject : devicesByKey.values()) {
172             if (echonetObject.buildUpdateMessage(messageBuilder, echonetChannel::nextTid, nowMs,
173                     requireNonNull(managementControllerKey))) {
174                 try {
175                     echonetChannel.sendMessage(messageBuilder);
176                 } catch (IOException e) {
177                     logger.warn("Failed to send echonet message", e);
178                 }
179             }
180
181             echonetObject.refreshAll(nowMs);
182
183             if (echonetObject.buildPollMessage(messageBuilder, echonetChannel::nextTid, nowMs,
184                     requireNonNull(managementControllerKey))) {
185                 try {
186                     echonetChannel.sendMessage(messageBuilder);
187                 } catch (IOException e) {
188                     logger.warn("Failed to send echonet message", e);
189                 }
190             } else {
191                 echonetObject.checkTimeouts();
192             }
193         }
194     }
195
196     private void pollRequests() {
197         Message message;
198         while (null != (message = requestsPoll())) {
199             logger.debug("Received request: {}", message);
200             if (message instanceof NewDeviceMessage deviceMessage) {
201                 newDeviceInternal(deviceMessage);
202             } else if (message instanceof RefreshMessage refreshMessage) {
203                 refreshDeviceInternal(refreshMessage);
204             } else if (message instanceof RemoveDevice device) {
205                 removeDeviceInternal(device);
206             } else if (message instanceof UpdateDevice device) {
207                 updateDeviceInternal(device);
208             } else if (message instanceof StartDiscoveryMessage discoveryMessage) {
209                 startDiscoveryInternal(discoveryMessage);
210             } else if (message instanceof StopDiscoveryMessage discoveryMessage) {
211                 stopDiscoveryInternal(discoveryMessage);
212             }
213         }
214     }
215
216     private @Nullable Message requestsPoll() {
217         return requests.poll();
218     }
219
220     private void pollNetwork(EchonetChannel echonetChannel) {
221         try {
222             echonetChannel.pollMessages(echonetMessage, this::onMessage,
223                     EchonetLiteBindingConstants.NETWORK_WAIT_TIMEOUT);
224         } catch (IOException e) {
225             logger.warn("Failed to poll for messages", e);
226         }
227     }
228
229     private void onMessage(final EchonetMessage echonetMessage, final SocketAddress sourceAddress) {
230         final EchonetClass echonetClass = echonetMessage.sourceClass();
231         if (null == echonetClass) {
232             logger.warn("Unable to find echonetClass for message: {}, from: {}", echonetMessage.toDebug(),
233                     sourceAddress);
234             return;
235         }
236
237         final InstanceKey instanceKey = new InstanceKey((InetSocketAddress) sourceAddress, echonetClass,
238                 echonetMessage.instance());
239         final Esv esv = echonetMessage.esv();
240
241         EchonetObject echonetObject = devicesByKey.get(instanceKey);
242         if (null == echonetObject) {
243             echonetObject = devicesByKey.get(discoveryKey);
244         }
245
246         logger.debug("Message {} for: {}", esv, echonetObject);
247         if (null != echonetObject) {
248             echonetObject.applyHeader(esv, echonetMessage.tid(), clock.timeMs());
249             while (echonetMessage.moveNext()) {
250                 final int epc = echonetMessage.currentEpc();
251                 final int pdc = echonetMessage.currentPdc();
252                 ByteBuffer edt = echonetMessage.currentEdt();
253                 echonetObject.applyProperty(instanceKey, esv, epc, pdc, edt);
254             }
255         }
256     }
257
258     private void poll() {
259         try {
260             doPoll();
261             updateStatus(ThingStatus.ONLINE);
262
263             while (!Thread.currentThread().isInterrupted()) {
264                 doPoll();
265             }
266         } catch (Exception e) {
267             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
268         }
269     }
270
271     private void doPoll() {
272         final long nowMs = clock.timeMs();
273         pollRequests();
274         pollDevices(nowMs, requireNonNull(echonetChannel));
275         pollNetwork(requireNonNull(echonetChannel));
276     }
277
278     @Override
279     public void initialize() {
280         final EchonetBridgeConfig bridgeConfig = getConfigAs(EchonetBridgeConfig.class);
281
282         final InstanceKey managementControllerKey = new InstanceKey(new InetSocketAddress(bridgeConfig.port),
283                 EchonetClass.MANAGEMENT_CONTROLLER, (byte) 0x01);
284         final InstanceKey discoveryKey = new InstanceKey(
285                 new InetSocketAddress(requireNonNull(bridgeConfig.multicastAddress), bridgeConfig.port),
286                 EchonetClass.NODE_PROFILE, (byte) 0x01);
287
288         updateStatus(ThingStatus.UNKNOWN);
289
290         try {
291             start(managementControllerKey, discoveryKey);
292         } catch (IOException e) {
293             throw new IllegalStateException("Unable to start networking thread", e);
294         }
295     }
296
297     @Override
298     public void dispose() {
299         if (networkingThread.isAlive()) {
300             networkingThread.interrupt();
301             try {
302                 networkingThread.join(TimeUnit.SECONDS.toMillis(5));
303             } catch (InterruptedException e) {
304                 logger.debug("Interrupted while closing", e);
305             }
306         }
307
308         @Nullable
309         final EchonetChannel echonetChannel = this.echonetChannel;
310         if (null != echonetChannel) {
311             echonetChannel.close();
312         }
313     }
314
315     @Override
316     public void handleCommand(ChannelUID channelUID, Command command) {
317     }
318
319     @Override
320     public Collection<Class<? extends ThingHandlerService>> getServices() {
321         return List.of(EchonetDiscoveryService.class);
322     }
323
324     private abstract static class Message {
325         final InstanceKey instanceKey;
326
327         public Message(InstanceKey instanceKey) {
328             this.instanceKey = instanceKey;
329         }
330     }
331
332     private static final class NewDeviceMessage extends Message {
333         final long pollIntervalMs;
334         final long retryTimeoutMs;
335         final EchonetDeviceListener echonetDeviceListener;
336
337         public NewDeviceMessage(final InstanceKey instanceKey, long pollIntervalMs, long retryTimeoutMs,
338                 final EchonetDeviceListener echonetDeviceListener) {
339             super(instanceKey);
340             this.pollIntervalMs = pollIntervalMs;
341             this.retryTimeoutMs = retryTimeoutMs;
342             this.echonetDeviceListener = echonetDeviceListener;
343         }
344
345         @Override
346         public String toString() {
347             return "NewDeviceMessage{" + "instanceKey=" + instanceKey + ", pollIntervalMs=" + pollIntervalMs
348                     + ", retryTimeoutMs=" + retryTimeoutMs + "} " + super.toString();
349         }
350     }
351
352     private static class RefreshMessage extends Message {
353         private final String channelId;
354
355         public RefreshMessage(InstanceKey instanceKey, String channelId) {
356             super(instanceKey);
357             this.channelId = channelId;
358         }
359     }
360
361     private static class RemoveDevice extends Message {
362         public RemoveDevice(final InstanceKey instanceKey) {
363             super(instanceKey);
364         }
365     }
366
367     private static class StartDiscoveryMessage extends Message {
368         private final EchonetDiscoveryListener echonetDiscoveryListener;
369
370         public StartDiscoveryMessage(EchonetDiscoveryListener echonetDiscoveryListener, InstanceKey discoveryKey) {
371             super(discoveryKey);
372             this.echonetDiscoveryListener = echonetDiscoveryListener;
373         }
374     }
375
376     private static class StopDiscoveryMessage extends Message {
377         public StopDiscoveryMessage(InstanceKey discoveryKey) {
378             super(discoveryKey);
379         }
380     }
381
382     private static class UpdateDevice extends Message {
383         private final String channelId;
384         private final State state;
385
386         public UpdateDevice(final InstanceKey instanceKey, final String channelId, final State state) {
387             super(instanceKey);
388             this.channelId = channelId;
389             this.state = state;
390         }
391
392         @Override
393         public String toString() {
394             return "UpdateDevice{" + "instanceKey=" + instanceKey + ", channelId='" + channelId + '\'' + ", state="
395                     + state + "} " + super.toString();
396         }
397     }
398 }