]> git.basschouten.com Git - openhab-addons.git/blob
e4cc01105e24f167e8ab0c94bc69ad3139bd2bfe
[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.*;
16 import static org.openhab.core.thing.ThingStatus.*;
17
18 import java.time.Duration;
19 import java.time.LocalDateTime;
20 import java.util.List;
21 import java.util.concurrent.TimeUnit;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.plugwise.internal.PlugwiseDeviceTask;
26 import org.openhab.binding.plugwise.internal.PlugwiseUtils;
27 import org.openhab.binding.plugwise.internal.config.PlugwiseRelayConfig;
28 import org.openhab.binding.plugwise.internal.config.PlugwiseRelayConfig.PowerStateChanging;
29 import org.openhab.binding.plugwise.internal.protocol.AcknowledgementMessage;
30 import org.openhab.binding.plugwise.internal.protocol.AcknowledgementMessage.ExtensionCode;
31 import org.openhab.binding.plugwise.internal.protocol.ClockGetRequestMessage;
32 import org.openhab.binding.plugwise.internal.protocol.ClockGetResponseMessage;
33 import org.openhab.binding.plugwise.internal.protocol.ClockSetRequestMessage;
34 import org.openhab.binding.plugwise.internal.protocol.InformationRequestMessage;
35 import org.openhab.binding.plugwise.internal.protocol.InformationResponseMessage;
36 import org.openhab.binding.plugwise.internal.protocol.Message;
37 import org.openhab.binding.plugwise.internal.protocol.PowerBufferRequestMessage;
38 import org.openhab.binding.plugwise.internal.protocol.PowerBufferResponseMessage;
39 import org.openhab.binding.plugwise.internal.protocol.PowerCalibrationRequestMessage;
40 import org.openhab.binding.plugwise.internal.protocol.PowerCalibrationResponseMessage;
41 import org.openhab.binding.plugwise.internal.protocol.PowerChangeRequestMessage;
42 import org.openhab.binding.plugwise.internal.protocol.PowerInformationRequestMessage;
43 import org.openhab.binding.plugwise.internal.protocol.PowerInformationResponseMessage;
44 import org.openhab.binding.plugwise.internal.protocol.PowerLogIntervalSetRequestMessage;
45 import org.openhab.binding.plugwise.internal.protocol.RealTimeClockGetRequestMessage;
46 import org.openhab.binding.plugwise.internal.protocol.RealTimeClockGetResponseMessage;
47 import org.openhab.binding.plugwise.internal.protocol.RealTimeClockSetRequestMessage;
48 import org.openhab.binding.plugwise.internal.protocol.field.DeviceType;
49 import org.openhab.binding.plugwise.internal.protocol.field.Energy;
50 import org.openhab.binding.plugwise.internal.protocol.field.MACAddress;
51 import org.openhab.binding.plugwise.internal.protocol.field.PowerCalibration;
52 import org.openhab.core.config.core.Configuration;
53 import org.openhab.core.library.types.OnOffType;
54 import org.openhab.core.library.types.QuantityType;
55 import org.openhab.core.library.types.StringType;
56 import org.openhab.core.library.unit.Units;
57 import org.openhab.core.thing.Bridge;
58 import org.openhab.core.thing.ChannelUID;
59 import org.openhab.core.thing.Thing;
60 import org.openhab.core.thing.ThingStatus;
61 import org.openhab.core.thing.ThingStatusDetail;
62 import org.openhab.core.types.Command;
63 import org.openhab.core.types.UnDefType;
64 import org.slf4j.Logger;
65 import org.slf4j.LoggerFactory;
66
67 /**
68  * <p>
69  * The {@link PlugwiseRelayDeviceHandler} handles channel updates and commands for a Plugwise device with a relay.
70  * Relay devices are the Circle, Circle+ and Stealth.
71  * </p>
72  * <p>
73  * A Circle maintains current energy usage by counting 'pulses' in a one or eight-second interval. Furthermore, it
74  * stores hourly energy usage as well in a buffer. Each entry in the buffer contains usage for the last 4 full hours of
75  * consumption. In order to convert pulses to energy (kWh) or power (W), a calculation is made in the {@link Energy}
76  * class with {@link PowerCalibration} data.
77  * </p>
78  * <p>
79  * A Circle+ is a special Circle. There is one Circle+ in a Plugwise network. The Circle+ serves as a master controller
80  * in a Plugwise network. It also provides clock data to the other devices and sends messages from and to the Stick.
81  * </p>
82  * <p>
83  * A Stealth behaves like a Circle but it has a more compact form factor.
84  * </p>
85  *
86  * @author Wouter Born, Karel Goderis - Initial contribution
87  */
88 @NonNullByDefault
89 public class PlugwiseRelayDeviceHandler extends AbstractPlugwiseThingHandler {
90
91     private static final int INVALID_WATT_THRESHOLD = 10000;
92     private static final int POWER_STATE_RETRIES = 3;
93
94     private class PendingPowerStateChange {
95         final OnOffType onOff;
96         int retries;
97
98         PendingPowerStateChange(OnOffType onOff) {
99             this.onOff = onOff;
100         }
101     }
102
103     private final PlugwiseDeviceTask clockUpdateTask = new PlugwiseDeviceTask("Clock update", scheduler) {
104         @Override
105         public Duration getConfiguredInterval() {
106             return getChannelUpdateInterval(CHANNEL_CLOCK);
107         }
108
109         @Override
110         public void runTask() {
111             sendMessage(new ClockGetRequestMessage(macAddress));
112         }
113
114         @Override
115         public boolean shouldBeScheduled() {
116             return thing.getStatus() == ONLINE && isLinked(CHANNEL_CLOCK);
117         }
118     };
119
120     private final PlugwiseDeviceTask currentPowerUpdateTask = new PlugwiseDeviceTask("Current power update",
121             scheduler) {
122         @Override
123         public Duration getConfiguredInterval() {
124             return getChannelUpdateInterval(CHANNEL_POWER);
125         }
126
127         @Override
128         public void runTask() {
129             if (isCalibrated()) {
130                 sendMessage(new PowerInformationRequestMessage(macAddress));
131             }
132         }
133
134         @Override
135         public boolean shouldBeScheduled() {
136             return thing.getStatus() == ONLINE && (isLinked(CHANNEL_POWER)
137                     || configuration.getPowerStateChanging() != PowerStateChanging.COMMAND_SWITCHING);
138         }
139     };
140
141     private final PlugwiseDeviceTask energyUpdateTask = new PlugwiseDeviceTask("Energy update", scheduler) {
142         @Override
143         public Duration getConfiguredInterval() {
144             return getChannelUpdateInterval(CHANNEL_ENERGY);
145         }
146
147         @Override
148         public void runTask() {
149             if (isRecentLogAddressKnown()) {
150                 updateEnergy();
151             }
152         }
153
154         @Override
155         public boolean shouldBeScheduled() {
156             return thing.getStatus() == ONLINE && isLinked(CHANNEL_ENERGY);
157         }
158     };
159
160     private final PlugwiseDeviceTask informationUpdateTask = new PlugwiseDeviceTask("Information update", scheduler) {
161         @Override
162         public Duration getConfiguredInterval() {
163             return PlugwiseUtils.minComparable(getChannelUpdateInterval(CHANNEL_STATE),
164                     getChannelUpdateInterval(CHANNEL_ENERGY));
165         }
166
167         @Override
168         public void runTask() {
169             updateInformation();
170         }
171
172         @Override
173         public boolean shouldBeScheduled() {
174             return thing.getStatus() == ONLINE && (isLinked(CHANNEL_STATE) || isLinked(CHANNEL_ENERGY));
175         }
176     };
177
178     private final PlugwiseDeviceTask realTimeClockUpdateTask = new PlugwiseDeviceTask("Real-time clock update",
179             scheduler) {
180         @Override
181         public Duration getConfiguredInterval() {
182             return getChannelUpdateInterval(CHANNEL_REAL_TIME_CLOCK);
183         }
184
185         @Override
186         public void runTask() {
187             sendMessage(new RealTimeClockGetRequestMessage(macAddress));
188         }
189
190         @Override
191         public boolean shouldBeScheduled() {
192             return thing.getStatus() == ONLINE && deviceType == DeviceType.CIRCLE_PLUS
193                     && isLinked(CHANNEL_REAL_TIME_CLOCK);
194         }
195     };
196
197     private final PlugwiseDeviceTask setClockTask = new PlugwiseDeviceTask("Set clock", scheduler) {
198         @Override
199         public Duration getConfiguredInterval() {
200             return Duration.ofDays(1);
201         }
202
203         @Override
204         public void runTask() {
205             if (deviceType == DeviceType.CIRCLE_PLUS) {
206                 // The Circle+ real-time clock needs to be updated first to prevent clock sync issues
207                 sendCommandMessage(new RealTimeClockSetRequestMessage(macAddress, LocalDateTime.now()));
208                 scheduler.schedule(
209                         () -> sendCommandMessage(new ClockSetRequestMessage(macAddress, LocalDateTime.now())), 5,
210                         TimeUnit.SECONDS);
211             } else {
212                 sendCommandMessage(new ClockSetRequestMessage(macAddress, LocalDateTime.now()));
213             }
214         }
215
216         @Override
217         public boolean shouldBeScheduled() {
218             return thing.getStatus() == ONLINE;
219         }
220     };
221
222     private final List<PlugwiseDeviceTask> recurringTasks = List.of(clockUpdateTask, currentPowerUpdateTask,
223             energyUpdateTask, informationUpdateTask, realTimeClockUpdateTask, setClockTask);
224
225     private final Logger logger = LoggerFactory.getLogger(PlugwiseRelayDeviceHandler.class);
226     private final DeviceType deviceType;
227
228     private int recentLogAddress = -1;
229
230     private @NonNullByDefault({}) PlugwiseRelayConfig configuration;
231     private @NonNullByDefault({}) MACAddress macAddress;
232
233     private @Nullable PowerCalibration calibration;
234     private @Nullable Energy energy;
235     private @Nullable PendingPowerStateChange pendingPowerStateChange;
236
237     // Flag that keeps track of the pending "measurement interval" device configuration update. When the corresponding
238     // Thing configuration parameter changes it is set to true. When the Circle/Stealth goes online a command is sent to
239     // update the device configuration. When the Circle/Stealth acknowledges the command the flag is again set to false.
240     private boolean updateMeasurementInterval;
241
242     public PlugwiseRelayDeviceHandler(Thing thing) {
243         super(thing);
244         deviceType = getDeviceType();
245     }
246
247     private void calibrate() {
248         sendFastUpdateMessage(new PowerCalibrationRequestMessage(macAddress));
249     }
250
251     @Override
252     public void channelLinked(ChannelUID channelUID) {
253         updateTasks(recurringTasks);
254     }
255
256     @Override
257     public void channelUnlinked(ChannelUID channelUID) {
258         updateTasks(recurringTasks);
259     }
260
261     private void correctPowerState(OnOffType powerState) {
262         if (configuration.getPowerStateChanging() == PowerStateChanging.ALWAYS_OFF && (powerState != OnOffType.OFF)) {
263             logger.debug("Correcting power state of {} ({}) to off", deviceType, macAddress);
264             handleOnOffCommand(OnOffType.OFF);
265         } else if (configuration.getPowerStateChanging() == PowerStateChanging.ALWAYS_ON
266                 && (powerState != OnOffType.ON)) {
267             logger.debug("Correcting power state of {} ({}) to on", deviceType, macAddress);
268             handleOnOffCommand(OnOffType.ON);
269         }
270     }
271
272     private double correctSign(double value) {
273         return configuration.isSuppliesPower() ? -Math.abs(value) : Math.abs(value);
274     }
275
276     @Override
277     public void dispose() {
278         stopTasks(recurringTasks);
279         super.dispose();
280     }
281
282     @Override
283     protected MACAddress getMACAddress() {
284         return macAddress;
285     }
286
287     private void handleAcknowledgement(AcknowledgementMessage message) {
288         boolean oldConfigurationPending = isConfigurationPending();
289
290         ExtensionCode extensionCode = message.getExtensionCode();
291         switch (extensionCode) {
292             case CLOCK_SET_ACK:
293                 logger.debug("Received ACK for clock set of {} ({})", deviceType, macAddress);
294                 sendMessage(new ClockGetRequestMessage(macAddress));
295                 break;
296             case ON_ACK:
297                 logger.debug("Received ACK for switching on {} ({})", deviceType, macAddress);
298                 updateState(CHANNEL_STATE, OnOffType.ON);
299                 break;
300             case ON_OFF_NACK:
301                 logger.debug("Received NACK for switching on/off {} ({})", deviceType, macAddress);
302                 break;
303             case OFF_ACK:
304                 logger.debug("Received ACK for switching off {} ({})", deviceType, macAddress);
305                 updateState(CHANNEL_STATE, OnOffType.OFF);
306                 break;
307             case POWER_LOG_INTERVAL_SET_ACK:
308                 logger.debug("Received ACK for power log interval set of {} ({})", deviceType, macAddress);
309                 updateMeasurementInterval = false;
310                 break;
311             case REAL_TIME_CLOCK_SET_ACK:
312                 logger.debug("Received ACK for setting real-time clock of {} ({})", deviceType, macAddress);
313                 sendMessage(new RealTimeClockGetRequestMessage(macAddress));
314                 break;
315             case REAL_TIME_CLOCK_SET_NACK:
316                 logger.debug("Received NACK for setting real-time clock of {} ({})", deviceType, macAddress);
317                 break;
318             default:
319                 logger.debug("{} ({}) {} acknowledgement", deviceType, macAddress, extensionCode);
320                 break;
321         }
322
323         boolean newConfigurationPending = isConfigurationPending();
324
325         if (oldConfigurationPending != newConfigurationPending && !newConfigurationPending) {
326             Configuration newConfiguration = editConfiguration();
327             newConfiguration.put(CONFIG_PROPERTY_UPDATE_CONFIGURATION, false);
328             updateConfiguration(newConfiguration);
329         }
330
331         updateStatusOnDetailChange();
332     }
333
334     private void handleCalibrationResponse(PowerCalibrationResponseMessage message) {
335         boolean wasCalibrated = isCalibrated();
336         calibration = message.getCalibration();
337         logger.debug("{} ({}) calibrated: {}", deviceType, macAddress, calibration);
338         if (!wasCalibrated) {
339             if (isRecentLogAddressKnown()) {
340                 updateEnergy();
341             } else {
342                 updateInformation();
343             }
344             sendFastUpdateMessage(new PowerInformationRequestMessage(macAddress));
345         }
346     }
347
348     private void handleClockGetResponse(ClockGetResponseMessage message) {
349         updateState(CHANNEL_CLOCK, new StringType(message.getTime()));
350     }
351
352     @Override
353     public void handleCommand(ChannelUID channelUID, Command command) {
354         logger.debug("Handling command '{}' for {} ({}) channel '{}'", command, deviceType, macAddress,
355                 channelUID.getId());
356         if (CHANNEL_STATE.equals(channelUID.getId()) && (command instanceof OnOffType onOffCommand)) {
357             if (configuration.getPowerStateChanging() == PowerStateChanging.COMMAND_SWITCHING) {
358                 pendingPowerStateChange = new PendingPowerStateChange(onOffCommand);
359                 handleOnOffCommand(onOffCommand);
360             } else {
361                 OnOffType onOff = configuration.getPowerStateChanging() == PowerStateChanging.ALWAYS_ON ? OnOffType.ON
362                         : OnOffType.OFF;
363                 logger.debug("Ignoring {} ({}) power state change (always {})", deviceType, macAddress, onOff);
364                 updateState(CHANNEL_STATE, onOff);
365             }
366         }
367     }
368
369     private void handleInformationResponse(InformationResponseMessage message) {
370         recentLogAddress = message.getLogAddress();
371         OnOffType powerState = OnOffType.from(message.getPowerState());
372
373         PendingPowerStateChange change = pendingPowerStateChange;
374         if (change != null) {
375             if (powerState == change.onOff) {
376                 pendingPowerStateChange = null;
377             } else {
378                 // Power state change message may be lost or the informationUpdateTask may have queried the power
379                 // state just before the power state change message arrived
380                 if (change.retries < POWER_STATE_RETRIES) {
381                     change.retries++;
382                     logger.warn("Retrying to switch {} ({}) {} (retry #{})", deviceType, macAddress, change.onOff,
383                             change.retries);
384                     handleOnOffCommand(change.onOff);
385                 } else {
386                     logger.warn("Failed to switch {} ({}) {} after {} retries", deviceType, macAddress, change.onOff,
387                             change.retries);
388                     pendingPowerStateChange = null;
389                 }
390             }
391         }
392
393         if (pendingPowerStateChange == null) {
394             updateState(CHANNEL_STATE, powerState);
395             correctPowerState(powerState);
396         }
397
398         if (energy == null && isCalibrated()) {
399             updateEnergy();
400         }
401
402         updateProperties(message);
403     }
404
405     private void handleOnOffCommand(OnOffType command) {
406         sendCommandMessage(new PowerChangeRequestMessage(macAddress, command == OnOffType.ON));
407         sendFastUpdateMessage(new InformationRequestMessage(macAddress));
408
409         // Measurements take 2 seconds to become stable
410         scheduler.schedule(() -> sendFastUpdateMessage(new PowerInformationRequestMessage(macAddress)), 2,
411                 TimeUnit.SECONDS);
412     }
413
414     private void handlePowerBufferResponse(PowerBufferResponseMessage message) {
415         PowerCalibration localCalibration = calibration;
416         if (localCalibration == null) {
417             calibrate();
418             return;
419         }
420
421         Energy mostRecentEnergy = message.getMostRecentDatapoint();
422
423         if (mostRecentEnergy != null) {
424             // When the current time is '11:44:55.888' and the measurement interval 1 hour, then the end of the most
425             // recent energy measurement interval is at '11:00:00.000'
426             LocalDateTime oneIntervalAgo = LocalDateTime.now().minus(configuration.getMeasurementInterval());
427
428             boolean isLastInterval = mostRecentEnergy.getEnd().isAfter(oneIntervalAgo);
429             if (isLastInterval) {
430                 mostRecentEnergy.setInterval(configuration.getMeasurementInterval());
431                 energy = mostRecentEnergy;
432                 logger.trace("Updating {} ({}) energy with: {}", deviceType, macAddress, mostRecentEnergy);
433                 updateState(CHANNEL_ENERGY,
434                         new QuantityType<>(correctSign(mostRecentEnergy.tokWh(localCalibration)), Units.KILOWATT_HOUR));
435                 LocalDateTime start = mostRecentEnergy.getStart();
436                 updateState(CHANNEL_ENERGY_STAMP,
437                         start != null ? PlugwiseUtils.newDateTimeType(start) : UnDefType.NULL);
438             } else {
439                 logger.trace("Most recent energy in buffer of {} ({}) is older than one interval ago: {}", deviceType,
440                         macAddress, mostRecentEnergy);
441             }
442         } else {
443             logger.trace("Most recent energy in buffer of {} ({}) is null", deviceType, macAddress);
444         }
445     }
446
447     private void handlePowerInformationResponse(PowerInformationResponseMessage message) {
448         PowerCalibration localCalibration = calibration;
449         if (localCalibration == null) {
450             calibrate();
451             return;
452         }
453
454         Energy one = message.getOneSecond();
455         double watt = one.toWatt(localCalibration);
456         if (watt > INVALID_WATT_THRESHOLD) {
457             logger.debug("{} ({}) is in a kind of error state, skipping power information response", deviceType,
458                     macAddress);
459             return;
460         }
461
462         updateState(CHANNEL_POWER, new QuantityType<>(correctSign(watt), Units.WATT));
463     }
464
465     private void handleRealTimeClockGetResponse(RealTimeClockGetResponseMessage message) {
466         updateState(CHANNEL_REAL_TIME_CLOCK, PlugwiseUtils.newDateTimeType(message.getDateTime()));
467     }
468
469     @Override
470     public void handleResponseMessage(Message message) {
471         updateLastSeen();
472
473         switch (message.getType()) {
474             case ACKNOWLEDGEMENT_V1, ACKNOWLEDGEMENT_V2:
475                 handleAcknowledgement((AcknowledgementMessage) message);
476                 break;
477             case CLOCK_GET_RESPONSE:
478                 handleClockGetResponse(((ClockGetResponseMessage) message));
479                 break;
480             case DEVICE_INFORMATION_RESPONSE:
481                 handleInformationResponse((InformationResponseMessage) message);
482                 break;
483             case POWER_BUFFER_RESPONSE:
484                 handlePowerBufferResponse((PowerBufferResponseMessage) message);
485                 break;
486             case POWER_CALIBRATION_RESPONSE:
487                 handleCalibrationResponse(((PowerCalibrationResponseMessage) message));
488                 break;
489             case POWER_INFORMATION_RESPONSE:
490                 handlePowerInformationResponse((PowerInformationResponseMessage) message);
491                 break;
492             case REAL_TIME_CLOCK_GET_RESPONSE:
493                 handleRealTimeClockGetResponse((RealTimeClockGetResponseMessage) message);
494                 break;
495             default:
496                 logger.trace("Received unhandled {} message from {} ({})", message.getType(), deviceType, macAddress);
497                 break;
498         }
499     }
500
501     @Override
502     public void initialize() {
503         configuration = getConfigAs(PlugwiseRelayConfig.class);
504         macAddress = configuration.getMACAddress();
505         if (!isInitialized()) {
506             setUpdateCommandFlags(null, configuration);
507         }
508         if (configuration.isTemporarilyNotInNetwork()) {
509             updateStatus(OFFLINE);
510         }
511         updateTasks(recurringTasks);
512         super.initialize();
513     }
514
515     private boolean isCalibrated() {
516         return calibration != null;
517     }
518
519     @Override
520     protected boolean isConfigurationPending() {
521         return updateMeasurementInterval;
522     }
523
524     private boolean isRecentLogAddressKnown() {
525         return recentLogAddress >= 0;
526     }
527
528     @Override
529     protected void sendConfigurationUpdateCommands() {
530         logger.debug("Sending {} ({}) configuration update commands", deviceType, macAddress);
531
532         if (updateMeasurementInterval) {
533             logger.debug("Sending command to update {} ({}) power log measurement interval", deviceType, macAddress);
534             Duration consumptionInterval = configuration.isSuppliesPower() ? Duration.ZERO
535                     : configuration.getMeasurementInterval();
536             Duration productionInterval = configuration.isSuppliesPower() ? configuration.getMeasurementInterval()
537                     : Duration.ZERO;
538             sendCommandMessage(
539                     new PowerLogIntervalSetRequestMessage(macAddress, consumptionInterval, productionInterval));
540         }
541
542         super.sendConfigurationUpdateCommands();
543     }
544
545     private void setUpdateCommandFlags(@Nullable PlugwiseRelayConfig oldConfiguration,
546             PlugwiseRelayConfig newConfiguration) {
547         boolean fullUpdate = newConfiguration.isUpdateConfiguration() && !isConfigurationPending();
548         if (fullUpdate) {
549             logger.debug("Updating all configuration properties of {} ({})", deviceType, macAddress);
550         }
551
552         updateMeasurementInterval = fullUpdate || (oldConfiguration != null
553                 && (!oldConfiguration.getMeasurementInterval().equals(newConfiguration.getMeasurementInterval())));
554         if (updateMeasurementInterval) {
555             logger.debug("Updating {} ({}) power log interval when online", deviceType, macAddress);
556         }
557     }
558
559     @Override
560     protected boolean shouldOnlineTaskBeScheduled() {
561         Bridge bridge = getBridge();
562         return !configuration.isTemporarilyNotInNetwork() && (bridge != null && bridge.getStatus() == ONLINE);
563     }
564
565     @Override
566     protected void updateConfiguration(Configuration configuration) {
567         PlugwiseRelayConfig oldConfiguration = this.configuration;
568         PlugwiseRelayConfig newConfiguration = configuration.as(PlugwiseRelayConfig.class);
569
570         setUpdateCommandFlags(oldConfiguration, newConfiguration);
571
572         configuration.put(CONFIG_PROPERTY_UPDATE_CONFIGURATION, isConfigurationPending());
573
574         super.updateConfiguration(configuration);
575     }
576
577     private void updateEnergy() {
578         int previousLogAddress = recentLogAddress - 1;
579         while (previousLogAddress <= recentLogAddress) {
580             PowerBufferRequestMessage message = new PowerBufferRequestMessage(macAddress, previousLogAddress);
581             previousLogAddress = previousLogAddress + 1;
582             sendMessage(message);
583         }
584     }
585
586     @Override
587     protected void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
588         super.updateStatus(status, statusDetail, description);
589
590         if (status == ONLINE) {
591             if (!isCalibrated()) {
592                 calibrate();
593             }
594             if (editProperties().isEmpty()) {
595                 updateInformation();
596             }
597         }
598
599         updateTasks(recurringTasks);
600     }
601 }