2 * Copyright (c) 2010-2020 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.List;
23 import java.util.concurrent.CopyOnWriteArrayList;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.plugwise.internal.PlugwiseCommunicationHandler;
28 import org.openhab.binding.plugwise.internal.PlugwiseDeviceTask;
29 import org.openhab.binding.plugwise.internal.PlugwiseInitializationException;
30 import org.openhab.binding.plugwise.internal.PlugwiseMessagePriority;
31 import org.openhab.binding.plugwise.internal.PlugwiseUtils;
32 import org.openhab.binding.plugwise.internal.config.PlugwiseStickConfig;
33 import org.openhab.binding.plugwise.internal.listener.PlugwiseMessageListener;
34 import org.openhab.binding.plugwise.internal.listener.PlugwiseStickStatusListener;
35 import org.openhab.binding.plugwise.internal.protocol.AcknowledgementMessage;
36 import org.openhab.binding.plugwise.internal.protocol.AcknowledgementMessage.ExtensionCode;
37 import org.openhab.binding.plugwise.internal.protocol.InformationRequestMessage;
38 import org.openhab.binding.plugwise.internal.protocol.InformationResponseMessage;
39 import org.openhab.binding.plugwise.internal.protocol.Message;
40 import org.openhab.binding.plugwise.internal.protocol.NetworkStatusRequestMessage;
41 import org.openhab.binding.plugwise.internal.protocol.NetworkStatusResponseMessage;
42 import org.openhab.binding.plugwise.internal.protocol.field.DeviceType;
43 import org.openhab.binding.plugwise.internal.protocol.field.MACAddress;
44 import org.openhab.core.io.transport.serial.SerialPortManager;
45 import org.openhab.core.thing.Bridge;
46 import org.openhab.core.thing.ChannelUID;
47 import org.openhab.core.thing.Thing;
48 import org.openhab.core.thing.ThingStatus;
49 import org.openhab.core.thing.ThingStatusDetail;
50 import org.openhab.core.thing.binding.BaseBridgeHandler;
51 import org.openhab.core.types.Command;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
57 * The {@link PlugwiseStickHandler} handles channel updates and commands for a Plugwise Stick device.
60 * The Stick is an USB ZigBee controller that communicates with the Circle+. It is a {@link Bridge} to the devices on a
61 * Plugwise ZigBee mesh network.
64 * @author Wouter Born, Karel Goderis - Initial contribution
67 public class PlugwiseStickHandler extends BaseBridgeHandler implements PlugwiseMessageListener {
69 private final PlugwiseDeviceTask onlineStateUpdateTask = new PlugwiseDeviceTask("Online state update", scheduler) {
71 public Duration getConfiguredInterval() {
72 return Duration.ofSeconds(20);
76 public void runTask() {
81 public boolean shouldBeScheduled() {
82 return thing.getStatus() == OFFLINE;
86 private final Logger logger = LoggerFactory.getLogger(PlugwiseStickHandler.class);
87 private final PlugwiseCommunicationHandler communicationHandler;
88 private final List<PlugwiseStickStatusListener> statusListeners = new CopyOnWriteArrayList<>();
90 private PlugwiseStickConfig configuration = new PlugwiseStickConfig();
92 private @Nullable MACAddress circlePlusMAC;
93 private @Nullable MACAddress stickMAC;
95 public PlugwiseStickHandler(Bridge bridge, SerialPortManager serialPortManager) {
97 communicationHandler = new PlugwiseCommunicationHandler(bridge.getUID(), () -> configuration,
101 public void addMessageListener(PlugwiseMessageListener listener) {
102 communicationHandler.addMessageListener(listener);
105 public void addMessageListener(PlugwiseMessageListener listener, MACAddress macAddress) {
106 communicationHandler.addMessageListener(listener, macAddress);
109 public void addStickStatusListener(PlugwiseStickStatusListener listener) {
110 statusListeners.add(listener);
111 listener.stickStatusChanged(thing.getStatus());
115 public void dispose() {
116 communicationHandler.stop();
117 communicationHandler.removeMessageListener(this);
118 onlineStateUpdateTask.stop();
121 public @Nullable MACAddress getCirclePlusMAC() {
122 return circlePlusMAC;
125 public @Nullable MACAddress getStickMAC() {
129 public @Nullable Thing getThingByMAC(MACAddress macAddress) {
130 for (Thing thing : getThing().getThings()) {
131 String thingMAC = (String) thing.getConfiguration().get(CONFIG_PROPERTY_MAC_ADDRESS);
132 if (thingMAC != null && macAddress.equals(new MACAddress(thingMAC))) {
140 private void handleAcknowledgement(AcknowledgementMessage acknowledge) {
141 if (acknowledge.isExtended() && acknowledge.getExtensionCode() == ExtensionCode.CIRCLE_PLUS) {
142 circlePlusMAC = acknowledge.getMACAddress();
143 logger.debug("Received extended acknowledgement, Circle+ MAC: {}", circlePlusMAC);
148 public void handleCommand(ChannelUID channelUID, Command command) {
149 logger.debug("Handling command, channelUID: {}, command: {}", channelUID, command);
152 private void handleDeviceInformationResponse(InformationResponseMessage message) {
153 if (message.getDeviceType() == STICK) {
154 updateProperties(message);
158 private void handleNetworkStatusResponse(NetworkStatusResponseMessage message) {
159 stickMAC = message.getMACAddress();
160 if (message.isOnline()) {
161 circlePlusMAC = message.getCirclePlusMAC();
162 logger.debug("The network is online: circlePlusMAC={}, stickMAC={}", circlePlusMAC, stickMAC);
163 updateStatus(ONLINE);
164 sendMessage(new InformationRequestMessage(stickMAC));
166 logger.debug("The network is offline: circlePlusMAC={}, stickMAC={}", circlePlusMAC, stickMAC);
167 updateStatus(OFFLINE);
172 public void handleReponseMessage(Message message) {
173 switch (message.getType()) {
174 case ACKNOWLEDGEMENT_V1:
175 case ACKNOWLEDGEMENT_V2:
176 handleAcknowledgement((AcknowledgementMessage) message);
178 case DEVICE_INFORMATION_RESPONSE:
179 handleDeviceInformationResponse((InformationResponseMessage) message);
181 case NETWORK_STATUS_RESPONSE:
182 handleNetworkStatusResponse((NetworkStatusResponseMessage) message);
185 logger.trace("Received unhandled {} message from {}", message.getType(), message.getMACAddress());
191 public void initialize() {
192 configuration = getConfigAs(PlugwiseStickConfig.class);
193 communicationHandler.addMessageListener(this);
196 communicationHandler.start();
197 sendMessage(new NetworkStatusRequestMessage());
198 } catch (PlugwiseInitializationException e) {
199 communicationHandler.stop();
200 communicationHandler.removeMessageListener(this);
201 updateStatus(OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
205 public void removeMessageListener(PlugwiseMessageListener listener) {
206 communicationHandler.removeMessageListener(listener);
209 public void removeMessageListener(PlugwiseMessageListener listener, MACAddress macAddress) {
210 communicationHandler.addMessageListener(listener, macAddress);
213 public void removeStickStatusListener(PlugwiseStickStatusListener listener) {
214 statusListeners.remove(listener);
217 private void sendMessage(Message message) {
218 sendMessage(message, PlugwiseMessagePriority.UPDATE_AND_DISCOVERY);
221 public void sendMessage(Message message, PlugwiseMessagePriority priority) {
223 communicationHandler.sendMessage(message, priority);
224 } catch (IOException e) {
225 communicationHandler.stop();
226 communicationHandler.removeMessageListener(this);
227 updateStatus(OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
231 protected void updateProperties(InformationResponseMessage message) {
232 Map<String, String> properties = editProperties();
233 boolean update = PlugwiseUtils.updateProperties(properties, message);
236 updateProperties(properties);
241 protected void updateStatus(ThingStatus status, ThingStatusDetail detail, @Nullable String comment) {
242 ThingStatus oldStatus = thing.getStatus();
243 super.updateStatus(status, detail, comment);
244 ThingStatus newStatus = thing.getStatus();
246 if (!oldStatus.equals(newStatus)) {
247 logger.debug("Updating listeners with status {}", status);
248 for (PlugwiseStickStatusListener listener : statusListeners) {
249 listener.stickStatusChanged(status);
251 updateTask(onlineStateUpdateTask);
255 protected void updateTask(PlugwiseDeviceTask task) {
256 if (task.shouldBeScheduled()) {
257 if (!task.isScheduled() || task.getConfiguredInterval() != task.getInterval()) {
258 if (task.isScheduled()) {
261 task.update(DeviceType.STICK, getStickMAC());
264 } else if (!task.shouldBeScheduled() && task.isScheduled()) {