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.CHANNEL_LAST_SEEN;
16 import static org.openhab.core.thing.ThingStatus.*;
18 import java.math.BigDecimal;
19 import java.time.Duration;
20 import java.time.LocalDateTime;
21 import java.util.List;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.plugwise.internal.PlugwiseBindingConstants;
27 import org.openhab.binding.plugwise.internal.PlugwiseDeviceTask;
28 import org.openhab.binding.plugwise.internal.PlugwiseMessagePriority;
29 import org.openhab.binding.plugwise.internal.PlugwiseUtils;
30 import org.openhab.binding.plugwise.internal.listener.PlugwiseMessageListener;
31 import org.openhab.binding.plugwise.internal.protocol.InformationRequestMessage;
32 import org.openhab.binding.plugwise.internal.protocol.InformationResponseMessage;
33 import org.openhab.binding.plugwise.internal.protocol.Message;
34 import org.openhab.binding.plugwise.internal.protocol.PingRequestMessage;
35 import org.openhab.binding.plugwise.internal.protocol.field.DeviceType;
36 import org.openhab.binding.plugwise.internal.protocol.field.MACAddress;
37 import org.openhab.core.thing.Bridge;
38 import org.openhab.core.thing.Channel;
39 import org.openhab.core.thing.ChannelUID;
40 import org.openhab.core.thing.Thing;
41 import org.openhab.core.thing.ThingStatus;
42 import org.openhab.core.thing.ThingStatusDetail;
43 import org.openhab.core.thing.ThingStatusInfo;
44 import org.openhab.core.thing.binding.BaseThingHandler;
45 import org.openhab.core.types.Command;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
50 * The {@link AbstractPlugwiseThingHandler} handles common Plugwise device channel updates and commands.
52 * @author Wouter Born - Initial contribution
55 public abstract class AbstractPlugwiseThingHandler extends BaseThingHandler implements PlugwiseMessageListener {
57 private static final Duration DEFAULT_UPDATE_INTERVAL = Duration.ofMinutes(1);
58 private static final Duration MESSAGE_TIMEOUT = Duration.ofSeconds(15);
59 private static final int MAX_UNANSWERED_PINGS = 2;
61 private final PlugwiseDeviceTask onlineStateUpdateTask = new PlugwiseDeviceTask("Online state update", scheduler) {
63 public Duration getConfiguredInterval() {
64 return MESSAGE_TIMEOUT;
68 public void runTask() {
73 public boolean shouldBeScheduled() {
74 return shouldOnlineTaskBeScheduled();
84 private final Logger logger = LoggerFactory.getLogger(AbstractPlugwiseThingHandler.class);
86 private LocalDateTime lastSeen = LocalDateTime.MIN;
87 private @Nullable PlugwiseStickHandler stickHandler;
88 private @Nullable LocalDateTime lastConfigurationUpdateSend;
89 private int unansweredPings;
91 protected AbstractPlugwiseThingHandler(Thing thing) {
95 protected void addMessageListener() {
96 if (stickHandler != null) {
97 stickHandler.addMessageListener(this, getMACAddress());
102 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
103 updateBridgeStatus();
107 public void dispose() {
108 removeMessageListener();
109 onlineStateUpdateTask.stop();
112 protected Duration durationSinceLastSeen() {
113 return Duration.between(lastSeen, LocalDateTime.now());
116 protected Duration getChannelUpdateInterval(String channelId) {
117 Channel channel = thing.getChannel(channelId);
118 if (channel == null) {
119 return DEFAULT_UPDATE_INTERVAL;
121 BigDecimal interval = (BigDecimal) channel.getConfiguration()
122 .get(PlugwiseBindingConstants.CONFIG_PROPERTY_UPDATE_INTERVAL);
123 return interval != null ? Duration.ofSeconds(interval.intValue()) : DEFAULT_UPDATE_INTERVAL;
126 protected DeviceType getDeviceType() {
127 return PlugwiseUtils.getDeviceType(thing.getThingTypeUID());
130 protected abstract MACAddress getMACAddress();
132 protected ThingStatusDetail getThingStatusDetail() {
133 return isConfigurationPending() ? ThingStatusDetail.CONFIGURATION_PENDING : ThingStatusDetail.NONE;
137 public void handleCommand(ChannelUID channelUID, Command command) {
138 logger.debug("Handling command '{}' for {} ({}) channel '{}'", command, getDeviceType(), getMACAddress(),
143 public void initialize() {
144 updateBridgeStatus();
145 updateTask(onlineStateUpdateTask);
147 // Add the message listener after dispose/initialize due to configuration update
148 if (isInitialized()) {
149 addMessageListener();
152 // Send configuration update commands after configuration update
153 if (thing.getStatus() == ONLINE) {
154 sendConfigurationUpdateCommands();
158 protected boolean isConfigurationPending() {
162 protected void ping() {
163 sendMessage(new PingRequestMessage(getMACAddress()));
166 protected boolean recentlySendConfigurationUpdate() {
167 LocalDateTime lastConfigurationUpdateSend = this.lastConfigurationUpdateSend;
168 return lastConfigurationUpdateSend != null
169 && LocalDateTime.now().minus(Duration.ofMillis(500)).isBefore(lastConfigurationUpdateSend);
172 protected void removeMessageListener() {
173 if (stickHandler != null) {
174 stickHandler.removeMessageListener(this);
178 protected abstract boolean shouldOnlineTaskBeScheduled();
180 protected void sendCommandMessage(Message message) {
181 if (stickHandler != null) {
182 stickHandler.sendMessage(message, PlugwiseMessagePriority.COMMAND);
186 protected void sendConfigurationUpdateCommands() {
187 lastConfigurationUpdateSend = LocalDateTime.now();
188 if (getThingStatusDetail() != thing.getStatusInfo().getStatusDetail()) {
189 updateStatus(thing.getStatus(), getThingStatusDetail());
193 protected void sendFastUpdateMessage(Message message) {
194 if (stickHandler != null) {
195 stickHandler.sendMessage(message, PlugwiseMessagePriority.FAST_UPDATE);
199 protected void sendMessage(Message message) {
200 if (stickHandler != null) {
201 stickHandler.sendMessage(message, PlugwiseMessagePriority.UPDATE_AND_DISCOVERY);
205 protected void stopTasks(List<PlugwiseDeviceTask> tasks) {
206 for (PlugwiseDeviceTask task : tasks) {
212 * Updates the thing state based on that of the Stick
214 protected void updateBridgeStatus() {
215 Bridge bridge = getBridge();
216 ThingStatus bridgeStatus = bridge != null ? bridge.getStatus() : null;
217 if (bridge == null) {
218 removeMessageListener();
219 updateStatus(OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
220 } else if (bridgeStatus == ONLINE && thing.getStatus() != ONLINE) {
221 stickHandler = (PlugwiseStickHandler) bridge.getHandler();
222 addMessageListener();
223 updateStatus(OFFLINE, getThingStatusDetail());
224 } else if (bridgeStatus == OFFLINE) {
225 removeMessageListener();
226 updateStatus(OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
227 } else if (bridgeStatus == UNKNOWN) {
228 removeMessageListener();
229 updateStatus(UNKNOWN);
233 protected void updateInformation() {
234 sendMessage(new InformationRequestMessage(getMACAddress()));
237 protected void updateLastSeen() {
239 lastSeen = LocalDateTime.now();
240 if (isLinked(CHANNEL_LAST_SEEN)) {
241 updateState(CHANNEL_LAST_SEEN, PlugwiseUtils.newDateTimeType(lastSeen));
243 if (thing.getStatus() == OFFLINE) {
244 updateStatus(ONLINE, getThingStatusDetail());
248 protected void updateOnlineState() {
249 ThingStatus status = thing.getStatus();
250 if (status == ONLINE && unansweredPings < MAX_UNANSWERED_PINGS
251 && MESSAGE_TIMEOUT.minus(durationSinceLastSeen()).isNegative()) {
254 } else if (status == ONLINE && unansweredPings >= MAX_UNANSWERED_PINGS) {
255 updateStatus(OFFLINE, getThingStatusDetail());
257 } else if (status == OFFLINE) {
262 protected void updateProperties(InformationResponseMessage message) {
263 Map<String, String> properties = editProperties();
264 boolean update = PlugwiseUtils.updateProperties(properties, message);
267 updateProperties(properties);
272 protected void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
273 ThingStatus oldStatus = thing.getStatus();
274 super.updateStatus(status, statusDetail, description);
276 updateTask(onlineStateUpdateTask);
277 if (oldStatus != ONLINE && status == ONLINE && isConfigurationPending()) {
278 sendConfigurationUpdateCommands();
282 protected void updateStatusOnDetailChange() {
283 if (thing.getStatusInfo().getStatusDetail() != getThingStatusDetail()) {
284 updateStatus(thing.getStatus(), getThingStatusDetail());
288 protected void updateTask(PlugwiseDeviceTask task) {
289 if (task.shouldBeScheduled()) {
290 if (!task.isScheduled() || !task.getConfiguredInterval().equals(task.getInterval())) {
291 if (task.isScheduled()) {
294 task.update(getDeviceType(), getMACAddress());
297 } else if (!task.shouldBeScheduled() && task.isScheduled()) {
302 protected void updateTasks(List<PlugwiseDeviceTask> tasks) {
303 for (PlugwiseDeviceTask task : tasks) {