2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.plugwise.internal.handler;
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.*;
19 import java.io.IOException;
20 import java.time.Duration;
21 import java.util.Collection;
22 import java.util.List;
24 import java.util.concurrent.CopyOnWriteArrayList;
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;
60 * The {@link PlugwiseStickHandler} handles channel updates and commands for a Plugwise Stick device.
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.
67 * @author Wouter Born, Karel Goderis - Initial contribution
70 public class PlugwiseStickHandler extends BaseBridgeHandler implements PlugwiseMessageListener {
72 private final PlugwiseDeviceTask onlineStateUpdateTask = new PlugwiseDeviceTask("Online state update", scheduler) {
74 public Duration getConfiguredInterval() {
75 return Duration.ofSeconds(20);
79 public void runTask() {
84 public boolean shouldBeScheduled() {
85 return thing.getStatus() == OFFLINE;
89 private final Logger logger = LoggerFactory.getLogger(PlugwiseStickHandler.class);
90 private final PlugwiseCommunicationHandler communicationHandler;
91 private final List<PlugwiseStickStatusListener> statusListeners = new CopyOnWriteArrayList<>();
93 private PlugwiseStickConfig configuration = new PlugwiseStickConfig();
95 private @Nullable MACAddress circlePlusMAC;
96 private @Nullable MACAddress stickMAC;
98 public PlugwiseStickHandler(Bridge bridge, SerialPortManager serialPortManager) {
100 communicationHandler = new PlugwiseCommunicationHandler(bridge.getUID(), () -> configuration,
104 public void addMessageListener(PlugwiseMessageListener listener) {
105 communicationHandler.addMessageListener(listener);
108 public void addMessageListener(PlugwiseMessageListener listener, MACAddress macAddress) {
109 communicationHandler.addMessageListener(listener, macAddress);
112 public void addStickStatusListener(PlugwiseStickStatusListener listener) {
113 statusListeners.add(listener);
114 listener.stickStatusChanged(thing.getStatus());
118 public void dispose() {
119 communicationHandler.stop();
120 communicationHandler.removeMessageListener(this);
121 onlineStateUpdateTask.stop();
124 public @Nullable MACAddress getCirclePlusMAC() {
125 return circlePlusMAC;
129 public Collection<Class<? extends ThingHandlerService>> getServices() {
130 return List.of(PlugwiseThingDiscoveryService.class);
133 public @Nullable MACAddress getStickMAC() {
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))) {
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);
156 public void handleCommand(ChannelUID channelUID, Command command) {
157 logger.debug("Handling command, channelUID: {}, command: {}", channelUID, command);
160 private void handleDeviceInformationResponse(InformationResponseMessage message) {
161 if (message.getDeviceType() == STICK) {
162 updateProperties(message);
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));
174 logger.debug("The network is offline: circlePlusMAC={}, stickMAC={}", circlePlusMAC, stickMAC);
175 updateStatus(OFFLINE);
180 public void handleReponseMessage(Message message) {
181 switch (message.getType()) {
182 case ACKNOWLEDGEMENT_V1:
183 case ACKNOWLEDGEMENT_V2:
184 handleAcknowledgement((AcknowledgementMessage) message);
186 case DEVICE_INFORMATION_RESPONSE:
187 handleDeviceInformationResponse((InformationResponseMessage) message);
189 case NETWORK_STATUS_RESPONSE:
190 handleNetworkStatusResponse((NetworkStatusResponseMessage) message);
193 logger.trace("Received unhandled {} message from {}", message.getType(), message.getMACAddress());
199 public void initialize() {
200 configuration = getConfigAs(PlugwiseStickConfig.class);
201 communicationHandler.addMessageListener(this);
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());
213 public void removeMessageListener(PlugwiseMessageListener listener) {
214 communicationHandler.removeMessageListener(listener);
217 public void removeMessageListener(PlugwiseMessageListener listener, MACAddress macAddress) {
218 communicationHandler.addMessageListener(listener, macAddress);
221 public void removeStickStatusListener(PlugwiseStickStatusListener listener) {
222 statusListeners.remove(listener);
225 private void sendMessage(Message message) {
226 sendMessage(message, PlugwiseMessagePriority.UPDATE_AND_DISCOVERY);
229 public void sendMessage(Message message, PlugwiseMessagePriority priority) {
231 communicationHandler.sendMessage(message, priority);
232 } catch (IOException e) {
233 communicationHandler.stop();
234 communicationHandler.removeMessageListener(this);
235 updateStatus(OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
239 protected void updateProperties(InformationResponseMessage message) {
240 Map<String, String> properties = editProperties();
241 boolean update = PlugwiseUtils.updateProperties(properties, message);
244 updateProperties(properties);
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();
254 if (!oldStatus.equals(newStatus)) {
255 logger.debug("Updating listeners with status {}", status);
256 for (PlugwiseStickStatusListener listener : statusListeners) {
257 listener.stickStatusChanged(status);
259 updateTask(onlineStateUpdateTask);
263 protected void updateTask(PlugwiseDeviceTask task) {
264 if (task.shouldBeScheduled()) {
265 if (!task.isScheduled() || !task.getConfiguredInterval().equals(task.getInterval())) {
266 if (task.isScheduled()) {
269 task.update(DeviceType.STICK, getStickMAC());
272 } else if (!task.shouldBeScheduled() && task.isScheduled()) {