]> git.basschouten.com Git - openhab-addons.git/blob
b95700cfcb7ed18e6ee0f212163987455910b34d
[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.plugwise.internal.handler;
14
15 import static org.openhab.binding.plugwise.internal.PlugwiseBindingConstants.CONFIG_PROPERTY_MAC_ADDRESS;
16 import static org.openhab.binding.plugwise.internal.protocol.field.DeviceType.STICK;
17 import static org.openhab.core.thing.ThingStatus.*;
18
19 import java.io.IOException;
20 import java.time.Duration;
21 import java.util.Collection;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.concurrent.CopyOnWriteArrayList;
25
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.plugwise.internal.PlugwiseCommunicationHandler;
29 import org.openhab.binding.plugwise.internal.PlugwiseDeviceTask;
30 import org.openhab.binding.plugwise.internal.PlugwiseInitializationException;
31 import org.openhab.binding.plugwise.internal.PlugwiseMessagePriority;
32 import org.openhab.binding.plugwise.internal.PlugwiseThingDiscoveryService;
33 import org.openhab.binding.plugwise.internal.PlugwiseUtils;
34 import org.openhab.binding.plugwise.internal.config.PlugwiseStickConfig;
35 import org.openhab.binding.plugwise.internal.listener.PlugwiseMessageListener;
36 import org.openhab.binding.plugwise.internal.listener.PlugwiseStickStatusListener;
37 import org.openhab.binding.plugwise.internal.protocol.AcknowledgementMessage;
38 import org.openhab.binding.plugwise.internal.protocol.AcknowledgementMessage.ExtensionCode;
39 import org.openhab.binding.plugwise.internal.protocol.InformationRequestMessage;
40 import org.openhab.binding.plugwise.internal.protocol.InformationResponseMessage;
41 import org.openhab.binding.plugwise.internal.protocol.Message;
42 import org.openhab.binding.plugwise.internal.protocol.NetworkStatusRequestMessage;
43 import org.openhab.binding.plugwise.internal.protocol.NetworkStatusResponseMessage;
44 import org.openhab.binding.plugwise.internal.protocol.field.DeviceType;
45 import org.openhab.binding.plugwise.internal.protocol.field.MACAddress;
46 import org.openhab.core.io.transport.serial.SerialPortManager;
47 import org.openhab.core.thing.Bridge;
48 import org.openhab.core.thing.ChannelUID;
49 import org.openhab.core.thing.Thing;
50 import org.openhab.core.thing.ThingStatus;
51 import org.openhab.core.thing.ThingStatusDetail;
52 import org.openhab.core.thing.binding.BaseBridgeHandler;
53 import org.openhab.core.thing.binding.ThingHandlerService;
54 import org.openhab.core.types.Command;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
57
58 /**
59  * <p>
60  * The {@link PlugwiseStickHandler} handles channel updates and commands for a Plugwise Stick device.
61  * </p>
62  * <p>
63  * The Stick is an USB Zigbee controller that communicates with the Circle+. It is a {@link Bridge} to the devices on a
64  * Plugwise Zigbee mesh network.
65  * </p>
66  *
67  * @author Wouter Born, Karel Goderis - Initial contribution
68  */
69 @NonNullByDefault
70 public class PlugwiseStickHandler extends BaseBridgeHandler implements PlugwiseMessageListener {
71
72     private final PlugwiseDeviceTask onlineStateUpdateTask = new PlugwiseDeviceTask("Online state update", scheduler) {
73         @Override
74         public Duration getConfiguredInterval() {
75             return Duration.ofSeconds(20);
76         }
77
78         @Override
79         public void runTask() {
80             initialize();
81         }
82
83         @Override
84         public boolean shouldBeScheduled() {
85             return thing.getStatus() == OFFLINE;
86         }
87     };
88
89     private final Logger logger = LoggerFactory.getLogger(PlugwiseStickHandler.class);
90     private final PlugwiseCommunicationHandler communicationHandler;
91     private final List<PlugwiseStickStatusListener> statusListeners = new CopyOnWriteArrayList<>();
92
93     private PlugwiseStickConfig configuration = new PlugwiseStickConfig();
94
95     private @Nullable MACAddress circlePlusMAC;
96     private @Nullable MACAddress stickMAC;
97
98     public PlugwiseStickHandler(Bridge bridge, SerialPortManager serialPortManager) {
99         super(bridge);
100         communicationHandler = new PlugwiseCommunicationHandler(bridge.getUID(), () -> configuration,
101                 serialPortManager);
102     }
103
104     public void addMessageListener(PlugwiseMessageListener listener) {
105         communicationHandler.addMessageListener(listener);
106     }
107
108     public void addMessageListener(PlugwiseMessageListener listener, MACAddress macAddress) {
109         communicationHandler.addMessageListener(listener, macAddress);
110     }
111
112     public void addStickStatusListener(PlugwiseStickStatusListener listener) {
113         statusListeners.add(listener);
114         listener.stickStatusChanged(thing.getStatus());
115     }
116
117     @Override
118     public void dispose() {
119         communicationHandler.stop();
120         communicationHandler.removeMessageListener(this);
121         onlineStateUpdateTask.stop();
122     }
123
124     public @Nullable MACAddress getCirclePlusMAC() {
125         return circlePlusMAC;
126     }
127
128     @Override
129     public Collection<Class<? extends ThingHandlerService>> getServices() {
130         return List.of(PlugwiseThingDiscoveryService.class);
131     }
132
133     public @Nullable MACAddress getStickMAC() {
134         return stickMAC;
135     }
136
137     public @Nullable Thing getThingByMAC(MACAddress macAddress) {
138         for (Thing thing : getThing().getThings()) {
139             String thingMAC = (String) thing.getConfiguration().get(CONFIG_PROPERTY_MAC_ADDRESS);
140             if (thingMAC != null && macAddress.equals(new MACAddress(thingMAC))) {
141                 return thing;
142             }
143         }
144
145         return null;
146     }
147
148     private void handleAcknowledgement(AcknowledgementMessage acknowledge) {
149         if (acknowledge.isExtended() && acknowledge.getExtensionCode() == ExtensionCode.CIRCLE_PLUS) {
150             circlePlusMAC = acknowledge.getMACAddress();
151             logger.debug("Received extended acknowledgement, Circle+ MAC: {}", circlePlusMAC);
152         }
153     }
154
155     @Override
156     public void handleCommand(ChannelUID channelUID, Command command) {
157         logger.debug("Handling command, channelUID: {}, command: {}", channelUID, command);
158     }
159
160     private void handleDeviceInformationResponse(InformationResponseMessage message) {
161         if (message.getDeviceType() == STICK) {
162             updateProperties(message);
163         }
164     }
165
166     private void handleNetworkStatusResponse(NetworkStatusResponseMessage message) {
167         stickMAC = message.getMACAddress();
168         if (message.isOnline()) {
169             circlePlusMAC = message.getCirclePlusMAC();
170             logger.debug("The network is online: circlePlusMAC={}, stickMAC={}", circlePlusMAC, stickMAC);
171             updateStatus(ONLINE);
172             sendMessage(new InformationRequestMessage(stickMAC));
173         } else {
174             logger.debug("The network is offline: circlePlusMAC={}, stickMAC={}", circlePlusMAC, stickMAC);
175             updateStatus(OFFLINE);
176         }
177     }
178
179     @Override
180     public void handleReponseMessage(Message message) {
181         switch (message.getType()) {
182             case ACKNOWLEDGEMENT_V1:
183             case ACKNOWLEDGEMENT_V2:
184                 handleAcknowledgement((AcknowledgementMessage) message);
185                 break;
186             case DEVICE_INFORMATION_RESPONSE:
187                 handleDeviceInformationResponse((InformationResponseMessage) message);
188                 break;
189             case NETWORK_STATUS_RESPONSE:
190                 handleNetworkStatusResponse((NetworkStatusResponseMessage) message);
191                 break;
192             default:
193                 logger.trace("Received unhandled {} message from {}", message.getType(), message.getMACAddress());
194                 break;
195         }
196     }
197
198     @Override
199     public void initialize() {
200         configuration = getConfigAs(PlugwiseStickConfig.class);
201         communicationHandler.addMessageListener(this);
202
203         try {
204             communicationHandler.start();
205             sendMessage(new NetworkStatusRequestMessage());
206         } catch (PlugwiseInitializationException e) {
207             communicationHandler.stop();
208             communicationHandler.removeMessageListener(this);
209             updateStatus(OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
210         }
211     }
212
213     public void removeMessageListener(PlugwiseMessageListener listener) {
214         communicationHandler.removeMessageListener(listener);
215     }
216
217     public void removeMessageListener(PlugwiseMessageListener listener, MACAddress macAddress) {
218         communicationHandler.addMessageListener(listener, macAddress);
219     }
220
221     public void removeStickStatusListener(PlugwiseStickStatusListener listener) {
222         statusListeners.remove(listener);
223     }
224
225     private void sendMessage(Message message) {
226         sendMessage(message, PlugwiseMessagePriority.UPDATE_AND_DISCOVERY);
227     }
228
229     public void sendMessage(Message message, PlugwiseMessagePriority priority) {
230         try {
231             communicationHandler.sendMessage(message, priority);
232         } catch (IOException e) {
233             communicationHandler.stop();
234             communicationHandler.removeMessageListener(this);
235             updateStatus(OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
236         }
237     }
238
239     protected void updateProperties(InformationResponseMessage message) {
240         Map<String, String> properties = editProperties();
241         boolean update = PlugwiseUtils.updateProperties(properties, message);
242
243         if (update) {
244             updateProperties(properties);
245         }
246     }
247
248     @Override
249     protected void updateStatus(ThingStatus status, ThingStatusDetail detail, @Nullable String comment) {
250         ThingStatus oldStatus = thing.getStatus();
251         super.updateStatus(status, detail, comment);
252         ThingStatus newStatus = thing.getStatus();
253
254         if (!oldStatus.equals(newStatus)) {
255             logger.debug("Updating listeners with status {}", status);
256             for (PlugwiseStickStatusListener listener : statusListeners) {
257                 listener.stickStatusChanged(status);
258             }
259             updateTask(onlineStateUpdateTask);
260         }
261     }
262
263     protected void updateTask(PlugwiseDeviceTask task) {
264         if (task.shouldBeScheduled()) {
265             if (!task.isScheduled() || !task.getConfiguredInterval().equals(task.getInterval())) {
266                 if (task.isScheduled()) {
267                     task.stop();
268                 }
269                 task.update(DeviceType.STICK, getStickMAC());
270                 task.start();
271             }
272         } else if (!task.shouldBeScheduled() && task.isScheduled()) {
273             task.stop();
274         }
275     }
276 }