]> git.basschouten.com Git - openhab-addons.git/blob
03a5621afce6d119bfcf08e9cbe05582fd28cfb3
[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()));
210                 }, 5, 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)) {
357             if (configuration.getPowerStateChanging() == PowerStateChanging.COMMAND_SWITCHING) {
358                 OnOffType onOff = (OnOffType) command;
359                 pendingPowerStateChange = new PendingPowerStateChange(onOff);
360                 handleOnOffCommand(onOff);
361             } else {
362                 OnOffType onOff = configuration.getPowerStateChanging() == PowerStateChanging.ALWAYS_ON ? OnOffType.ON
363                         : OnOffType.OFF;
364                 logger.debug("Ignoring {} ({}) power state change (always {})", deviceType, macAddress, onOff);
365                 updateState(CHANNEL_STATE, onOff);
366             }
367         }
368     }
369
370     private void handleInformationResponse(InformationResponseMessage message) {
371         recentLogAddress = message.getLogAddress();
372         OnOffType powerState = message.getPowerState() ? OnOffType.ON : OnOffType.OFF;
373
374         PendingPowerStateChange change = pendingPowerStateChange;
375         if (change != null) {
376             if (powerState == change.onOff) {
377                 pendingPowerStateChange = null;
378             } else {
379                 // Power state change message may be lost or the informationUpdateTask may have queried the power
380                 // state just before the power state change message arrived
381                 if (change.retries < POWER_STATE_RETRIES) {
382                     change.retries++;
383                     logger.warn("Retrying to switch {} ({}) {} (retry #{})", deviceType, macAddress, change.onOff,
384                             change.retries);
385                     handleOnOffCommand(change.onOff);
386                 } else {
387                     logger.warn("Failed to switch {} ({}) {} after {} retries", deviceType, macAddress, change.onOff,
388                             change.retries);
389                     pendingPowerStateChange = null;
390                 }
391             }
392         }
393
394         if (pendingPowerStateChange == null) {
395             updateState(CHANNEL_STATE, powerState);
396             correctPowerState(powerState);
397         }
398
399         if (energy == null && isCalibrated()) {
400             updateEnergy();
401         }
402
403         updateProperties(message);
404     }
405
406     private void handleOnOffCommand(OnOffType command) {
407         sendCommandMessage(new PowerChangeRequestMessage(macAddress, command == OnOffType.ON));
408         sendFastUpdateMessage(new InformationRequestMessage(macAddress));
409
410         // Measurements take 2 seconds to become stable
411         scheduler.schedule(() -> sendFastUpdateMessage(new PowerInformationRequestMessage(macAddress)), 2,
412                 TimeUnit.SECONDS);
413     }
414
415     private void handlePowerBufferResponse(PowerBufferResponseMessage message) {
416         PowerCalibration localCalibration = calibration;
417         if (localCalibration == null) {
418             calibrate();
419             return;
420         }
421
422         Energy mostRecentEnergy = message.getMostRecentDatapoint();
423
424         if (mostRecentEnergy != null) {
425             // When the current time is '11:44:55.888' and the measurement interval 1 hour, then the end of the most
426             // recent energy measurement interval is at '11:00:00.000'
427             LocalDateTime oneIntervalAgo = LocalDateTime.now().minus(configuration.getMeasurementInterval());
428
429             boolean isLastInterval = mostRecentEnergy.getEnd().isAfter(oneIntervalAgo);
430             if (isLastInterval) {
431                 mostRecentEnergy.setInterval(configuration.getMeasurementInterval());
432                 energy = mostRecentEnergy;
433                 logger.trace("Updating {} ({}) energy with: {}", deviceType, macAddress, mostRecentEnergy);
434                 updateState(CHANNEL_ENERGY,
435                         new QuantityType<>(correctSign(mostRecentEnergy.tokWh(localCalibration)), Units.KILOWATT_HOUR));
436                 LocalDateTime start = mostRecentEnergy.getStart();
437                 updateState(CHANNEL_ENERGY_STAMP,
438                         start != null ? PlugwiseUtils.newDateTimeType(start) : UnDefType.NULL);
439             } else {
440                 logger.trace("Most recent energy in buffer of {} ({}) is older than one interval ago: {}", deviceType,
441                         macAddress, mostRecentEnergy);
442             }
443         } else {
444             logger.trace("Most recent energy in buffer of {} ({}) is null", deviceType, macAddress);
445         }
446     }
447
448     private void handlePowerInformationResponse(PowerInformationResponseMessage message) {
449         PowerCalibration localCalibration = calibration;
450         if (localCalibration == null) {
451             calibrate();
452             return;
453         }
454
455         Energy one = message.getOneSecond();
456         double watt = one.toWatt(localCalibration);
457         if (watt > INVALID_WATT_THRESHOLD) {
458             logger.debug("{} ({}) is in a kind of error state, skipping power information response", deviceType,
459                     macAddress);
460             return;
461         }
462
463         updateState(CHANNEL_POWER, new QuantityType<>(correctSign(watt), Units.WATT));
464     }
465
466     private void handleRealTimeClockGetResponse(RealTimeClockGetResponseMessage message) {
467         updateState(CHANNEL_REAL_TIME_CLOCK, PlugwiseUtils.newDateTimeType(message.getDateTime()));
468     }
469
470     @Override
471     public void handleReponseMessage(Message message) {
472         updateLastSeen();
473
474         switch (message.getType()) {
475             case ACKNOWLEDGEMENT_V1:
476             case ACKNOWLEDGEMENT_V2:
477                 handleAcknowledgement((AcknowledgementMessage) message);
478                 break;
479             case CLOCK_GET_RESPONSE:
480                 handleClockGetResponse(((ClockGetResponseMessage) message));
481                 break;
482             case DEVICE_INFORMATION_RESPONSE:
483                 handleInformationResponse((InformationResponseMessage) message);
484                 break;
485             case POWER_BUFFER_RESPONSE:
486                 handlePowerBufferResponse((PowerBufferResponseMessage) message);
487                 break;
488             case POWER_CALIBRATION_RESPONSE:
489                 handleCalibrationResponse(((PowerCalibrationResponseMessage) message));
490                 break;
491             case POWER_INFORMATION_RESPONSE:
492                 handlePowerInformationResponse((PowerInformationResponseMessage) message);
493                 break;
494             case REAL_TIME_CLOCK_GET_RESPONSE:
495                 handleRealTimeClockGetResponse((RealTimeClockGetResponseMessage) message);
496                 break;
497             default:
498                 logger.trace("Received unhandled {} message from {} ({})", message.getType(), deviceType, macAddress);
499                 break;
500         }
501     }
502
503     @Override
504     public void initialize() {
505         configuration = getConfigAs(PlugwiseRelayConfig.class);
506         macAddress = configuration.getMACAddress();
507         if (!isInitialized()) {
508             setUpdateCommandFlags(null, configuration);
509         }
510         if (configuration.isTemporarilyNotInNetwork()) {
511             updateStatus(OFFLINE);
512         }
513         updateTasks(recurringTasks);
514         super.initialize();
515     }
516
517     private boolean isCalibrated() {
518         return calibration != null;
519     }
520
521     @Override
522     protected boolean isConfigurationPending() {
523         return updateMeasurementInterval;
524     }
525
526     private boolean isRecentLogAddressKnown() {
527         return recentLogAddress >= 0;
528     }
529
530     @Override
531     protected void sendConfigurationUpdateCommands() {
532         logger.debug("Sending {} ({}) configuration update commands", deviceType, macAddress);
533
534         if (updateMeasurementInterval) {
535             logger.debug("Sending command to update {} ({}) power log measurement interval", deviceType, macAddress);
536             Duration consumptionInterval = configuration.isSuppliesPower() ? Duration.ZERO
537                     : configuration.getMeasurementInterval();
538             Duration productionInterval = configuration.isSuppliesPower() ? configuration.getMeasurementInterval()
539                     : Duration.ZERO;
540             sendCommandMessage(
541                     new PowerLogIntervalSetRequestMessage(macAddress, consumptionInterval, productionInterval));
542         }
543
544         super.sendConfigurationUpdateCommands();
545     }
546
547     private void setUpdateCommandFlags(@Nullable PlugwiseRelayConfig oldConfiguration,
548             PlugwiseRelayConfig newConfiguration) {
549         boolean fullUpdate = newConfiguration.isUpdateConfiguration() && !isConfigurationPending();
550         if (fullUpdate) {
551             logger.debug("Updating all configuration properties of {} ({})", deviceType, macAddress);
552         }
553
554         updateMeasurementInterval = fullUpdate || (oldConfiguration != null
555                 && (!oldConfiguration.getMeasurementInterval().equals(newConfiguration.getMeasurementInterval())));
556         if (updateMeasurementInterval) {
557             logger.debug("Updating {} ({}) power log interval when online", deviceType, macAddress);
558         }
559     }
560
561     @Override
562     protected boolean shouldOnlineTaskBeScheduled() {
563         Bridge bridge = getBridge();
564         return !configuration.isTemporarilyNotInNetwork() && (bridge != null && bridge.getStatus() == ONLINE);
565     }
566
567     @Override
568     protected void updateConfiguration(Configuration configuration) {
569         PlugwiseRelayConfig oldConfiguration = this.configuration;
570         PlugwiseRelayConfig newConfiguration = configuration.as(PlugwiseRelayConfig.class);
571
572         setUpdateCommandFlags(oldConfiguration, newConfiguration);
573
574         configuration.put(CONFIG_PROPERTY_UPDATE_CONFIGURATION, isConfigurationPending());
575
576         super.updateConfiguration(configuration);
577     }
578
579     private void updateEnergy() {
580         int previousLogAddress = recentLogAddress - 1;
581         while (previousLogAddress <= recentLogAddress) {
582             PowerBufferRequestMessage message = new PowerBufferRequestMessage(macAddress, previousLogAddress);
583             previousLogAddress = previousLogAddress + 1;
584             sendMessage(message);
585         }
586     }
587
588     @Override
589     protected void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
590         super.updateStatus(status, statusDetail, description);
591
592         if (status == ONLINE) {
593             if (!isCalibrated()) {
594                 calibrate();
595             }
596             if (editProperties().isEmpty()) {
597                 updateInformation();
598             }
599         }
600
601         updateTasks(recurringTasks);
602     }
603 }