2 * Copyright (c) 2010-2024 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.netatmo.internal.handler.capability;
15 import static java.time.temporal.ChronoUnit.*;
17 import java.time.Duration;
18 import java.time.Instant;
19 import java.time.ZonedDateTime;
20 import java.util.Optional;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.openhab.binding.netatmo.internal.api.dto.NAThing;
26 import org.openhab.binding.netatmo.internal.handler.CommonInterface;
27 import org.openhab.core.thing.ThingStatus;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
32 * {@link RefreshCapability} is the class used to embed the refreshing needs calculation for devices
34 * @author Gaƫl L'hopital - Initial contribution
38 public class RefreshCapability extends Capability {
39 private static final Duration DEFAULT_DELAY = Duration.of(20, SECONDS);
40 private static final Duration PROBING_INTERVAL = Duration.of(120, SECONDS);
41 private static final Duration OFFLINE_INTERVAL = Duration.of(15, MINUTES);
43 private final Logger logger = LoggerFactory.getLogger(RefreshCapability.class);
45 private Duration dataValidity;
46 private Instant dataTimeStamp = Instant.now();
47 private Instant dataTimeStamp0 = Instant.MIN;
48 private Optional<ScheduledFuture<?>> refreshJob = Optional.empty();
49 private boolean refreshConfigured;
51 public RefreshCapability(CommonInterface handler, int refreshInterval) {
53 this.dataValidity = Duration.ofSeconds(Math.max(0, refreshInterval));
57 public void initialize() {
58 this.refreshConfigured = !probing();
59 freeJobAndReschedule(2);
63 public void dispose() {
64 freeJobAndReschedule(0);
69 public void expireData() {
70 dataTimeStamp = Instant.now().minus(dataValidity);
71 freeJobAndReschedule(1);
74 private Duration dataAge() {
75 return Duration.between(dataTimeStamp, Instant.now());
78 private boolean probing() {
79 return dataValidity.getSeconds() <= 0;
82 private void proceedWithUpdate() {
83 handler.proceedWithUpdate();
85 if (!ThingStatus.ONLINE.equals(handler.getThing().getStatus())) {
86 logger.debug("{} is not ONLINE, special refresh interval is used", thingUID);
87 delay = OFFLINE_INTERVAL.toSeconds();
89 dataTimeStamp0 = Instant.MIN;
92 delay = refreshConfigured ? dataValidity.getSeconds()
93 : (probing() ? PROBING_INTERVAL : dataValidity.minus(dataAge()).plus(DEFAULT_DELAY)).toSeconds();
95 delay = delay < 2 ? PROBING_INTERVAL.toSeconds() : delay;
96 logger.debug("{} refreshed, next one in {}s", thingUID, delay);
97 freeJobAndReschedule(delay);
101 protected void updateNAThing(NAThing newData) {
102 super.updateNAThing(newData);
103 newData.getLastSeen().map(ZonedDateTime::toInstant).ifPresent(tsInstant -> {
105 if (Instant.MIN.equals(dataTimeStamp0)) {
106 dataTimeStamp0 = tsInstant;
107 logger.debug("First data timestamp of {} is {}", thingUID, dataTimeStamp0);
108 } else if (tsInstant.isAfter(dataTimeStamp0)) {
109 dataValidity = Duration.between(dataTimeStamp0, tsInstant);
110 refreshConfigured = true;
111 logger.debug("Data validity period of {} identified to be {}", thingUID, dataValidity);
113 logger.debug("Data validity period of {} not yet found, data timestamp unchanged", thingUID);
116 dataTimeStamp = tsInstant;
120 private void freeJobAndReschedule(long delay) {
121 refreshJob.ifPresent(job -> job.cancel(true));
122 refreshJob = Optional.ofNullable(delay == 0 ? null
123 : handler.getScheduler().schedule(() -> proceedWithUpdate(), delay, TimeUnit.SECONDS));