[nikohomecontrol] Bug fixes and improvements to thermostats (#11963)

* Thermostat fixes and implement extra thermostat channels

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
This commit is contained in:
Mark Herwege 2022-06-15 13:01:22 +02:00 committed by GitHub
parent 3349cf4945
commit f8bbb2b5a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 215 additions and 116 deletions

View File

@ -237,19 +237,25 @@ OnOff, IncreaseDecrease and Percent command types are supported.
Note that sending an ON command will switch the dimmer to the value stored when last turning the dimmer off, or 100% depending on the configuration in the Niko Home Control Controller. Note that sending an ON command will switch the dimmer to the value stored when last turning the dimmer off, or 100% depending on the configuration in the Niko Home Control Controller.
This can be changed with the Niko Home Control programming software. This can be changed with the Niko Home Control programming software.
For thing type `blind` the supported channel is `rollershutter`. UpDown, StopMove and Percent command types are supported. For thing type `blind` the supported channel is `rollershutter`.
UpDown, StopMove and Percent command types are supported.
For thing type `thermostat` the supported channels are `measured`, `mode`, `setpoint`, `overruletime` and `demand`. For thing type `thermostat` the supported channels are `measured`, `heatingmode`, `mode`, `setpoint`, `overruletime`, `heatingdemand` and `demand`.
`measured` gives the current temperature in QuantityType<Temperature>, allowing for different temperature units. `measured` gives the current temperature in QuantityType<Temperature>, allowing for different temperature units.
This channel is read only. This channel is read only.
`mode` can be set and shows the current thermostat mode. `heatingmode` can be set and shows the current thermostat mode.
Allowed values are 0 (day), 1 (night), 2 (eco), 3 (off), 4 (cool), 5 (prog 1), 6 (prog 2), 7 (prog 3). Allowed values are Day, Night, Eco, Off, Cool, Prog1, Prog2, Prog3.
If mode is set, the `setpoint` temperature will return to its standard value from the mode. As an alternative to `heatingmode` and for backward compatibility, the advanced channel `mode` is provided.
`setpoint` can be set and shows the current thermostat setpoint value in QuantityType<Temperature>. This channel has the same meaning, but with numeric values (0=Day, 1=Night, 2=Eco, 3=Off, 4=Cool, 5=Prog1, 6=Prog2, 7=Prog3) instead of string values.
If `heatingmode` or `mode` is set, the `setpoint` temperature will return to the standard value for the mode as defined in Niko Home Control.
`setpoint` shows the current thermostat setpoint value in QuantityType<Temperature>.
When updating `setpoint`, it will overrule the temperature setpoint defined by the thermostat mode for `overruletime` duration. When updating `setpoint`, it will overrule the temperature setpoint defined by the thermostat mode for `overruletime` duration.
`overruletime` is used to set the total duration to apply the setpoint temperature set in the setpoint channel before the thermostat returns to the setting in its mode. `overruletime` is used to set the total duration to apply the setpoint temperature set in the setpoint channel before the thermostat returns to the setting from its mode.
`demand` is a number indicating of the system is actively heating/cooling. `heatingdemand` is a string indicating if the system is actively heating/cooling.
This channel will have value Heating, Cooling or None.
As an alternative to `heatingdemand`, the advanced channel `demand` is provided.
The value will be 1 for heating, -1 for cooling and 0 if not heating or cooling. The value will be 1 for heating, -1 for cooling and 0 if not heating or cooling.
`heatingdemand` and `demand` are read only channels.
Note that cooling in NHC I is set by the binding, and will incorrectly show cooling demand when the system does not have cooling capabilities. Note that cooling in NHC I is set by the binding, and will incorrectly show cooling demand when the system does not have cooling capabilities.
In NHC II, `measured` and `setpoint` temperatures will always report in 0.5°C increments due to a Niko Home Control II API restriction. In NHC II, `measured` and `setpoint` temperatures will always report in 0.5°C increments due to a Niko Home Control II API restriction.
@ -310,12 +316,12 @@ Switch AllOff {channel="nikohomecontrol:onOff:nhc1:1:button"}
Switch LivingRoom {channel="nikohomecontrol:onOff:nhc1:2:switch"} # Switch for onOff type action Switch LivingRoom {channel="nikohomecontrol:onOff:nhc1:2:switch"} # Switch for onOff type action
Dimmer TVRoom {channel="nikohomecontrol:dimmer:nhc1:3:brightness"} # Changing brightness dimmer type action Dimmer TVRoom {channel="nikohomecontrol:dimmer:nhc1:3:brightness"} # Changing brightness dimmer type action
Rollershutter Kitchen {channel="nikohomecontrol:blind:nhc1:4:rollershutter"} # Controlling rollershutter or blind type action Rollershutter Kitchen {channel="nikohomecontrol:blind:nhc1:4:rollershutter"} # Controlling rollershutter or blind type action
Number:Temperature CurTemperature "[%.1f °F]" {channel="nikohomecontrol:thermostat:nhc1:5:measured"} # Getting measured temperature from thermostat in °F, read only Number:Temperature CurTemperature "[%.1f °F]" {channel="nikohomecontrol:thermostat:nhc1:5:measured"} # Getting measured temperature from thermostat in °F, read only
Number ThermostatMode {channel="nikohomecontrol:thermostat:nhc1:5:mode"} # Get and set thermostat mode String ThermostatMode {channel="nikohomecontrol:thermostat:nhc1:5:heatingmode"} # Get and set thermostat mode
Number:Temperature SetTemperature "[%.1f °C]" {channel="nikohomecontrol:thermostat:nhc1:5:setpoint"} # Get and set target temperature in °C Number:Temperature SetTemperature "[%.1f °C]" {channel="nikohomecontrol:thermostat:nhc1:5:setpoint"} # Get and set target temperature in °C
Number OverruleDuration {channel="nikohomecontrol:thermostat:nhc1:5:overruletime"} # Get and set the overrule time Number OverruleDuration {channel="nikohomecontrol:thermostat:nhc1:5:overruletime"} # Get and set the overrule time
Number ThermostatDemand {channel="nikohomecontrol:thermostat:nhc1:5:demand"} # Get the current heating/cooling demand String ThermostatDemand {channel="nikohomecontrol:thermostat:nhc1:5:heatingdemand"} # Get the current heating/cooling demand
Number:Power CurPower "[%.0f W]" {channel="nikohomecontrol:energyMeter:nhc2:6:power"} # Get current power consumption Number:Power CurPower "[%.0f W]" {channel="nikohomecontrol:energyMeter:nhc2:6:power"} # Get current power consumption
``` ```
.sitemap: .sitemap:
@ -327,7 +333,7 @@ Slider item=TVRoom
Switch item=TVRoom # allows switching dimmer item off or on (with controller defined behavior) Switch item=TVRoom # allows switching dimmer item off or on (with controller defined behavior)
Rollershutter item=Kitchen Rollershutter item=Kitchen
Text item=CurTemperature Text item=CurTemperature
Selection item=ThermostatMode mappings=[0="day", 1="night", 2="eco", 3="off", 4="cool", 5="prog 1", 6="prog 2", 7="prog 3"] Selection item=ThermostatMode mappings=[Day="day", Night="night", Eco="eco", Off="off", Prog1="Away"]
Setpoint item=SetTemperature minValue=0 maxValue=30 Setpoint item=SetTemperature minValue=0 maxValue=30
Slider item=OverruleDuration minValue=0 maxValue=120 Slider item=OverruleDuration minValue=0 maxValue=120
Text item=Power Text item=Power

View File

@ -69,6 +69,8 @@ public class NikoHomeControlBindingConstants {
public static final String CHANNEL_OVERRULETIME = "overruletime"; public static final String CHANNEL_OVERRULETIME = "overruletime";
public static final String CHANNEL_MODE = "mode"; public static final String CHANNEL_MODE = "mode";
public static final String CHANNEL_DEMAND = "demand"; public static final String CHANNEL_DEMAND = "demand";
public static final String CHANNEL_HEATING_MODE = "heatingmode";
public static final String CHANNEL_HEATING_DEMAND = "heatingdemand";
public static final String CHANNEL_POWER = "power"; public static final String CHANNEL_POWER = "power";

View File

@ -21,6 +21,7 @@ import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlBridg
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlBridgeHandler2; import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlBridgeHandler2;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlEnergyMeterHandler; import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlEnergyMeterHandler;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlThermostatHandler; import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlThermostatHandler;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.net.NetworkAddressService; import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
@ -28,6 +29,7 @@ import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory; import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler; import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory; import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.Reference;
@ -42,7 +44,16 @@ import org.osgi.service.component.annotations.Reference;
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.nikohomecontrol") @Component(service = ThingHandlerFactory.class, configurationPid = "binding.nikohomecontrol")
public class NikoHomeControlHandlerFactory extends BaseThingHandlerFactory { public class NikoHomeControlHandlerFactory extends BaseThingHandlerFactory {
private @NonNullByDefault({}) NetworkAddressService networkAddressService; private final NetworkAddressService networkAddressService;
private final TimeZoneProvider timeZoneProvider;
@Activate
public NikoHomeControlHandlerFactory(final @Reference NetworkAddressService networkAddressService,
final @Reference TimeZoneProvider timeZoneProvider) {
super();
this.networkAddressService = networkAddressService;
this.timeZoneProvider = timeZoneProvider;
}
@Override @Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) { public boolean supportsThingType(ThingTypeUID thingTypeUID) {
@ -53,9 +64,9 @@ public class NikoHomeControlHandlerFactory extends BaseThingHandlerFactory {
protected @Nullable ThingHandler createHandler(Thing thing) { protected @Nullable ThingHandler createHandler(Thing thing) {
if (BRIDGE_THING_TYPES_UIDS.contains(thing.getThingTypeUID())) { if (BRIDGE_THING_TYPES_UIDS.contains(thing.getThingTypeUID())) {
if (BRIDGEII_THING_TYPE.equals(thing.getThingTypeUID())) { if (BRIDGEII_THING_TYPE.equals(thing.getThingTypeUID())) {
return new NikoHomeControlBridgeHandler2((Bridge) thing, networkAddressService); return new NikoHomeControlBridgeHandler2((Bridge) thing, networkAddressService, timeZoneProvider);
} else { } else {
return new NikoHomeControlBridgeHandler1((Bridge) thing); return new NikoHomeControlBridgeHandler1((Bridge) thing, timeZoneProvider);
} }
} else if (THING_TYPE_THERMOSTAT.equals(thing.getThingTypeUID())) { } else if (THING_TYPE_THERMOSTAT.equals(thing.getThingTypeUID())) {
return new NikoHomeControlThermostatHandler(thing); return new NikoHomeControlThermostatHandler(thing);
@ -67,13 +78,4 @@ public class NikoHomeControlHandlerFactory extends BaseThingHandlerFactory {
return null; return null;
} }
@Reference
protected void setNetworkAddressService(NetworkAddressService networkAddressService) {
this.networkAddressService = networkAddressService;
}
protected void unsetNetworkAddressService(NetworkAddressService networkAddressService) {
this.networkAddressService = null;
}
} }

View File

@ -16,6 +16,7 @@ import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindin
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.time.ZoneId;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@ -29,6 +30,7 @@ import org.openhab.binding.nikohomecontrol.internal.discovery.NikoHomeControlDis
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcControllerEvent; import org.openhab.binding.nikohomecontrol.internal.protocol.NhcControllerEvent;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication; import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.Configuration;
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.ThingStatus; import org.openhab.core.thing.ThingStatus;
@ -55,8 +57,11 @@ public abstract class NikoHomeControlBridgeHandler extends BaseBridgeHandler imp
private volatile @Nullable ScheduledFuture<?> refreshTimer; private volatile @Nullable ScheduledFuture<?> refreshTimer;
public NikoHomeControlBridgeHandler(Bridge nikoHomeControlBridge) { protected final TimeZoneProvider timeZoneProvider;
public NikoHomeControlBridgeHandler(Bridge nikoHomeControlBridge, TimeZoneProvider timeZoneProvider) {
super(nikoHomeControlBridge); super(nikoHomeControlBridge);
this.timeZoneProvider = timeZoneProvider;
} }
@Override @Override
@ -253,6 +258,11 @@ public abstract class NikoHomeControlBridgeHandler extends BaseBridgeHandler imp
return getConfig().as(NikoHomeControlBridgeConfig.class).port; return getConfig().as(NikoHomeControlBridgeConfig.class).port;
} }
@Override
public ZoneId getTimeZone() {
return timeZoneProvider.getTimeZone();
}
@Override @Override
public Collection<Class<? extends ThingHandlerService>> getServices() { public Collection<Class<? extends ThingHandlerService>> getServices() {
return Set.of(NikoHomeControlDiscoveryService.class); return Set.of(NikoHomeControlDiscoveryService.class);

View File

@ -20,6 +20,7 @@ import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc1.NikoHomeControlCommunication1; import org.openhab.binding.nikohomecontrol.internal.protocol.nhc1.NikoHomeControlCommunication1;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingStatusDetail;
@ -37,8 +38,8 @@ public class NikoHomeControlBridgeHandler1 extends NikoHomeControlBridgeHandler
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlBridgeHandler1.class); private final Logger logger = LoggerFactory.getLogger(NikoHomeControlBridgeHandler1.class);
public NikoHomeControlBridgeHandler1(Bridge nikoHomeControlBridge) { public NikoHomeControlBridgeHandler1(Bridge nikoHomeControlBridge, TimeZoneProvider timeZoneProvider) {
super(nikoHomeControlBridge); super(nikoHomeControlBridge, timeZoneProvider);
} }
@Override @Override

View File

@ -24,6 +24,7 @@ import java.util.NoSuchElementException;
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.nikohomecontrol.internal.protocol.nhc2.NikoHomeControlCommunication2; import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NikoHomeControlCommunication2;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.net.NetworkAddressService; import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatus;
@ -50,8 +51,9 @@ public class NikoHomeControlBridgeHandler2 extends NikoHomeControlBridgeHandler
NetworkAddressService networkAddressService; NetworkAddressService networkAddressService;
public NikoHomeControlBridgeHandler2(Bridge nikoHomeControlBridge, NetworkAddressService networkAddressService) { public NikoHomeControlBridgeHandler2(Bridge nikoHomeControlBridge, NetworkAddressService networkAddressService,
super(nikoHomeControlBridge); TimeZoneProvider timeZoneProvider) {
super(nikoHomeControlBridge, timeZoneProvider);
this.networkAddressService = networkAddressService; this.networkAddressService = networkAddressService;
} }

View File

@ -13,16 +13,16 @@
package org.openhab.binding.nikohomecontrol.internal.handler; package org.openhab.binding.nikohomecontrol.internal.handler;
import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.*; import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.*;
import static org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.*;
import static org.openhab.core.library.unit.SIUnits.CELSIUS; import static org.openhab.core.library.unit.SIUnits.CELSIUS;
import static org.openhab.core.types.RefreshType.REFRESH; import static org.openhab.core.types.RefreshType.REFRESH;
import java.math.BigDecimal;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.measure.quantity.Temperature;
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.nikohomecontrol.internal.protocol.NhcThermostat; import org.openhab.binding.nikohomecontrol.internal.protocol.NhcThermostat;
@ -31,6 +31,7 @@ import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlComm
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcThermostat2; import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcThermostat2;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
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;
@ -105,33 +106,39 @@ public class NikoHomeControlThermostatHandler extends BaseThingHandler implement
switch (channelUID.getId()) { switch (channelUID.getId()) {
case CHANNEL_MEASURED: case CHANNEL_MEASURED:
case CHANNEL_DEMAND: case CHANNEL_DEMAND:
case CHANNEL_HEATING_DEMAND:
updateStatus(ThingStatus.ONLINE); updateStatus(ThingStatus.ONLINE);
break; break;
case CHANNEL_MODE: case CHANNEL_MODE:
if (command instanceof DecimalType) { if (command instanceof DecimalType) {
nhcThermostat.executeMode(((DecimalType) command).intValue()); nhcThermostat.executeMode(((DecimalType) command).intValue());
} }
updateStatus(ThingStatus.ONLINE); updateStatus(ThingStatus.ONLINE);
break; break;
case CHANNEL_HEATING_MODE:
case CHANNEL_SETPOINT: if (command instanceof StringType) {
QuantityType<Temperature> setpoint = null; nhcThermostat.executeMode(command.toString());
if (command instanceof QuantityType) { }
setpoint = ((QuantityType<Temperature>) command).toUnit(CELSIUS); updateStatus(ThingStatus.ONLINE);
// Always set the new setpoint temperature as an overrule break;
// If no overrule time is given yet, set the overrule time to the configuration parameter case CHANNEL_SETPOINT:
int time = nhcThermostat.getOverruletime(); // Always set the new setpoint temperature as an overrule
if (time <= 0) { // If no overrule time is given yet, set the overrule time to the configuration parameter
time = overruleTime; int time = nhcThermostat.getOverruletime();
} if (time <= 0) {
if (setpoint != null) { time = overruleTime;
nhcThermostat.executeOverrule(Math.round(setpoint.floatValue() * 10), time); }
} if (command instanceof QuantityType<?>) {
QuantityType<?> setpoint = ((QuantityType<?>) command).toUnit(CELSIUS);
if (setpoint != null) {
nhcThermostat.executeOverrule(Math.round(setpoint.floatValue() * 10), time);
}
} else if (command instanceof DecimalType) {
BigDecimal setpoint = ((DecimalType) command).toBigDecimal();
nhcThermostat.executeOverrule(Math.round(setpoint.floatValue() * 10), time);
} }
updateStatus(ThingStatus.ONLINE); updateStatus(ThingStatus.ONLINE);
break; break;
case CHANNEL_OVERRULETIME: case CHANNEL_OVERRULETIME:
if (command instanceof DecimalType) { if (command instanceof DecimalType) {
int overruletime = ((DecimalType) command).intValue(); int overruletime = ((DecimalType) command).intValue();
@ -255,7 +262,7 @@ public class NikoHomeControlThermostatHandler extends BaseThingHandler implement
return; return;
} }
updateState(CHANNEL_MEASURED, new QuantityType<>(nhcThermostat.getMeasured() / 10.0, CELSIUS)); updateState(CHANNEL_MEASURED, new QuantityType<>(measured / 10.0, CELSIUS));
int overruletime = nhcThermostat.getRemainingOverruletime(); int overruletime = nhcThermostat.getRemainingOverruletime();
updateState(CHANNEL_OVERRULETIME, new DecimalType(overruletime)); updateState(CHANNEL_OVERRULETIME, new DecimalType(overruletime));
@ -271,8 +278,10 @@ public class NikoHomeControlThermostatHandler extends BaseThingHandler implement
} }
updateState(CHANNEL_MODE, new DecimalType(mode)); updateState(CHANNEL_MODE, new DecimalType(mode));
updateState(CHANNEL_HEATING_MODE, new StringType(THERMOSTATMODES[mode]));
updateState(CHANNEL_DEMAND, new DecimalType(demand)); updateState(CHANNEL_DEMAND, new DecimalType(demand));
updateState(CHANNEL_HEATING_DEMAND, new StringType(THERMOSTATDEMAND[Math.abs(demand) <= 1 ? (demand + 1) : 0]));
updateStatus(ThingStatus.ONLINE); updateStatus(ThingStatus.ONLINE);
} }
@ -286,14 +295,14 @@ public class NikoHomeControlThermostatHandler extends BaseThingHandler implement
private void scheduleRefreshOverruletime(NhcThermostat nhcThermostat) { private void scheduleRefreshOverruletime(NhcThermostat nhcThermostat) {
cancelRefreshTimer(); cancelRefreshTimer();
if (nhcThermostat.getRemainingOverruletime() <= 0) { if (nhcThermostat.getRemainingOverruletime() == 0) {
return; return;
} }
refreshTimer = scheduler.scheduleWithFixedDelay(() -> { refreshTimer = scheduler.scheduleWithFixedDelay(() -> {
int remainingTime = nhcThermostat.getRemainingOverruletime(); int remainingTime = nhcThermostat.getRemainingOverruletime();
updateState(CHANNEL_OVERRULETIME, new DecimalType(remainingTime)); updateState(CHANNEL_OVERRULETIME, new DecimalType(remainingTime));
if (remainingTime <= 0) { if (remainingTime == 0) {
cancelRefreshTimer(); cancelRefreshTimer();
} }
}, 1, 1, TimeUnit.MINUTES); }, 1, 1, TimeUnit.MINUTES);

View File

@ -13,6 +13,7 @@
package org.openhab.binding.nikohomecontrol.internal.protocol; package org.openhab.binding.nikohomecontrol.internal.protocol;
import java.net.InetAddress; import java.net.InetAddress;
import java.time.ZoneId;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
@ -64,6 +65,13 @@ public interface NhcControllerEvent {
return ""; return "";
} }
/**
* Get the time zone ID of the Niko Home Control system.
*
* @return the zone ID
*/
public ZoneId getTimeZone();
/** /**
* Called to indicate the connection with the Niko Home Control Controller is offline. * Called to indicate the connection with the Niko Home Control Controller is offline.
* *

View File

@ -20,10 +20,8 @@ import org.slf4j.LoggerFactory;
/** /**
* The {@link NhcEnergyMeter} class represents the energyMeters metering Niko Home Control communication object. It * The {@link NhcEnergyMeter} class represents the energyMeters metering Niko Home Control communication object. It
* contains all * contains all fields representing a Niko Home Control energyMeters meter and has methods to receive energyMeters usage
* fields representing a Niko Home Control energyMeters meter and has methods to receive energyMeters usage information. * information. A specific implementation is {@link NhcEnergyMeter2}.
* A specific
* implementation is {@link NhcEnergyMeter2}.
* *
* @author Mark Herwege - Initial Contribution * @author Mark Herwege - Initial Contribution
*/ */

View File

@ -12,8 +12,12 @@
*/ */
package org.openhab.binding.nikohomecontrol.internal.protocol; package org.openhab.binding.nikohomecontrol.internal.protocol;
import static org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.THERMOSTATMODES;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
@ -47,7 +51,7 @@ public abstract class NhcThermostat {
protected volatile int ecosave; protected volatile int ecosave;
protected volatile int demand; protected volatile int demand;
private @Nullable LocalDateTime overruleStart; private @Nullable volatile ZonedDateTime overruleStart;
private @Nullable NhcThermostatEvent eventHandler; private @Nullable NhcThermostatEvent eventHandler;
@ -84,33 +88,6 @@ public abstract class NhcThermostat {
updateChannels(); updateChannels();
} }
/**
* Update overrule values of the thermostat without touching the thermostat definition (id, name and location) and
* without changing the ThingHandler callback.
*
* @param overrule the overrule temperature in 0.1°C multiples
* @param overruletime in minutes
*/
public void updateState(int overrule, int overruletime) {
setOverrule(overrule);
setOverruletime(overruletime);
updateChannels();
}
/**
* Update overrule values of the thermostat without touching the thermostat definition (id, name and location) and
* without changing the ThingHandler callback.
*
* @param overrule the overrule temperature in 0.1°C multiples
* @param overruletime in minutes
*/
public void updateState(int mode) {
setMode(mode);
updateChannels();
}
/** /**
* Method called when thermostat is removed from the Niko Home Control Controller. * Method called when thermostat is removed from the Niko Home Control Controller.
*/ */
@ -249,6 +226,9 @@ public abstract class NhcThermostat {
private void setOverrule(int overrule) { private void setOverrule(int overrule) {
this.overrule = overrule; this.overrule = overrule;
if (overrule <= 0) {
stopOverrule();
}
} }
/** /**
@ -266,12 +246,14 @@ public abstract class NhcThermostat {
* @param overruletime the overruletime in minutes * @param overruletime the overruletime in minutes
*/ */
private void setOverruletime(int overruletime) { private void setOverruletime(int overruletime) {
if (overruletime <= 0) { if (overruletime != this.overruletime) {
stopOverrule(); if (overruletime <= 0) {
} else if (overruletime != this.overruletime) { stopOverrule();
startOverrule(); } else {
startOverrule();
}
this.overruletime = overruletime;
} }
this.overruletime = overruletime;
} }
/** /**
@ -289,7 +271,7 @@ public abstract class NhcThermostat {
} }
/** /**
* @return the heating/cooling demand: 0 if no demand, >0 if heating, <0 if cooling * @return the heating/cooling demand: 0 if no demand, 1 if heating, -1 if cooling
*/ */
public int getDemand() { public int getDemand() {
return demand; return demand;
@ -310,6 +292,20 @@ public abstract class NhcThermostat {
*/ */
public abstract void executeMode(int mode); public abstract void executeMode(int mode);
/**
* Sends thermostat mode to Niko Home Control.
*
* @param mode allowed values are Day, Night, Eco, Off, Cool, Prog1, Prog2, Prog3
*/
public void executeMode(String mode) {
int intMode = Arrays.asList(THERMOSTATMODES).indexOf(mode);
if (intMode < 0) { // if not recognized, default to Day
intMode = 0;
logger.debug("Thermostat mode {} not recognized, default to Day mode", mode);
}
executeMode(intMode);
};
/** /**
* Sends thermostat setpoint to Niko Home Control. This method is implemented in {@link NhcThermostat1} and * Sends thermostat setpoint to Niko Home Control. This method is implemented in {@link NhcThermostat1} and
* {@link NhcThermostat2}. * {@link NhcThermostat2}.
@ -320,22 +316,24 @@ public abstract class NhcThermostat {
public abstract void executeOverrule(int overrule, int overruletime); public abstract void executeOverrule(int overrule, int overruletime);
/** /**
* @return remaining overrule time in minutes * @return remaining overrule time in minutes, 0 or positive
*/ */
public int getRemainingOverruletime() { public int getRemainingOverruletime() {
if (overruleStart == null) { int remainingTime = 0;
return 0; if (overruleStart != null) {
} else {
// overruletime time max 23h59min, therefore can safely cast to int // overruletime time max 23h59min, therefore can safely cast to int
return overruletime - (int) ChronoUnit.MINUTES.between(overruleStart, LocalDateTime.now()); remainingTime = Math.max(0, overruletime - (int) ChronoUnit.MINUTES.between(overruleStart,
LocalDateTime.now().atZone(nhcComm.getTimeZone())));
} }
logger.trace("Getting remaining overrule time, remaining: {}", remainingTime);
return remainingTime;
} }
/** /**
* Start a new overrule, this method is used to be able to calculate the remaining overrule time * Start a new overrule, this method is used to be able to calculate the remaining overrule time
*/ */
private void startOverrule() { private void startOverrule() {
overruleStart = LocalDateTime.now(); overruleStart = LocalDateTime.now().atZone(nhcComm.getTimeZone());
} }
/** /**
@ -343,5 +341,7 @@ public abstract class NhcThermostat {
*/ */
private void stopOverrule() { private void stopOverrule() {
overruleStart = null; overruleStart = null;
overruletime = 0;
overrule = 0;
} }
} }

View File

@ -12,6 +12,7 @@
*/ */
package org.openhab.binding.nikohomecontrol.internal.protocol; package org.openhab.binding.nikohomecontrol.internal.protocol;
import java.time.ZoneId;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
@ -54,7 +55,7 @@ public abstract class NikoHomeControlCommunication {
// restart attempts // restart attempts
private volatile int delay = 0; private volatile int delay = 0;
private volatile int attempt = 0; private volatile int attempt = 0;
protected @Nullable ScheduledFuture<?> scheduledRestart = null; protected volatile @Nullable ScheduledFuture<?> scheduledRestart = null;
protected NikoHomeControlCommunication(NhcControllerEvent handler, ScheduledExecutorService scheduler) { protected NikoHomeControlCommunication(NhcControllerEvent handler, ScheduledExecutorService scheduler) {
this.handler = handler; this.handler = handler;
@ -137,6 +138,15 @@ public abstract class NikoHomeControlCommunication {
*/ */
public abstract boolean communicationActive(); public abstract boolean communicationActive();
/**
* Return the timezone for the system.
*
* @return zoneId
*/
public ZoneId getTimeZone() {
return handler.getTimeZone();
}
/** /**
* Return all actions in the Niko Home Control Controller. * Return all actions in the Niko Home Control Controller.
* *

View File

@ -47,4 +47,5 @@ public class NikoHomeControlConstants {
// NhcII thermostat modes // NhcII thermostat modes
public static final String[] THERMOSTATMODES = { "Day", "Night", "Eco", "Off", "Cool", "Prog1", "Prog2", "Prog3" }; public static final String[] THERMOSTATMODES = { "Day", "Night", "Eco", "Off", "Cool", "Prog1", "Prog2", "Prog3" };
public static final String[] THERMOSTATDEMAND = { "Cooling", "None", "Heating" };
} }

View File

@ -569,11 +569,11 @@ public class NikoHomeControlCommunication2 extends NikoHomeControlCommunication
int measured = ambientTemperatureProperty.orElse(thermostat.getMeasured()); int measured = ambientTemperatureProperty.orElse(thermostat.getMeasured());
int setpoint = setpointTemperatureProperty.orElse(thermostat.getSetpoint()); int setpoint = setpointTemperatureProperty.orElse(thermostat.getSetpoint());
int overrule = thermostat.getOverrule(); int overrule = 0;
int overruletime = thermostat.getRemainingOverruletime(); int overruletime = 0;
if (overruleActiveProperty.orElse(false)) { if (overruleActiveProperty.orElse(true)) {
overrule = overruleSetpointProperty.orElse(0); overrule = overruleSetpointProperty.orElse(thermostat.getOverrule());
overruletime = overruleTimeProperty.orElse(0); overruletime = overruleTimeProperty.orElse(thermostat.getRemainingOverruletime());
} }
int ecosave = thermostat.getEcosave(); int ecosave = thermostat.getEcosave();
@ -618,7 +618,7 @@ public class NikoHomeControlCommunication2 extends NikoHomeControlCommunication
energyMeter.setPower(power); energyMeter.setPower(power);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
energyMeter.setPower(null); energyMeter.setPower(null);
logger.trace("received empty energy meter {} power reading", energyMeter.getId()); logger.trace("wrong format in energy meter {} power reading", energyMeter.getId());
} }
} }

View File

@ -83,14 +83,20 @@ channelOverruletimeDescription = Time duration for overruling thermostat target
channelModeLabel = Mode channelModeLabel = Mode
channelModeDescription = Thermostat mode channelModeDescription = Thermostat mode
channelModeOption0 = day channelModeOption0 = Day
channelModeOption1 = night channelModeOption1 = Night
channelModeOption2 = eco channelModeOption2 = Eco
channelModeOption3 = off channelModeOption3 = Off
channelModeOption4 = cool channelModeOption4 = Cool
channelModeOption5 = prog 1 channelModeOption5 = Program 1
channelModeOption6 = prog 2 channelModeOption6 = Program 2
channelModeOption7 = prog 3 channelModeOption7 = Program 3
channelDemandLabel = Demand
channelDemandDescription = Heating/cooling demand
channelDemand-1 = Cooling
channelDemand0 = None
channelDemand1 = Heating
channelPowerLabel = Power channelPowerLabel = Power
channelPowerDescription = Momentary power consumption/production (positive is consumption) channelPowerDescription = Momentary power consumption/production (positive is consumption)

View File

@ -161,13 +161,16 @@
<bridge-type-ref id="bridge"/> <bridge-type-ref id="bridge"/>
<bridge-type-ref id="bridge2"/> <bridge-type-ref id="bridge2"/>
</supported-bridge-type-refs> </supported-bridge-type-refs>
<label>@text/ThermostatLabel</label> <label>@text/thermostatLabel</label>
<description>@text/ThermostatDescription</description> <description>@text/thermostatDescription</description>
<channels> <channels>
<channel id="measured" typeId="measured"/> <channel id="measured" typeId="measured"/>
<channel id="heatingmode" typeId="heatingmode"/>
<channel id="mode" typeId="mode"/> <channel id="mode" typeId="mode"/>
<channel id="setpoint" typeId="setpoint"/> <channel id="setpoint" typeId="setpoint"/>
<channel id="overruletime" typeId="overruletime"/> <channel id="overruletime" typeId="overruletime"/>
<channel id="heatingdemand" typeId="heatingdemand"/>
<channel id="demand" typeId="demand"/>
</channels> </channels>
<config-description> <config-description>
<parameter name="thermostatId" type="text" required="true"> <parameter name="thermostatId" type="text" required="true">
@ -244,11 +247,27 @@
<category>Number</category> <category>Number</category>
<state min="0" max="1440" step="5"/> <state min="0" max="1440" step="5"/>
</channel-type> </channel-type>
<channel-type id="mode"> <channel-type id="heatingmode">
<item-type>String</item-type>
<label>@text/channelModeLabel</label>
<description>@text/channelModeDescription</description>
<state>
<options>
<option value="Day">@text/channelModeOption0</option>
<option value="Night">@text/channelModeOption1</option>
<option value="Eco">@text/channelModeOption2</option>
<option value="Off">@text/channelModeOption3</option>
<option value="Cool">@text/channelModeOption4</option>
<option value="Prog1">@text/channelModeOption5</option>
<option value="Prog2">@text/channelModeOption6</option>
<option value="Prog3">@text/channelModeOption7</option>
</options>
</state>
</channel-type>
<channel-type id="mode" advanced="true">
<item-type>Number</item-type> <item-type>Number</item-type>
<label>@text/channelModeLabel</label> <label>@text/channelModeLabel</label>
<description>@text/channelModeDescription</description> <description>@text/channelModeDescription</description>
<category>Number</category>
<state> <state>
<options> <options>
<option value="0">@text/channelModeOption0</option> <option value="0">@text/channelModeOption0</option>
@ -262,6 +281,31 @@
</options> </options>
</state> </state>
</channel-type> </channel-type>
<channel-type id="heatingdemand">
<item-type>String</item-type>
<label>@text/channelDemandLabel</label>
<description>@text/channelDemandDescription</description>
<state readOnly="true">
<options>
<option value="Cooling">@text/channelDemand-1</option>
<option value="None">@text/channelDemand0</option>
<option value="Heating">@text/channelDemand1</option>
</options>
</state>
</channel-type>
<channel-type id="demand" advanced="true">
<item-type>Number</item-type>
<label>@text/channelDemandLabel</label>
<description>@text/channelDemandDescription</description>
<state readOnly="true">
<options>
<option value="-1">@text/channelDemand-1</option>
<option value="0">@text/channelDemand0</option>
<option value="1">@text/channelDemand1</option>
</options>
</state>
</channel-type>
<channel-type id="power"> <channel-type id="power">
<item-type>Number:Power</item-type> <item-type>Number:Power</item-type>
<label>@text/channelPowerLabel</label> <label>@text/channelPowerLabel</label>