2 * Copyright (c) 2010-2023 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.avmfritz.internal.handler;
15 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.BINDING_ID;
16 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_ACTUALTEMP;
17 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_BATTERY;
18 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_BATTERY_LOW;
19 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_BRIGHTNESS;
20 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_COLOR;
21 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_COLORTEMPERATURE;
22 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_COLORTEMPERATURE_ABS;
23 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_COMFORTTEMP;
24 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_CONTACT_STATE;
25 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_DEVICE_LOCKED;
26 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_ECOTEMP;
27 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_ENERGY;
28 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_HUMIDITY;
29 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_LAST_CHANGE;
30 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_LOCKED;
31 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_MODE;
32 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_NEXTTEMP;
33 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_NEXT_CHANGE;
34 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_OBSTRUCTION_ALARM;
35 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_ON_OFF;
36 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_OUTLET;
37 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_POWER;
38 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_RADIATOR_MODE;
39 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_ROLLERSHUTTER;
40 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_SETTEMP;
41 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_TEMPERATURE;
42 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_TEMPERATURE_ALARM;
43 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CHANNEL_VOLTAGE;
44 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.CONFIG_CHANNEL_TEMP_OFFSET;
45 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.MODE_BOOST;
46 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.MODE_COMFORT;
47 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.MODE_ECO;
48 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.MODE_OFF;
49 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.MODE_ON;
50 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.MODE_UNKNOWN;
51 import static org.openhab.binding.avmfritz.internal.AVMFritzBindingConstants.MODE_WINDOW_OPEN;
52 import static org.openhab.binding.avmfritz.internal.dto.HeatingModel.TEMP_FRITZ_MAX;
53 import static org.openhab.binding.avmfritz.internal.dto.HeatingModel.TEMP_FRITZ_OFF;
54 import static org.openhab.binding.avmfritz.internal.dto.HeatingModel.TEMP_FRITZ_ON;
55 import static org.openhab.binding.avmfritz.internal.dto.HeatingModel.TEMP_FRITZ_UNDEFINED;
56 import static org.openhab.binding.avmfritz.internal.dto.HeatingModel.fromCelsius;
57 import static org.openhab.binding.avmfritz.internal.dto.HeatingModel.normalizeCelsius;
58 import static org.openhab.binding.avmfritz.internal.dto.HeatingModel.toCelsius;
60 import java.math.BigDecimal;
61 import java.time.Instant;
62 import java.time.ZoneId;
63 import java.time.ZonedDateTime;
66 import javax.measure.quantity.Temperature;
68 import org.eclipse.jdt.annotation.NonNullByDefault;
69 import org.eclipse.jdt.annotation.Nullable;
70 import org.openhab.binding.avmfritz.internal.config.AVMFritzDeviceConfiguration;
71 import org.openhab.binding.avmfritz.internal.dto.AVMFritzBaseModel;
72 import org.openhab.binding.avmfritz.internal.dto.AlertModel;
73 import org.openhab.binding.avmfritz.internal.dto.BatteryModel;
74 import org.openhab.binding.avmfritz.internal.dto.ColorControlModel;
75 import org.openhab.binding.avmfritz.internal.dto.DeviceModel;
76 import org.openhab.binding.avmfritz.internal.dto.HeatingModel;
77 import org.openhab.binding.avmfritz.internal.dto.HeatingModel.NextChangeModel;
78 import org.openhab.binding.avmfritz.internal.dto.HumidityModel;
79 import org.openhab.binding.avmfritz.internal.dto.LevelControlModel;
80 import org.openhab.binding.avmfritz.internal.dto.PowerMeterModel;
81 import org.openhab.binding.avmfritz.internal.dto.SimpleOnOffModel;
82 import org.openhab.binding.avmfritz.internal.dto.SwitchModel;
83 import org.openhab.binding.avmfritz.internal.dto.TemperatureModel;
84 import org.openhab.binding.avmfritz.internal.hardware.FritzAhaStatusListener;
85 import org.openhab.binding.avmfritz.internal.hardware.FritzAhaWebInterface;
86 import org.openhab.binding.avmfritz.internal.hardware.callbacks.FritzAhaSetBlindTargetCallback.BlindCommand;
87 import org.openhab.core.config.core.Configuration;
88 import org.openhab.core.library.types.DateTimeType;
89 import org.openhab.core.library.types.DecimalType;
90 import org.openhab.core.library.types.HSBType;
91 import org.openhab.core.library.types.IncreaseDecreaseType;
92 import org.openhab.core.library.types.OnOffType;
93 import org.openhab.core.library.types.OpenClosedType;
94 import org.openhab.core.library.types.PercentType;
95 import org.openhab.core.library.types.QuantityType;
96 import org.openhab.core.library.types.StopMoveType;
97 import org.openhab.core.library.types.StringType;
98 import org.openhab.core.library.types.UpDownType;
99 import org.openhab.core.library.unit.SIUnits;
100 import org.openhab.core.library.unit.Units;
101 import org.openhab.core.thing.Bridge;
102 import org.openhab.core.thing.Channel;
103 import org.openhab.core.thing.ChannelUID;
104 import org.openhab.core.thing.DefaultSystemChannelTypeProvider;
105 import org.openhab.core.thing.Thing;
106 import org.openhab.core.thing.ThingStatus;
107 import org.openhab.core.thing.ThingStatusDetail;
108 import org.openhab.core.thing.ThingUID;
109 import org.openhab.core.thing.binding.BaseThingHandler;
110 import org.openhab.core.thing.binding.BridgeHandler;
111 import org.openhab.core.thing.binding.ThingHandlerCallback;
112 import org.openhab.core.thing.type.ChannelTypeUID;
113 import org.openhab.core.types.Command;
114 import org.openhab.core.types.RefreshType;
115 import org.openhab.core.types.State;
116 import org.openhab.core.types.UnDefType;
117 import org.slf4j.Logger;
118 import org.slf4j.LoggerFactory;
121 * Abstract handler for a FRITZ! thing. Handles commands, which are sent to one of the channels.
123 * @author Robert Bausdorf - Initial contribution
124 * @author Christoph Weitkamp - Added support for AVM FRITZ!DECT 300 and Comet DECT
125 * @author Christoph Weitkamp - Added support for groups
126 * @author Ulrich Mertin - Added support for HAN-FUN blinds
127 * @author Christoph Sommer - Added support for color temperature
128 * @author Tobias Lange - Added abs color temperature and fixed on/off behavior of light blub
131 public abstract class AVMFritzBaseThingHandler extends BaseThingHandler implements FritzAhaStatusListener {
133 private final Logger logger = LoggerFactory.getLogger(AVMFritzBaseThingHandler.class);
136 * keeps track of the current state for handling of increase/decrease
138 private AVMFritzBaseModel currentDevice = new DeviceModel();
139 private @Nullable String identifier;
144 * @param thing Thing object representing a FRITZ! device
146 public AVMFritzBaseThingHandler(Thing thing) {
151 public void initialize() {
152 final AVMFritzDeviceConfiguration config = getConfigAs(AVMFritzDeviceConfiguration.class);
153 final String newIdentifier = config.ain;
154 if (newIdentifier == null || newIdentifier.isBlank()) {
155 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
156 "The 'ain' parameter must be configured.");
158 this.identifier = newIdentifier;
159 updateStatus(ThingStatus.UNKNOWN);
164 public void onDeviceAdded(AVMFritzBaseModel device) {
169 public void onDeviceUpdated(ThingUID thingUID, AVMFritzBaseModel device) {
170 if (thing.getUID().equals(thingUID)) {
171 logger.debug("Update thing '{}' with device model: {}", thingUID, device);
172 if (device.getPresent() == 1) {
173 updateStatus(ThingStatus.ONLINE);
175 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Device not present");
177 currentDevice = device;
179 updateProperties(device, editProperties());
181 if (device.isPowermeter()) {
182 updatePowermeter(device.getPowermeter());
184 if (device.isSwitchableOutlet()) {
185 updateSwitchableOutlet(device.getSwitch());
187 if (device.isHeatingThermostat()) {
188 updateHeatingThermostat(device.getHkr());
190 if (device instanceof DeviceModel) {
191 DeviceModel deviceModel = (DeviceModel) device;
192 if (deviceModel.isTemperatureSensor()) {
193 updateTemperatureSensor(deviceModel.getTemperature());
195 if (deviceModel.isHumiditySensor()) {
196 updateHumiditySensor(deviceModel.getHumidity());
198 if (deviceModel.isHANFUNAlarmSensor()) {
199 if (deviceModel.isHANFUNBlinds()) {
200 updateHANFUNBlindsAlarmSensor(deviceModel.getAlert());
202 updateHANFUNAlarmSensor(deviceModel.getAlert());
205 if (deviceModel.isHANFUNBlinds()) {
206 updateLevelControl(deviceModel.getLevelControlModel());
207 } else if (deviceModel.isColorLight()) {
208 updateColorLight(deviceModel.getColorControlModel(), deviceModel.getLevelControlModel(),
209 deviceModel.getSimpleOnOffUnit());
210 } else if (deviceModel.isDimmableLight() && !deviceModel.isHANFUNBlinds()) {
211 updateDimmableLight(deviceModel.getLevelControlModel());
212 } else if (deviceModel.isHANFUNUnit() && deviceModel.isHANFUNOnOff()) {
213 updateSimpleOnOffUnit(deviceModel.getSimpleOnOffUnit());
219 private void updateHANFUNAlarmSensor(@Nullable AlertModel alertModel) {
220 if (alertModel != null) {
221 updateThingChannelState(CHANNEL_CONTACT_STATE,
222 AlertModel.ON.equals(alertModel.getState()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
226 private void updateHANFUNBlindsAlarmSensor(@Nullable AlertModel alertModel) {
227 if (alertModel != null) {
228 updateThingChannelState(CHANNEL_OBSTRUCTION_ALARM,
229 OnOffType.from(alertModel.hasObstructionAlarmOccurred()));
230 updateThingChannelState(CHANNEL_TEMPERATURE_ALARM, OnOffType.from(alertModel.hasTemperaturAlarmOccurred()));
231 if (alertModel.hasUnknownAlarmOccurred()) {
232 logger.warn("Unknown blinds alarm {}", alertModel.getState());
237 protected void updateTemperatureSensor(@Nullable TemperatureModel temperatureModel) {
238 if (temperatureModel != null) {
239 updateThingChannelState(CHANNEL_TEMPERATURE,
240 new QuantityType<>(temperatureModel.getCelsius(), SIUnits.CELSIUS));
241 updateThingChannelConfiguration(CHANNEL_TEMPERATURE, CONFIG_CHANNEL_TEMP_OFFSET,
242 temperatureModel.getOffset());
246 protected void updateHumiditySensor(@Nullable HumidityModel humidityModel) {
247 if (humidityModel != null) {
248 updateThingChannelState(CHANNEL_HUMIDITY,
249 new QuantityType<>(humidityModel.getRelativeHumidity(), Units.PERCENT));
253 protected void updateLevelControl(@Nullable LevelControlModel levelControlModel) {
254 if (levelControlModel != null) {
255 updateThingChannelState(CHANNEL_ROLLERSHUTTER, new PercentType(levelControlModel.getLevelPercentage()));
259 private void updateDimmableLight(@Nullable LevelControlModel levelControlModel) {
260 if (levelControlModel != null) {
261 updateThingChannelState(CHANNEL_BRIGHTNESS, new PercentType(levelControlModel.getLevelPercentage()));
265 private void updateColorLight(@Nullable ColorControlModel colorControlModel,
266 @Nullable LevelControlModel levelControlModel, @Nullable SimpleOnOffModel simpleOnOff) {
267 if (colorControlModel != null && levelControlModel != null && simpleOnOff != null) {
268 DecimalType hue = new DecimalType(colorControlModel.hue);
269 PercentType saturation = ColorControlModel.toPercent(colorControlModel.saturation);
270 PercentType brightness;
271 if (simpleOnOff.state) {
272 brightness = new PercentType(levelControlModel.getLevelPercentage());
274 brightness = PercentType.ZERO;
276 updateThingChannelState(CHANNEL_COLOR, new HSBType(hue, saturation, brightness));
278 if (colorControlModel.currentMode == 4) {
279 int temperature = colorControlModel.temperature;
280 int tempMired = 1000000 / temperature;
281 int tempMinMired = 1000000 / 2700;
282 int tempMaxMired = 1000000 / 6500;
283 int pct = (tempMired - tempMinMired) * 100 / (tempMaxMired - tempMinMired);
284 updateThingChannelState(CHANNEL_COLORTEMPERATURE, new PercentType(pct));
285 updateThingChannelState(CHANNEL_COLORTEMPERATURE_ABS,
286 new QuantityType<>(BigDecimal.valueOf(temperature), Units.KELVIN));
291 private void updateHeatingThermostat(@Nullable HeatingModel heatingModel) {
292 if (heatingModel != null) {
293 updateThingChannelState(CHANNEL_MODE, new StringType(heatingModel.getMode()));
294 updateThingChannelState(CHANNEL_LOCKED,
295 BigDecimal.ZERO.equals(heatingModel.getLock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
296 updateThingChannelState(CHANNEL_DEVICE_LOCKED,
297 BigDecimal.ZERO.equals(heatingModel.getDevicelock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
298 updateThingChannelState(CHANNEL_ACTUALTEMP,
299 new QuantityType<>(toCelsius(heatingModel.getTist()), SIUnits.CELSIUS));
300 updateThingChannelState(CHANNEL_SETTEMP,
301 new QuantityType<>(toCelsius(heatingModel.getTsoll()), SIUnits.CELSIUS));
302 updateThingChannelState(CHANNEL_ECOTEMP,
303 new QuantityType<>(toCelsius(heatingModel.getAbsenk()), SIUnits.CELSIUS));
304 updateThingChannelState(CHANNEL_COMFORTTEMP,
305 new QuantityType<>(toCelsius(heatingModel.getKomfort()), SIUnits.CELSIUS));
306 updateThingChannelState(CHANNEL_RADIATOR_MODE, new StringType(heatingModel.getRadiatorMode()));
307 NextChangeModel nextChange = heatingModel.getNextchange();
308 if (nextChange != null) {
309 int endPeriod = nextChange.getEndperiod();
310 updateThingChannelState(CHANNEL_NEXT_CHANGE, endPeriod == 0 ? UnDefType.UNDEF
312 ZonedDateTime.ofInstant(Instant.ofEpochSecond(endPeriod), ZoneId.systemDefault())));
313 BigDecimal nextTemperature = nextChange.getTchange();
314 updateThingChannelState(CHANNEL_NEXTTEMP, TEMP_FRITZ_UNDEFINED.equals(nextTemperature) ? UnDefType.UNDEF
315 : new QuantityType<>(toCelsius(nextTemperature), SIUnits.CELSIUS));
317 updateBattery(heatingModel);
321 protected void updateBattery(BatteryModel batteryModel) {
322 BigDecimal batteryLevel = batteryModel.getBattery();
323 updateThingChannelState(CHANNEL_BATTERY,
324 batteryLevel == null ? UnDefType.UNDEF : new DecimalType(batteryLevel));
325 BigDecimal lowBattery = batteryModel.getBatterylow();
326 if (lowBattery == null) {
327 updateThingChannelState(CHANNEL_BATTERY_LOW, UnDefType.UNDEF);
329 updateThingChannelState(CHANNEL_BATTERY_LOW, OnOffType.from(BatteryModel.BATTERY_ON.equals(lowBattery)));
333 private void updateSimpleOnOffUnit(@Nullable SimpleOnOffModel simpleOnOffUnit) {
334 if (simpleOnOffUnit != null) {
335 updateThingChannelState(CHANNEL_ON_OFF, OnOffType.from(simpleOnOffUnit.state));
339 private void updateSwitchableOutlet(@Nullable SwitchModel switchModel) {
340 if (switchModel != null) {
341 updateThingChannelState(CHANNEL_MODE, new StringType(switchModel.getMode()));
342 updateThingChannelState(CHANNEL_LOCKED,
343 BigDecimal.ZERO.equals(switchModel.getLock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
344 updateThingChannelState(CHANNEL_DEVICE_LOCKED,
345 BigDecimal.ZERO.equals(switchModel.getDevicelock()) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
346 BigDecimal state = switchModel.getState();
348 updateThingChannelState(CHANNEL_OUTLET, UnDefType.UNDEF);
350 updateThingChannelState(CHANNEL_OUTLET, OnOffType.from(SwitchModel.ON.equals(state)));
355 private void updatePowermeter(@Nullable PowerMeterModel powerMeterModel) {
356 if (powerMeterModel != null) {
357 updateThingChannelState(CHANNEL_ENERGY, new QuantityType<>(powerMeterModel.getEnergy(), Units.WATT_HOUR));
358 updateThingChannelState(CHANNEL_POWER, new QuantityType<>(powerMeterModel.getPower(), Units.WATT));
359 updateThingChannelState(CHANNEL_VOLTAGE, new QuantityType<>(powerMeterModel.getVoltage(), Units.VOLT));
364 * Updates thing properties.
366 * @param device the {@link AVMFritzBaseModel}
367 * @param editProperties map of existing properties
369 protected void updateProperties(AVMFritzBaseModel device, Map<String, String> editProperties) {
370 editProperties.put(Thing.PROPERTY_FIRMWARE_VERSION, device.getFirmwareVersion());
371 updateProperties(editProperties);
375 * Updates thing channels and creates dynamic channels if missing.
377 * @param channelId ID of the channel to be updated.
378 * @param state State to be set.
380 protected void updateThingChannelState(String channelId, State state) {
381 Channel channel = thing.getChannel(channelId);
382 if (channel != null) {
383 updateState(channel.getUID(), state);
385 logger.debug("Channel '{}' in thing '{}' does not exist, recreating thing.", channelId, thing.getUID());
386 createChannel(channelId);
391 * Creates a {@link ChannelTypeUID} from the given channel id.
393 * @param channelId ID of the channel type UID to be created.
394 * @return the channel type UID
396 private ChannelTypeUID createChannelTypeUID(String channelId) {
397 int pos = channelId.indexOf(ChannelUID.CHANNEL_GROUP_SEPARATOR);
398 String id = pos > -1 ? channelId.substring(pos + 1) : channelId;
399 final ChannelTypeUID channelTypeUID;
401 case CHANNEL_BATTERY:
402 channelTypeUID = DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_BATTERY_LEVEL.getUID();
404 case CHANNEL_VOLTAGE:
405 channelTypeUID = DefaultSystemChannelTypeProvider.SYSTEM_ELECTRIC_VOLTAGE.getUID();
408 channelTypeUID = new ChannelTypeUID(BINDING_ID, id);
411 return channelTypeUID;
415 * Creates new channels for the thing.
417 * @param channelId ID of the channel to be created.
419 private void createChannel(String channelId) {
420 ThingHandlerCallback callback = getCallback();
421 if (callback != null) {
422 final ChannelUID channelUID = new ChannelUID(thing.getUID(), channelId);
423 final ChannelTypeUID channelTypeUID = createChannelTypeUID(channelId);
424 final Channel channel = callback.createChannelBuilder(channelUID, channelTypeUID).build();
425 updateThing(editThing().withoutChannel(channelUID).withChannel(channel).build());
430 * Updates thing channel configurations.
432 * @param channelId ID of the channel which configuration to be updated.
433 * @param configId ID of the configuration to be updated.
434 * @param value Value to be set.
436 protected void updateThingChannelConfiguration(String channelId, String configId, Object value) {
437 Channel channel = thing.getChannel(channelId);
438 if (channel != null) {
439 Configuration editConfig = channel.getConfiguration();
440 editConfig.put(configId, value);
445 public void onDeviceGone(ThingUID thingUID) {
446 if (thing.getUID().equals(thingUID)) {
447 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE, "Device not present in response");
452 public void handleCommand(ChannelUID channelUID, Command command) {
453 String channelId = channelUID.getIdWithoutGroup();
454 logger.debug("Handle command '{}' for channel {}", command, channelId);
455 if (command == RefreshType.REFRESH) {
456 handleRefreshCommand();
459 FritzAhaWebInterface fritzBox = getWebInterface();
460 if (fritzBox == null) {
461 logger.debug("Cannot handle command '{}' because connection is missing", command);
464 String ain = getIdentifier();
466 logger.debug("Cannot handle command '{}' because AIN is missing", command);
472 case CHANNEL_DEVICE_LOCKED:
473 case CHANNEL_TEMPERATURE:
474 case CHANNEL_HUMIDITY:
477 case CHANNEL_VOLTAGE:
478 case CHANNEL_ACTUALTEMP:
479 case CHANNEL_ECOTEMP:
480 case CHANNEL_COMFORTTEMP:
481 case CHANNEL_NEXT_CHANGE:
482 case CHANNEL_NEXTTEMP:
483 case CHANNEL_BATTERY:
484 case CHANNEL_BATTERY_LOW:
485 case CHANNEL_CONTACT_STATE:
486 case CHANNEL_LAST_CHANGE:
487 case CHANNEL_OBSTRUCTION_ALARM:
488 case CHANNEL_TEMPERATURE_ALARM:
489 logger.debug("Channel {} is a read-only channel and cannot handle command '{}'", channelId, command);
493 if (command instanceof OnOffType) {
494 fritzBox.setSwitch(ain, OnOffType.ON.equals(command));
498 case CHANNEL_BRIGHTNESS:
499 BigDecimal brightness = null;
500 if (command instanceof HSBType) {
501 HSBType hsbType = (HSBType) command;
502 brightness = hsbType.getBrightness().toBigDecimal();
503 fritzBox.setUnmappedHueAndSaturation(ain, hsbType.getHue().intValue(),
504 ColorControlModel.fromPercent(hsbType.getSaturation()), 0);
505 } else if (command instanceof PercentType) {
506 brightness = ((PercentType) command).toBigDecimal();
507 } else if (command instanceof OnOffType) {
508 fritzBox.setSwitch(ain, OnOffType.ON.equals(command));
509 } else if (command instanceof IncreaseDecreaseType) {
510 brightness = ((DeviceModel) currentDevice).getLevelControlModel().getLevelPercentage();
511 if (IncreaseDecreaseType.INCREASE.equals(command)) {
512 brightness.add(BigDecimal.TEN);
514 brightness.subtract(BigDecimal.TEN);
517 if (brightness != null) {
518 if (brightness.equals(BigDecimal.ZERO)) {
519 fritzBox.setSwitch(ain, false);
521 fritzBox.setSwitch(ain, true);
522 fritzBox.setLevelPercentage(ain, brightness);
526 case CHANNEL_COLORTEMPERATURE:
527 BigDecimal colorTemperaturePct = null;
528 if (command instanceof PercentType) {
529 colorTemperaturePct = ((PercentType) command).toBigDecimal();
531 if (colorTemperaturePct != null) {
532 int pct = colorTemperaturePct.intValue();
533 // AHA-HTTP-Inteface docu say that the values typically between 2700K and 6500K
534 int tempMinMired = 1000000 / 2700;
535 int tempMaxMired = 1000000 / 6500;
536 int tempScaledMired = tempMinMired + ((tempMaxMired - tempMinMired) * pct / 100);
537 int tempKelvin = 1000000 / tempScaledMired;
538 fritzBox.setColorTemperature(ain, tempKelvin, 0);
541 case CHANNEL_COLORTEMPERATURE_ABS:
542 BigDecimal colorTemperature = null;
543 if (command instanceof QuantityType) {
544 QuantityType<?> convertedCommand = ((QuantityType<?>) command).toInvertibleUnit(Units.KELVIN);
545 if (convertedCommand != null) {
546 colorTemperature = convertedCommand.toBigDecimal();
548 } else if (command instanceof DecimalType) {
549 colorTemperature = ((DecimalType) command).toBigDecimal();
551 if (colorTemperature != null) {
552 fritzBox.setColorTemperature(ain, colorTemperature.intValue(), 0);
555 case CHANNEL_SETTEMP:
556 BigDecimal temperature = null;
557 if (command instanceof DecimalType) {
558 temperature = normalizeCelsius(((DecimalType) command).toBigDecimal());
559 } else if (command instanceof QuantityType) {
560 @SuppressWarnings("unchecked")
561 QuantityType<Temperature> convertedCommand = ((QuantityType<Temperature>) command)
562 .toUnit(SIUnits.CELSIUS);
563 if (convertedCommand != null) {
564 temperature = normalizeCelsius(convertedCommand.toBigDecimal());
566 logger.warn("Unable to convert unit from '{}' to '{}'. Skipping command.",
567 ((QuantityType<?>) command).getUnit(), SIUnits.CELSIUS);
569 } else if (command instanceof IncreaseDecreaseType) {
570 temperature = currentDevice.getHkr().getTsoll();
571 if (IncreaseDecreaseType.INCREASE.equals(command)) {
572 temperature.add(BigDecimal.ONE);
574 temperature.subtract(BigDecimal.ONE);
576 } else if (command instanceof OnOffType) {
577 temperature = OnOffType.ON.equals(command) ? TEMP_FRITZ_ON : TEMP_FRITZ_OFF;
579 if (temperature != null) {
580 fritzBox.setSetTemp(ain, fromCelsius(temperature));
581 HeatingModel heatingModel = currentDevice.getHkr();
582 heatingModel.setTsoll(temperature);
583 updateState(CHANNEL_RADIATOR_MODE, new StringType(heatingModel.getRadiatorMode()));
586 case CHANNEL_RADIATOR_MODE:
587 BigDecimal targetTemperature = null;
588 if (command instanceof StringType) {
589 switch (command.toString()) {
591 targetTemperature = TEMP_FRITZ_ON;
594 targetTemperature = TEMP_FRITZ_OFF;
597 targetTemperature = currentDevice.getHkr().getKomfort();
600 targetTemperature = currentDevice.getHkr().getAbsenk();
603 targetTemperature = TEMP_FRITZ_MAX;
606 case MODE_WINDOW_OPEN:
607 logger.debug("Command '{}' is a read-only command for channel {}.", command, channelId);
610 if (targetTemperature != null) {
611 fritzBox.setSetTemp(ain, targetTemperature);
612 currentDevice.getHkr().setTsoll(targetTemperature);
613 updateState(CHANNEL_SETTEMP, new QuantityType<>(toCelsius(targetTemperature), SIUnits.CELSIUS));
617 case CHANNEL_ROLLERSHUTTER:
618 if (command instanceof StopMoveType) {
619 StopMoveType rollershutterCommand = (StopMoveType) command;
620 if (StopMoveType.STOP.equals(rollershutterCommand)) {
621 fritzBox.setBlind(ain, BlindCommand.STOP);
623 logger.debug("Received unknown rollershutter StopMove command MOVE");
625 } else if (command instanceof UpDownType) {
626 UpDownType rollershutterCommand = (UpDownType) command;
627 if (UpDownType.UP.equals(rollershutterCommand)) {
628 fritzBox.setBlind(ain, BlindCommand.OPEN);
630 fritzBox.setBlind(ain, BlindCommand.CLOSE);
632 } else if (command instanceof PercentType) {
633 BigDecimal levelPercentage = ((PercentType) command).toBigDecimal();
634 fritzBox.setLevelPercentage(ain, levelPercentage);
636 logger.debug("Received unknown rollershutter command type '{}'", command.toString());
640 logger.debug("Received unknown channel {}", channelId);
646 * Handles a command for a given action.
651 protected void handleAction(String action, long duration) {
652 FritzAhaWebInterface fritzBox = getWebInterface();
653 if (fritzBox == null) {
654 logger.debug("Cannot handle action '{}' because connection is missing", action);
657 String ain = getIdentifier();
659 logger.debug("Cannot handle action '{}' because AIN is missing", action);
662 if (duration < 0 || 86400 < duration) {
663 throw new IllegalArgumentException("Duration must not be less than zero or greater than 86400");
667 fritzBox.setBoostMode(ain,
668 duration > 0 ? ZonedDateTime.now().plusSeconds(duration).toEpochSecond() : 0);
670 case MODE_WINDOW_OPEN:
671 fritzBox.setWindowOpenMode(ain,
672 duration > 0 ? ZonedDateTime.now().plusSeconds(duration).toEpochSecond() : 0);
675 logger.debug("Received unknown action '{}'", action);
681 * Provides the web interface object.
683 * @return The web interface object
685 private @Nullable FritzAhaWebInterface getWebInterface() {
686 Bridge bridge = getBridge();
687 if (bridge != null) {
688 BridgeHandler handler = bridge.getHandler();
689 if (handler instanceof AVMFritzBaseBridgeHandler) {
690 return ((AVMFritzBaseBridgeHandler) handler).getWebInterface();
697 * Handles a refresh command.
699 private void handleRefreshCommand() {
700 Bridge bridge = getBridge();
701 if (bridge != null) {
702 BridgeHandler handler = bridge.getHandler();
703 if (handler instanceof AVMFritzBaseBridgeHandler) {
704 ((AVMFritzBaseBridgeHandler) handler).handleRefreshCommand();
714 public @Nullable String getIdentifier() {