[netatmo] Consider timezone of the house when defining the end time of a setpoint (#17586)

* Consider timezone of the house when defining the end time of a setpoint

Signed-off-by: Gaël L'hopital <gael@lhopital.org>
Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
Gaël L'hopital 2024-10-21 20:32:01 +02:00 committed by Ciprian Pascu
parent d8f80dcee5
commit 749805d4a0
11 changed files with 71 additions and 21 deletions

View File

@ -47,6 +47,7 @@ import org.openhab.binding.netatmo.internal.handler.channelhelper.ChannelHelper;
import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider; import org.openhab.binding.netatmo.internal.providers.NetatmoDescriptionProvider;
import org.openhab.core.auth.client.oauth2.OAuthFactory; import org.openhab.core.auth.client.oauth2.OAuthFactory;
import org.openhab.core.config.core.ConfigParser; import org.openhab.core.config.core.ConfigParser;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
@ -80,17 +81,19 @@ public class NetatmoHandlerFactory extends BaseThingHandlerFactory {
private final HttpClient httpClient; private final HttpClient httpClient;
private final HttpService httpService; private final HttpService httpService;
private final OAuthFactory oAuthFactory; private final OAuthFactory oAuthFactory;
private final TimeZoneProvider timeZoneProvider;
@Activate @Activate
public NetatmoHandlerFactory(final @Reference NetatmoDescriptionProvider stateDescriptionProvider, public NetatmoHandlerFactory(final @Reference NetatmoDescriptionProvider stateDescriptionProvider,
final @Reference HttpClientFactory factory, final @Reference NADeserializer deserializer, final @Reference HttpClientFactory factory, final @Reference NADeserializer deserializer,
final @Reference HttpService httpService, final @Reference OAuthFactory oAuthFactory, final @Reference HttpService httpService, final @Reference OAuthFactory oAuthFactory,
Map<String, @Nullable Object> config) { final @Reference TimeZoneProvider timeZoneProvider, Map<String, @Nullable Object> config) {
this.stateDescriptionProvider = stateDescriptionProvider; this.stateDescriptionProvider = stateDescriptionProvider;
this.httpClient = factory.getCommonHttpClient(); this.httpClient = factory.getCommonHttpClient();
this.deserializer = deserializer; this.deserializer = deserializer;
this.httpService = httpService; this.httpService = httpService;
this.oAuthFactory = oAuthFactory; this.oAuthFactory = oAuthFactory;
this.timeZoneProvider = timeZoneProvider;
configChanged(config); configChanged(config);
} }
@ -119,7 +122,8 @@ public class NetatmoHandlerFactory extends BaseThingHandlerFactory {
return new ApiBridgeHandler((Bridge) thing, httpClient, deserializer, configuration, httpService, return new ApiBridgeHandler((Bridge) thing, httpClient, deserializer, configuration, httpService,
oAuthFactory); oAuthFactory);
} }
CommonInterface handler = moduleType.isABridge() ? new DeviceHandler((Bridge) thing) : new ModuleHandler(thing); CommonInterface handler = moduleType.isABridge() ? new DeviceHandler((Bridge) thing, timeZoneProvider)
: new ModuleHandler(thing, timeZoneProvider);
List<ChannelHelper> helpers = new ArrayList<>(); List<ChannelHelper> helpers = new ArrayList<>();
@ -127,6 +131,7 @@ public class NetatmoHandlerFactory extends BaseThingHandlerFactory {
moduleType.capabilities.forEach(capability -> { moduleType.capabilities.forEach(capability -> {
Capability newCap = null; Capability newCap = null;
if (capability == DeviceCapability.class) { if (capability == DeviceCapability.class) {
newCap = new DeviceCapability(handler); newCap = new DeviceCapability(handler);
} else if (capability == AirCareCapability.class) { } else if (capability == AirCareCapability.class) {
@ -161,7 +166,7 @@ public class NetatmoHandlerFactory extends BaseThingHandlerFactory {
if (newCap != null) { if (newCap != null) {
handler.getCapabilities().put(newCap); handler.getCapabilities().put(newCap);
} else { } else {
logger.warn("No factory entry defined to create Capability : {}", capability); logger.warn("No factory entry defined to create Capability: {}", capability);
} }
}); });

View File

@ -12,6 +12,7 @@
*/ */
package org.openhab.binding.netatmo.internal.api.dto; package org.openhab.binding.netatmo.internal.api.dto;
import java.time.Duration;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
@ -59,8 +60,8 @@ public class HomeData extends NAThing implements NAModule, LocationEx {
private int thermSetpointDefaultDuration; private int thermSetpointDefaultDuration;
private List<ThermProgram> schedules = List.of(); private List<ThermProgram> schedules = List.of();
public int getThermSetpointDefaultDuration() { public Duration getSetpointDefaultDuration() {
return thermSetpointDefaultDuration; return Duration.ofMinutes(thermSetpointDefaultDuration);
} }
public SetpointMode getThermMode() { public SetpointMode getThermMode() {
@ -109,8 +110,8 @@ public class HomeData extends NAThing implements NAModule, LocationEx {
} }
@Override @Override
public Optional<String> getTimezone() { public @Nullable String getTimezone() {
return Optional.ofNullable(timezone); return timezone;
} }
public NAObjectMap<HomeDataRoom> getRooms() { public NAObjectMap<HomeDataRoom> getRooms() {

View File

@ -12,9 +12,12 @@
*/ */
package org.openhab.binding.netatmo.internal.api.dto; package org.openhab.binding.netatmo.internal.api.dto;
import java.time.DateTimeException;
import java.time.ZoneId;
import java.util.Optional; import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/** /**
* The {@link LocationEx} is the common interface for dto holding an extra location data * The {@link LocationEx} is the common interface for dto holding an extra location data
@ -26,5 +29,17 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
public interface LocationEx extends Location { public interface LocationEx extends Location {
Optional<String> getCountry(); Optional<String> getCountry();
Optional<String> getTimezone(); @Nullable
String getTimezone();
public default ZoneId getZoneId(ZoneId fallback) {
String local = getTimezone();
if (local != null) {
try {
return ZoneId.of(local);
} catch (DateTimeException ignore) {
}
}
return fallback;
}
} }

View File

@ -42,8 +42,8 @@ public class Place implements LocationEx {
} }
@Override @Override
public Optional<String> getTimezone() { public @Nullable String getTimezone() {
return Optional.ofNullable(timezone); return timezone;
} }
@Override @Override

View File

@ -13,6 +13,7 @@
package org.openhab.binding.netatmo.internal.handler; package org.openhab.binding.netatmo.internal.handler;
import java.time.Duration; import java.time.Duration;
import java.time.ZoneId;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -83,6 +84,8 @@ public interface CommonInterface {
@Nullable @Nullable
Bridge getBridge(); Bridge getBridge();
ZoneId getSystemTimeZone();
default @Nullable CommonInterface getBridgeHandler() { default @Nullable CommonInterface getBridgeHandler() {
Bridge bridge = getBridge(); Bridge bridge = getBridge();
return bridge != null && bridge.getHandler() instanceof DeviceHandler ? (DeviceHandler) bridge.getHandler() return bridge != null && bridge.getHandler() instanceof DeviceHandler ? (DeviceHandler) bridge.getHandler()

View File

@ -12,11 +12,13 @@
*/ */
package org.openhab.binding.netatmo.internal.handler; package org.openhab.binding.netatmo.internal.handler;
import java.time.ZoneId;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.netatmo.internal.handler.capability.CapabilityMap; import org.openhab.binding.netatmo.internal.handler.capability.CapabilityMap;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
@ -39,10 +41,12 @@ import org.slf4j.LoggerFactory;
@NonNullByDefault @NonNullByDefault
public class DeviceHandler extends BaseBridgeHandler implements CommonInterface { public class DeviceHandler extends BaseBridgeHandler implements CommonInterface {
private final Logger logger = LoggerFactory.getLogger(DeviceHandler.class); private final Logger logger = LoggerFactory.getLogger(DeviceHandler.class);
private CapabilityMap capabilities = new CapabilityMap(); private final CapabilityMap capabilities = new CapabilityMap();
private final TimeZoneProvider timeZoneProvider;
public DeviceHandler(Bridge bridge) { public DeviceHandler(Bridge bridge, TimeZoneProvider timeZoneProvider) {
super(bridge); super(bridge);
this.timeZoneProvider = timeZoneProvider;
} }
@Override @Override
@ -118,4 +122,9 @@ public class DeviceHandler extends BaseBridgeHandler implements CommonInterface
public ScheduledExecutorService getScheduler() { public ScheduledExecutorService getScheduler() {
return scheduler; return scheduler;
} }
@Override
public ZoneId getSystemTimeZone() {
return timeZoneProvider.getTimeZone();
}
} }

View File

@ -12,6 +12,7 @@
*/ */
package org.openhab.binding.netatmo.internal.handler; package org.openhab.binding.netatmo.internal.handler;
import java.time.ZoneId;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@ -20,6 +21,7 @@ import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.netatmo.internal.handler.capability.CapabilityMap; import org.openhab.binding.netatmo.internal.handler.capability.CapabilityMap;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
@ -43,10 +45,12 @@ import org.slf4j.LoggerFactory;
@NonNullByDefault @NonNullByDefault
public class ModuleHandler extends BaseThingHandler implements CommonInterface { public class ModuleHandler extends BaseThingHandler implements CommonInterface {
private final Logger logger = LoggerFactory.getLogger(ModuleHandler.class); private final Logger logger = LoggerFactory.getLogger(ModuleHandler.class);
private CapabilityMap capabilities = new CapabilityMap(); private final CapabilityMap capabilities = new CapabilityMap();
private final TimeZoneProvider timeZoneProvider;
public ModuleHandler(Thing thing) { public ModuleHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
super(thing); super(thing);
this.timeZoneProvider = timeZoneProvider;
} }
@Override @Override
@ -129,4 +133,9 @@ public class ModuleHandler extends BaseThingHandler implements CommonInterface {
public ScheduledExecutorService getScheduler() { public ScheduledExecutorService getScheduler() {
return scheduler; return scheduler;
} }
@Override
public ZoneId getSystemTimeZone() {
return timeZoneProvider.getTimeZone();
}
} }

View File

@ -47,7 +47,7 @@ public class DeviceCapability extends Capability {
newData.getPlace().ifPresent(place -> { newData.getPlace().ifPresent(place -> {
place.getCity().map(city -> properties.put(PROPERTY_CITY, city)); place.getCity().map(city -> properties.put(PROPERTY_CITY, city));
place.getCountry().map(country -> properties.put(PROPERTY_COUNTRY, country)); place.getCountry().map(country -> properties.put(PROPERTY_COUNTRY, country));
place.getTimezone().map(tz -> properties.put(PROPERTY_TIMEZONE, tz)); properties.put(PROPERTY_TIMEZONE, place.getZoneId(handler.getSystemTimeZone()).toString());
}); });
} }
if (!newData.hasFreshData(DATA_AGE_LIMIT_S)) { if (!newData.hasFreshData(DATA_AGE_LIMIT_S)) {

View File

@ -14,6 +14,7 @@ package org.openhab.binding.netatmo.internal.handler.capability;
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
import java.time.Duration;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
@ -48,7 +49,7 @@ public class EnergyCapability extends RestCapability<EnergyApi> {
private final Logger logger = LoggerFactory.getLogger(EnergyCapability.class); private final Logger logger = LoggerFactory.getLogger(EnergyCapability.class);
private final NetatmoDescriptionProvider descriptionProvider; private final NetatmoDescriptionProvider descriptionProvider;
private int setPointDefaultDuration = -1; private Duration setPointDefaultDuration = Duration.ofMinutes(120);
private String energyId = ""; private String energyId = "";
EnergyCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider) { EnergyCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider) {
@ -79,7 +80,7 @@ public class EnergyCapability extends RestCapability<EnergyApi> {
}); });
descriptionProvider.setStateOptions(new ChannelUID(thingUID, GROUP_ENERGY, CHANNEL_PLANNING), descriptionProvider.setStateOptions(new ChannelUID(thingUID, GROUP_ENERGY, CHANNEL_PLANNING),
energyData.getThermSchedules().stream().map(p -> new StateOption(p.getId(), p.getName())).toList()); energyData.getThermSchedules().stream().map(p -> new StateOption(p.getId(), p.getName())).toList());
setPointDefaultDuration = energyData.getThermSetpointDefaultDuration(); setPointDefaultDuration = energyData.getSetpointDefaultDuration();
} }
} }
@ -157,6 +158,10 @@ public class EnergyCapability extends RestCapability<EnergyApi> {
} }
private long setpointEndTimeFromNow() { private long setpointEndTimeFromNow() {
return ZonedDateTime.now().plusMinutes(setPointDefaultDuration).toEpochSecond(); return handler.getHomeCapability(HomeCapability.class).map(cap -> {
ZonedDateTime now = ZonedDateTime.now().plus(setPointDefaultDuration);
now = now.withZoneSameInstant(cap.zoneId);
return now.toEpochSecond();
}).orElse(-1l);
} }
} }

View File

@ -15,6 +15,7 @@ package org.openhab.binding.netatmo.internal.handler.capability;
import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*; import static org.openhab.binding.netatmo.internal.NetatmoBindingConstants.*;
import java.time.Duration; import java.time.Duration;
import java.time.ZoneId;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -44,12 +45,13 @@ import org.slf4j.LoggerFactory;
*/ */
@NonNullByDefault @NonNullByDefault
public class HomeCapability extends CacheCapability<HomeApi> { public class HomeCapability extends CacheCapability<HomeApi> {
private final Logger logger = LoggerFactory.getLogger(HomeCapability.class); private final Logger logger = LoggerFactory.getLogger(HomeCapability.class);
private final Set<FeatureArea> featureAreas = new HashSet<>(); private final Set<FeatureArea> featureAreas = new HashSet<>();
private final NetatmoDescriptionProvider descriptionProvider; private final NetatmoDescriptionProvider descriptionProvider;
private final Set<String> homeIds = new HashSet<>(3); private final Set<String> homeIds = new HashSet<>(3);
protected ZoneId zoneId = ZoneId.systemDefault();
public HomeCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider) { public HomeCapability(CommonInterface handler, NetatmoDescriptionProvider descriptionProvider) {
super(handler, Duration.ofSeconds(2), HomeApi.class); super(handler, Duration.ofSeconds(2), HomeApi.class);
this.descriptionProvider = descriptionProvider; this.descriptionProvider = descriptionProvider;
@ -88,7 +90,8 @@ public class HomeCapability extends CacheCapability<HomeApi> {
handler.removeChannels(thing.getChannelsOfGroup(GROUP_ENERGY)); handler.removeChannels(thing.getChannelsOfGroup(GROUP_ENERGY));
} }
home.getCountry().map(country -> properties.put(PROPERTY_COUNTRY, country)); home.getCountry().map(country -> properties.put(PROPERTY_COUNTRY, country));
home.getTimezone().map(tz -> properties.put(PROPERTY_TIMEZONE, tz)); zoneId = home.getZoneId(handler.getSystemTimeZone());
properties.put(PROPERTY_TIMEZONE, zoneId.toString());
properties.put(GROUP_LOCATION, ((Location) home).getLocation().toString()); properties.put(GROUP_LOCATION, ((Location) home).getLocation().toString());
properties.put(PROPERTY_FEATURE, properties.put(PROPERTY_FEATURE,
featureAreas.stream().map(FeatureArea::name).collect(Collectors.joining(","))); featureAreas.stream().map(FeatureArea::name).collect(Collectors.joining(",")));

View File

@ -55,7 +55,7 @@ public class EnergyChannelHelper extends ChannelHelper {
ThermProgram currentProgram = energyData.getActiveProgram(); ThermProgram currentProgram = energyData.getActiveProgram();
switch (channelId) { switch (channelId) {
case CHANNEL_SETPOINT_DURATION: case CHANNEL_SETPOINT_DURATION:
return toQuantityType(energyData.getThermSetpointDefaultDuration(), Units.MINUTE); return toQuantityType(energyData.getSetpointDefaultDuration().getSeconds(), Units.SECOND);
case CHANNEL_PLANNING: case CHANNEL_PLANNING:
return (currentProgram != null ? toStringType(currentProgram.getName()) : null); return (currentProgram != null ? toStringType(currentProgram.getName()) : null);
case CHANNEL_SETPOINT_END_TIME: case CHANNEL_SETPOINT_END_TIME: