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.livisismarthome.internal.handler;
15 import static org.openhab.binding.livisismarthome.internal.LivisiBindingConstants.*;
17 import java.time.format.DateTimeFormatter;
18 import java.time.format.FormatStyle;
20 import java.util.Map.Entry;
21 import java.util.Optional;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.livisismarthome.internal.client.api.entity.action.ShutterActionType;
26 import org.openhab.binding.livisismarthome.internal.client.api.entity.capability.CapabilityDTO;
27 import org.openhab.binding.livisismarthome.internal.client.api.entity.capability.CapabilityStateDTO;
28 import org.openhab.binding.livisismarthome.internal.client.api.entity.device.DeviceDTO;
29 import org.openhab.binding.livisismarthome.internal.client.api.entity.event.EventDTO;
30 import org.openhab.binding.livisismarthome.internal.client.api.entity.event.EventPropertiesDTO;
31 import org.openhab.binding.livisismarthome.internal.listener.DeviceStatusListener;
32 import org.openhab.core.library.types.DecimalType;
33 import org.openhab.core.library.types.OnOffType;
34 import org.openhab.core.library.types.OpenClosedType;
35 import org.openhab.core.library.types.PercentType;
36 import org.openhab.core.library.types.QuantityType;
37 import org.openhab.core.library.types.StopMoveType;
38 import org.openhab.core.library.types.StringType;
39 import org.openhab.core.library.types.UpDownType;
40 import org.openhab.core.library.unit.SIUnits;
41 import org.openhab.core.library.unit.Units;
42 import org.openhab.core.thing.Bridge;
43 import org.openhab.core.thing.Channel;
44 import org.openhab.core.thing.ChannelUID;
45 import org.openhab.core.thing.CommonTriggerEvents;
46 import org.openhab.core.thing.Thing;
47 import org.openhab.core.thing.ThingStatus;
48 import org.openhab.core.thing.ThingStatusDetail;
49 import org.openhab.core.thing.ThingStatusInfo;
50 import org.openhab.core.thing.binding.BaseThingHandler;
51 import org.openhab.core.thing.binding.ThingHandler;
52 import org.openhab.core.types.Command;
53 import org.openhab.core.types.RefreshType;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
58 * The {@link LivisiDeviceHandler} is responsible for handling the {@link DeviceDTO}s and their commands, which are
59 * sent to one of the channels.
61 * @author Oliver Kuhl - Initial contribution
62 * @author Sven Strohschein - Renamed from Innogy to Livisi
65 public class LivisiDeviceHandler extends BaseThingHandler implements DeviceStatusListener {
67 private static final int MIN_TEMPERATURE_CELSIUS = 6;
68 private static final int MAX_TEMPERATURE_CELSIUS = 30;
69 private static final String LONG_PRESS = "LongPress";
70 private static final String SHORT_PRESS = "ShortPress";
72 private final Logger logger = LoggerFactory.getLogger(LivisiDeviceHandler.class);
73 private final Object lock = new Object();
75 private String deviceId = "";
76 private @Nullable LivisiBridgeHandler bridgeHandler;
79 * Constructs a new {@link LivisiDeviceHandler} for the given {@link Thing}.
81 * @param thing device thing
83 public LivisiDeviceHandler(final Thing thing) {
88 public void handleCommand(final ChannelUID channelUID, final Command command) {
89 logger.debug("handleCommand called for channel '{}' of type '{}' with command '{}'", channelUID,
90 getThing().getThingTypeUID().getId(), command);
92 if (ThingStatus.ONLINE.equals(getThing().getStatus())) {
93 final Optional<LivisiBridgeHandler> bridgeHandlerOptional = getBridgeHandler();
94 if (bridgeHandlerOptional.isPresent()) {
95 LivisiBridgeHandler bridgeHandler = bridgeHandlerOptional.get();
96 if (command instanceof RefreshType) {
97 final Optional<DeviceDTO> device = bridgeHandler.getDeviceById(deviceId);
98 device.ifPresent(this::onDeviceStateChanged);
100 executeCommand(channelUID, command, bridgeHandler);
103 logger.warn("BridgeHandler not found. Cannot handle command without bridge.");
106 logger.debug("Cannot handle command - thing is not online. Command ignored.");
110 private void executeCommand(ChannelUID channelUID, Command command, LivisiBridgeHandler bridgeHandler) {
111 if (CHANNEL_SWITCH.equals(channelUID.getId())) {
112 commandSwitchDevice(command, bridgeHandler);
113 } else if (CHANNEL_DIMMER.equals(channelUID.getId())) {
114 commandSetDimLevel(command, bridgeHandler);
115 } else if (CHANNEL_ROLLERSHUTTER.equals(channelUID.getId())) {
116 commandRollerShutter(command, bridgeHandler);
117 } else if (CHANNEL_TARGET_TEMPERATURE.equals(channelUID.getId())) {
118 commandUpdatePointTemperature(command, bridgeHandler);
119 } else if (CHANNEL_OPERATION_MODE.equals(channelUID.getId())) {
120 commandSetOperationMode(command, bridgeHandler);
121 } else if (CHANNEL_ALARM.equals(channelUID.getId())) {
122 commandSwitchAlarm(command, bridgeHandler);
124 logger.debug("UNSUPPORTED channel {} for device {}.", channelUID.getId(), deviceId);
128 private void commandSwitchDevice(Command command, LivisiBridgeHandler bridgeHandler) {
129 if (command instanceof OnOffType) {
130 bridgeHandler.commandSwitchDevice(deviceId, OnOffType.ON.equals(command));
134 private void commandSetDimLevel(Command command, LivisiBridgeHandler bridgeHandler) {
135 if (command instanceof DecimalType) {
136 final DecimalType dimLevel = (DecimalType) command;
137 bridgeHandler.commandSetDimLevel(deviceId, dimLevel.intValue());
138 } else if (command instanceof OnOffType) {
139 if (OnOffType.ON.equals(command)) {
140 bridgeHandler.commandSetDimLevel(deviceId, 100);
142 bridgeHandler.commandSetDimLevel(deviceId, 0);
147 private void commandRollerShutter(Command command, LivisiBridgeHandler bridgeHandler) {
148 if (command instanceof DecimalType) {
149 final DecimalType rollerShutterLevel = (DecimalType) command;
150 bridgeHandler.commandSetRollerShutterLevel(deviceId,
151 invertRollerShutterValueIfConfigured(rollerShutterLevel.intValue()));
152 } else if (command instanceof OnOffType) {
153 if (OnOffType.ON.equals(command)) {
154 bridgeHandler.commandSetRollerShutterStop(deviceId, ShutterActionType.DOWN);
156 bridgeHandler.commandSetRollerShutterStop(deviceId, ShutterActionType.UP);
158 } else if (command instanceof UpDownType) {
159 if (UpDownType.DOWN.equals(command)) {
160 bridgeHandler.commandSetRollerShutterStop(deviceId, ShutterActionType.DOWN);
162 bridgeHandler.commandSetRollerShutterStop(deviceId, ShutterActionType.UP);
164 } else if (command instanceof StopMoveType) {
165 if (StopMoveType.STOP.equals(command)) {
166 bridgeHandler.commandSetRollerShutterStop(deviceId, ShutterActionType.STOP);
171 private void commandUpdatePointTemperature(Command command, LivisiBridgeHandler bridgeHandler) {
172 if (command instanceof QuantityType) {
173 final QuantityType<?> pointTemperatureCommand = ((QuantityType<?>) command).toUnit(SIUnits.CELSIUS);
174 if (pointTemperatureCommand != null) {
175 commandUpdatePointTemperature(pointTemperatureCommand.doubleValue(), bridgeHandler);
177 } else if (command instanceof DecimalType) {
178 commandUpdatePointTemperature(((DecimalType) command).doubleValue(), bridgeHandler);
182 private void commandUpdatePointTemperature(double pointTemperature, LivisiBridgeHandler bridgeHandler) {
183 if (pointTemperature < MIN_TEMPERATURE_CELSIUS) {
184 pointTemperature = MIN_TEMPERATURE_CELSIUS;
186 "pointTemperature set to value {} (instead of value '{}'), because it is the minimal possible value!",
187 MIN_TEMPERATURE_CELSIUS, pointTemperature);
188 } else if (pointTemperature > MAX_TEMPERATURE_CELSIUS) {
189 pointTemperature = MAX_TEMPERATURE_CELSIUS;
191 "pointTemperature set to value {} (instead of value '{}'), because it is the maximal possible value!",
192 MAX_TEMPERATURE_CELSIUS, pointTemperature);
194 bridgeHandler.commandUpdatePointTemperature(deviceId, pointTemperature);
197 private void commandSetOperationMode(Command command, LivisiBridgeHandler bridgeHandler) {
198 if (command instanceof StringType) {
199 final String autoModeCommand = command.toString();
201 if (CapabilityStateDTO.STATE_VALUE_OPERATION_MODE_AUTO.equals(autoModeCommand)) {
202 bridgeHandler.commandSetOperationMode(deviceId, true);
203 } else if (CapabilityStateDTO.STATE_VALUE_OPERATION_MODE_MANUAL.equals(autoModeCommand)) {
204 bridgeHandler.commandSetOperationMode(deviceId, false);
206 logger.warn("Could not set operationMode. Invalid value '{}'! Only '{}' or '{}' allowed.",
207 autoModeCommand, CapabilityStateDTO.STATE_VALUE_OPERATION_MODE_AUTO,
208 CapabilityStateDTO.STATE_VALUE_OPERATION_MODE_MANUAL);
213 private void commandSwitchAlarm(Command command, LivisiBridgeHandler bridgeHandler) {
214 if (command instanceof OnOffType) {
215 bridgeHandler.commandSwitchAlarm(deviceId, OnOffType.ON.equals(command));
220 public void initialize() {
221 logger.debug("Initializing LIVISI SmartHome device handler.");
222 initializeThing(isBridgeOnline());
226 public void dispose() {
227 unregisterListeners(bridgeHandler, deviceId);
230 private static void unregisterListeners(@Nullable LivisiBridgeHandler bridgeHandler, String deviceId) {
231 if (bridgeHandler != null) {
232 bridgeHandler.unregisterDeviceStatusListener(deviceId);
237 public void bridgeStatusChanged(final ThingStatusInfo bridgeStatusInfo) {
238 logger.debug("bridgeStatusChanged {}", bridgeStatusInfo);
239 initializeThing(ThingStatus.ONLINE == bridgeStatusInfo.getStatus());
243 * Initializes the {@link Thing} corresponding to the given status of the bridge.
245 * @param isBridgeOnline true if the bridge thing is online, otherwise false
247 private void initializeThing(final boolean isBridgeOnline) {
248 logger.debug("initializeThing thing {} bridge online status: {}", getThing().getUID(), isBridgeOnline);
249 final String configDeviceId = (String) getConfig().get(PROPERTY_ID);
250 if (configDeviceId != null) {
251 deviceId = configDeviceId;
253 Optional<LivisiBridgeHandler> bridgeHandler = registerAtBridgeHandler();
254 if (bridgeHandler.isPresent()) {
255 if (isBridgeOnline) {
256 initializeProperties();
258 Optional<DeviceDTO> deviceOptional = getDevice();
259 if (deviceOptional.isPresent()) {
260 DeviceDTO device = deviceOptional.get();
261 if (device.isReachable() != null && !device.isReachable()) {
262 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
263 "@text/error.notReachable");
265 updateStatus(ThingStatus.ONLINE);
268 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE, "@text/error.deviceNotFound");
271 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
274 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
275 "@text/error.bridgeHandlerMissing");
278 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/error.deviceIdUnknown");
283 * Initializes all properties of the {@link DeviceDTO}, like vendor, serialnumber etc.
285 private void initializeProperties() {
286 synchronized (this.lock) {
287 final Optional<DeviceDTO> deviceOptional = getDevice();
288 if (deviceOptional.isPresent()) {
289 DeviceDTO device = deviceOptional.get();
291 final Map<String, String> properties = editProperties();
292 properties.put(PROPERTY_ID, device.getId());
293 properties.put(PROPERTY_PROTOCOL_ID, device.getConfig().getProtocolId());
294 if (device.hasSerialNumber()) {
295 properties.put(Thing.PROPERTY_SERIAL_NUMBER, device.getSerialNumber());
297 properties.put(Thing.PROPERTY_VENDOR, device.getManufacturer());
298 properties.put(PROPERTY_VERSION, device.getVersion());
299 if (device.hasLocation()) {
300 properties.put(PROPERTY_LOCATION, device.getLocation().getName());
302 if (device.isBatteryPowered()) {
303 properties.put(PROPERTY_BATTERY_POWERED, "yes");
305 properties.put(PROPERTY_BATTERY_POWERED, "no");
307 if (device.isController()) {
308 properties.put(PROPERTY_DEVICE_TYPE, "Controller");
309 } else if (device.isVirtualDevice()) {
310 properties.put(PROPERTY_DEVICE_TYPE, "Virtual");
311 } else if (device.isRadioDevice()) {
312 properties.put(PROPERTY_DEVICE_TYPE, "Radio");
316 if (DEVICE_RST.equals(device.getType()) || DEVICE_RST2.equals(device.getType())
317 || DEVICE_WRT.equals(device.getType())) {
318 properties.put(PROPERTY_DISPLAY_CURRENT_TEMPERATURE,
319 device.getConfig().getDisplayCurrentTemperature());
323 if (DEVICE_ANALOG_METER.equals(device.getType()) || DEVICE_GENERATION_METER.equals(device.getType())
324 || DEVICE_SMART_METER.equals(device.getType())
325 || DEVICE_TWO_WAY_METER.equals(device.getType())) {
326 properties.put(PROPERTY_METER_ID, device.getConfig().getMeterId());
327 properties.put(PROPERTY_METER_FIRMWARE_VERSION, device.getConfig().getMeterFirmwareVersion());
330 if (device.getConfig().getTimeOfAcceptance() != null) {
331 properties.put(PROPERTY_TIME_OF_ACCEPTANCE, device.getConfig().getTimeOfAcceptance()
332 .format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)));
334 if (device.getConfig().getTimeOfDiscovery() != null) {
335 properties.put(PROPERTY_TIME_OF_DISCOVERY, device.getConfig().getTimeOfDiscovery()
336 .format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)));
339 updateProperties(properties);
341 onDeviceStateChanged(device);
343 logger.debug("initializeProperties: The device with id {} isn't found", deviceId);
349 public void onDeviceStateChanged(final DeviceDTO device) {
350 synchronized (this.lock) {
351 updateChannels(device, false);
356 public void onDeviceStateChanged(final DeviceDTO device, final EventDTO event) {
357 synchronized (this.lock) {
358 if (event.isLinkedtoCapability()) {
359 final String linkedCapabilityId = event.getSourceId();
361 CapabilityDTO capability = device.getCapabilityMap().get(linkedCapabilityId);
362 if (capability != null) {
363 logger.trace("Loaded Capability {}, {} with id {}, device {} from device id {}",
364 capability.getType(), capability.getName(), capability.getId(), capability.getDeviceLink(),
367 if (capability.hasState()) {
368 boolean deviceChanged = updateDevice(event, capability);
370 updateChannels(device, true);
373 logger.debug("Capability {} has no state (yet?) - refreshing device.", capability.getName());
375 Optional<DeviceDTO> deviceOptional = refreshDevice(linkedCapabilityId);
376 deviceOptional.ifPresent((d) -> updateChannels(d, true));
379 } else if (event.isLinkedtoDevice()) {
380 if (device.hasDeviceState()) {
381 updateChannels(device, true);
383 logger.debug("Device {}/{} has no state.", device.getConfig().getName(), device.getId());
389 private Optional<DeviceDTO> refreshDevice(String linkedCapabilityId) {
390 Optional<LivisiBridgeHandler> bridgeHandler = registerAtBridgeHandler();
391 Optional<DeviceDTO> deviceOptional = bridgeHandler.flatMap(bh -> bh.refreshDevice(deviceId));
392 if (deviceOptional.isPresent()) {
393 DeviceDTO device = deviceOptional.get();
394 CapabilityDTO capability = device.getCapabilityMap().get(linkedCapabilityId);
395 if (capability != null && capability.hasState()) {
396 return Optional.of(device);
399 return Optional.empty();
402 private boolean updateDevice(EventDTO event, CapabilityDTO capability) {
403 CapabilityStateDTO capabilityState = capability.getCapabilityState();
406 if (capability.isTypeVariableActuator()) {
407 capabilityState.setVariableActuatorState(event.getProperties().getValue());
410 } else if (capability.isTypeSwitchActuator()) {
411 capabilityState.setSwitchActuatorState(event.getProperties().getOnState());
414 } else if (capability.isTypeDimmerActuator()) {
415 capabilityState.setDimmerActuatorState(event.getProperties().getDimLevel());
417 // RollerShutterActuator
418 } else if (capability.isTypeRollerShutterActuator()) {
419 capabilityState.setRollerShutterActuatorState(event.getProperties().getShutterLevel());
422 } else if (capability.isTypeTemperatureSensor()) {
423 // when values are changed, they come with separate events
424 // values should only updated when they are not null
425 final Double temperature = event.getProperties().getTemperature();
426 final Boolean frostWarning = event.getProperties().getFrostWarning();
427 if (temperature != null) {
428 capabilityState.setTemperatureSensorTemperatureState(temperature);
430 if (frostWarning != null) {
431 capabilityState.setTemperatureSensorFrostWarningState(frostWarning);
434 // ThermostatActuator
435 } else if (capability.isTypeThermostatActuator()) {
436 // when values are changed, they come with separate events
437 // values should only updated when they are not null
439 final Double pointTemperature = event.getProperties().getPointTemperature();
440 final String operationMode = event.getProperties().getOperationMode();
441 final Boolean windowReductionActive = event.getProperties().getWindowReductionActive();
443 if (pointTemperature != null) {
444 capabilityState.setThermostatActuatorPointTemperatureState(pointTemperature);
446 if (operationMode != null) {
447 capabilityState.setThermostatActuatorOperationModeState(operationMode);
449 if (windowReductionActive != null) {
450 capabilityState.setThermostatActuatorWindowReductionActiveState(windowReductionActive);
454 } else if (capability.isTypeHumiditySensor()) {
455 // when values are changed, they come with separate events
456 // values should only updated when they are not null
457 final Double humidity = event.getProperties().getHumidity();
458 final Boolean moldWarning = event.getProperties().getMoldWarning();
459 if (humidity != null) {
460 capabilityState.setHumiditySensorHumidityState(humidity);
462 if (moldWarning != null) {
463 capabilityState.setHumiditySensorMoldWarningState(moldWarning);
467 } else if (capability.isTypeWindowDoorSensor()) {
468 capabilityState.setWindowDoorSensorState(event.getProperties().getIsOpen());
470 // SmokeDetectorSensor
471 } else if (capability.isTypeSmokeDetectorSensor()) {
472 capabilityState.setSmokeDetectorSensorState(event.getProperties().getIsSmokeAlarm());
475 } else if (capability.isTypeAlarmActuator()) {
476 capabilityState.setAlarmActuatorState(event.getProperties().getOnState());
478 // MotionDetectionSensor
479 } else if (capability.isTypeMotionDetectionSensor()) {
480 capabilityState.setMotionDetectionSensorState(event.getProperties().getMotionDetectedCount());
483 } else if (capability.isTypeLuminanceSensor()) {
484 capabilityState.setLuminanceSensorState(event.getProperties().getLuminance());
487 } else if (capability.isTypePushButtonSensor()) {
488 if (event.isButtonPressedEvent()) {
489 // Some devices send both StateChanged and ButtonPressed. But only the ButtonPressed should be handled,
490 // therefore it is checked for button pressed event (button index is set).
491 EventPropertiesDTO properties = event.getProperties();
492 capabilityState.setPushButtonSensorButtonIndexState(properties.getKeyPressButtonIndex());
493 capabilityState.setPushButtonSensorButtonIndexType(properties.getKeyPressType());
494 capabilityState.setPushButtonSensorCounterState(properties.getKeyPressCounter());
497 // EnergyConsumptionSensor
498 } else if (capability.isTypeEnergyConsumptionSensor()) {
499 capabilityState.setEnergyConsumptionSensorEnergyConsumptionMonthKWhState(
500 event.getProperties().getEnergyConsumptionMonthKWh());
501 capabilityState.setEnergyConsumptionSensorAbsoluteEnergyConsumptionState(
502 event.getProperties().getAbsoluteEnergyConsumption());
503 capabilityState.setEnergyConsumptionSensorEnergyConsumptionMonthEuroState(
504 event.getProperties().getEnergyConsumptionMonthEuro());
505 capabilityState.setEnergyConsumptionSensorEnergyConsumptionDayEuroState(
506 event.getProperties().getEnergyConsumptionDayEuro());
507 capabilityState.setEnergyConsumptionSensorEnergyConsumptionDayKWhState(
508 event.getProperties().getEnergyConsumptionDayKWh());
510 // PowerConsumptionSensor
511 } else if (capability.isTypePowerConsumptionSensor()) {
512 capabilityState.setPowerConsumptionSensorPowerConsumptionWattState(
513 event.getProperties().getPowerConsumptionWatt());
515 // GenerationMeterEnergySensor
516 } else if (capability.isTypeGenerationMeterEnergySensor()) {
517 capabilityState.setGenerationMeterEnergySensorEnergyPerMonthInKWhState(
518 event.getProperties().getEnergyPerMonthInKWh());
519 capabilityState.setGenerationMeterEnergySensorTotalEnergyState(event.getProperties().getTotalEnergy());
520 capabilityState.setGenerationMeterEnergySensorEnergyPerMonthInEuroState(
521 event.getProperties().getEnergyPerMonthInEuro());
522 capabilityState.setGenerationMeterEnergySensorEnergyPerDayInEuroState(
523 event.getProperties().getEnergyPerDayInEuro());
525 .setGenerationMeterEnergySensorEnergyPerDayInKWhState(event.getProperties().getEnergyPerDayInKWh());
527 // GenerationMeterPowerConsumptionSensor
528 } else if (capability.isTypeGenerationMeterPowerConsumptionSensor()) {
530 .setGenerationMeterPowerConsumptionSensorPowerInWattState(event.getProperties().getPowerInWatt());
532 // TwoWayMeterEnergyConsumptionSensor
533 } else if (capability.isTypeTwoWayMeterEnergyConsumptionSensor()) {
534 capabilityState.setTwoWayMeterEnergyConsumptionSensorEnergyPerMonthInKWhState(
535 event.getProperties().getEnergyPerMonthInKWh());
537 .setTwoWayMeterEnergyConsumptionSensorTotalEnergyState(event.getProperties().getTotalEnergy());
538 capabilityState.setTwoWayMeterEnergyConsumptionSensorEnergyPerMonthInEuroState(
539 event.getProperties().getEnergyPerMonthInEuro());
540 capabilityState.setTwoWayMeterEnergyConsumptionSensorEnergyPerDayInEuroState(
541 event.getProperties().getEnergyPerDayInEuro());
542 capabilityState.setTwoWayMeterEnergyConsumptionSensorEnergyPerDayInKWhState(
543 event.getProperties().getEnergyPerDayInKWh());
545 // TwoWayMeterEnergyFeedSensor
546 } else if (capability.isTypeTwoWayMeterEnergyFeedSensor()) {
547 capabilityState.setTwoWayMeterEnergyFeedSensorEnergyPerMonthInKWhState(
548 event.getProperties().getEnergyPerMonthInKWh());
549 capabilityState.setTwoWayMeterEnergyFeedSensorTotalEnergyState(event.getProperties().getTotalEnergy());
550 capabilityState.setTwoWayMeterEnergyFeedSensorEnergyPerMonthInEuroState(
551 event.getProperties().getEnergyPerMonthInEuro());
552 capabilityState.setTwoWayMeterEnergyFeedSensorEnergyPerDayInEuroState(
553 event.getProperties().getEnergyPerDayInEuro());
555 .setTwoWayMeterEnergyFeedSensorEnergyPerDayInKWhState(event.getProperties().getEnergyPerDayInKWh());
557 // TwoWayMeterPowerConsumptionSensor
558 } else if (capability.isTypeTwoWayMeterPowerConsumptionSensor()) {
560 .setTwoWayMeterPowerConsumptionSensorPowerInWattState(event.getProperties().getPowerInWatt());
563 logger.debug("Unsupported capability type {}.", capability.getType());
569 private void updateChannels(DeviceDTO device, boolean isChangedByEvent) {
571 final boolean isReachable = updateStatus(device);
574 updateDeviceChannels(device);
577 for (final Entry<String, CapabilityDTO> entry : device.getCapabilityMap().entrySet()) {
578 final CapabilityDTO capability = entry.getValue();
580 logger.debug("->capability:{} ({}/{})", capability.getId(), capability.getType(), capability.getName());
582 if (capability.hasState()) {
583 updateCapabilityChannels(device, capability, isChangedByEvent);
585 logger.debug("Capability not available for device {} ({})", device.getConfig().getName(),
592 private boolean updateStatus(DeviceDTO device) {
593 Boolean reachable = device.isReachable();
594 if (reachable != null) {
596 updateStatus(ThingStatus.ONLINE);
598 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.notReachable");
605 private void updateDeviceChannels(DeviceDTO device) {
606 if (device.isBatteryPowered()) {
607 updateState(CHANNEL_BATTERY_LOW, OnOffType.from(device.hasLowBattery()));
611 private void updateCapabilityChannels(DeviceDTO device, CapabilityDTO capability, boolean isChangedByEvent) {
612 switch (capability.getType()) {
613 case CapabilityDTO.TYPE_VARIABLEACTUATOR:
614 updateVariableActuatorChannels(capability);
616 case CapabilityDTO.TYPE_SWITCHACTUATOR:
617 updateSwitchActuatorChannels(capability);
619 case CapabilityDTO.TYPE_DIMMERACTUATOR:
620 updateDimmerActuatorChannels(capability);
622 case CapabilityDTO.TYPE_ROLLERSHUTTERACTUATOR:
623 updateRollerShutterActuatorChannels(capability);
625 case CapabilityDTO.TYPE_TEMPERATURESENSOR:
626 updateTemperatureSensorChannels(capability);
628 case CapabilityDTO.TYPE_THERMOSTATACTUATOR:
629 updateThermostatActuatorChannels(device, capability);
631 case CapabilityDTO.TYPE_HUMIDITYSENSOR:
632 updateHumiditySensorChannels(capability);
634 case CapabilityDTO.TYPE_WINDOWDOORSENSOR:
635 updateWindowDoorSensorChannels(capability);
637 case CapabilityDTO.TYPE_SMOKEDETECTORSENSOR:
638 updateSmokeDetectorChannels(capability);
640 case CapabilityDTO.TYPE_ALARMACTUATOR:
641 updateAlarmActuatorChannels(capability);
643 case CapabilityDTO.TYPE_MOTIONDETECTIONSENSOR:
644 updateMotionDetectionSensorChannels(capability);
646 case CapabilityDTO.TYPE_LUMINANCESENSOR:
647 updateLuminanceSensorChannels(capability);
649 case CapabilityDTO.TYPE_PUSHBUTTONSENSOR:
650 updatePushButtonSensorChannels(capability, isChangedByEvent);
652 case CapabilityDTO.TYPE_ENERGYCONSUMPTIONSENSOR:
653 updateEnergyConsumptionSensorChannels(capability);
655 case CapabilityDTO.TYPE_POWERCONSUMPTIONSENSOR:
656 updateStateForEnergyChannelWatt(CHANNEL_POWER_CONSUMPTION_WATT,
657 capability.getCapabilityState().getPowerConsumptionSensorPowerConsumptionWattState(),
660 case CapabilityDTO.TYPE_GENERATIONMETERENERGYSENSOR:
661 updateGenerationMeterEnergySensorChannels(capability);
663 case CapabilityDTO.TYPE_GENERATIONMETERPOWERCONSUMPTIONSENSOR:
664 updateStateForEnergyChannelWatt(CHANNEL_POWER_GENERATION_WATT,
665 capability.getCapabilityState().getGenerationMeterPowerConsumptionSensorPowerInWattState(),
668 case CapabilityDTO.TYPE_TWOWAYMETERENERGYCONSUMPTIONSENSOR:
669 updateTwoWayMeterEnergyConsumptionSensorChannels(capability);
671 case CapabilityDTO.TYPE_TWOWAYMETERENERGYFEEDSENSOR:
672 updateTwoWayMeterEnergyFeedSensorChannels(capability);
674 case CapabilityDTO.TYPE_TWOWAYMETERPOWERCONSUMPTIONSENSOR:
675 updateStateForEnergyChannelWatt(CHANNEL_POWER_WATT,
676 capability.getCapabilityState().getTwoWayMeterPowerConsumptionSensorPowerInWattState(),
680 logger.debug("Unsupported capability type {}.", capability.getType());
685 private void updateVariableActuatorChannels(CapabilityDTO capability) {
686 final Boolean variableActuatorState = capability.getCapabilityState().getVariableActuatorState();
687 if (variableActuatorState != null) {
688 updateState(CHANNEL_SWITCH, OnOffType.from(variableActuatorState));
690 logStateNull(capability);
694 private void updateSwitchActuatorChannels(CapabilityDTO capability) {
695 final Boolean switchActuatorState = capability.getCapabilityState().getSwitchActuatorState();
696 if (switchActuatorState != null) {
697 updateState(CHANNEL_SWITCH, OnOffType.from(switchActuatorState));
699 logStateNull(capability);
703 private void updateDimmerActuatorChannels(CapabilityDTO capability) {
704 final Integer dimLevel = capability.getCapabilityState().getDimmerActuatorState();
705 if (dimLevel != null) {
706 logger.debug("Dimlevel state {}", dimLevel);
707 updateState(CHANNEL_DIMMER, new PercentType(dimLevel));
709 logStateNull(capability);
713 private void updateRollerShutterActuatorChannels(CapabilityDTO capability) {
714 Integer rollerShutterLevel = capability.getCapabilityState().getRollerShutterActuatorState();
715 if (rollerShutterLevel != null) {
716 rollerShutterLevel = invertRollerShutterValueIfConfigured(rollerShutterLevel);
717 logger.debug("RollerShutterlevel state {}", rollerShutterLevel);
718 updateState(CHANNEL_ROLLERSHUTTER, new PercentType(rollerShutterLevel));
720 logStateNull(capability);
724 private void updateTemperatureSensorChannels(CapabilityDTO capability) {
726 final Double temperature = capability.getCapabilityState().getTemperatureSensorTemperatureState();
727 if (temperature != null) {
728 logger.debug("-> Temperature sensor state: {}", temperature);
729 updateState(CHANNEL_CURRENT_TEMPERATURE, QuantityType.valueOf(temperature, SIUnits.CELSIUS));
731 logStateNull(capability);
735 final Boolean frostWarning = capability.getCapabilityState().getTemperatureSensorFrostWarningState();
736 if (frostWarning != null) {
737 updateState(CHANNEL_FROST_WARNING, OnOffType.from(frostWarning));
739 logStateNull(capability);
743 private void updateThermostatActuatorChannels(DeviceDTO device, CapabilityDTO capability) {
745 final Double pointTemperature = capability.getCapabilityState().getThermostatActuatorPointTemperatureState();
746 if (pointTemperature != null) {
747 logger.debug("Update CHANNEL_SET_TEMPERATURE: state:{} (DeviceName {}, Capab-ID:{})", pointTemperature,
748 device.getConfig().getName(), capability.getId());
749 updateState(CHANNEL_TARGET_TEMPERATURE, QuantityType.valueOf(pointTemperature, SIUnits.CELSIUS));
751 logStateNull(capability);
755 final String operationMode = capability.getCapabilityState().getThermostatActuatorOperationModeState();
756 if (operationMode != null) {
757 updateState(CHANNEL_OPERATION_MODE, new StringType(operationMode));
759 logStateNull(capability);
762 // window reduction active
763 final Boolean windowReductionActive = capability.getCapabilityState()
764 .getThermostatActuatorWindowReductionActiveState();
765 if (windowReductionActive != null) {
766 updateState(CHANNEL_WINDOW_REDUCTION_ACTIVE, OnOffType.from(windowReductionActive));
768 logStateNull(capability);
772 private void updateHumiditySensorChannels(CapabilityDTO capability) {
774 final Double humidity = capability.getCapabilityState().getHumiditySensorHumidityState();
775 if (humidity != null) {
776 updateState(CHANNEL_HUMIDITY, QuantityType.valueOf(humidity, Units.PERCENT));
778 logStateNull(capability);
782 final Boolean moldWarning = capability.getCapabilityState().getHumiditySensorMoldWarningState();
783 if (moldWarning != null) {
784 updateState(CHANNEL_MOLD_WARNING, OnOffType.from(moldWarning));
786 logStateNull(capability);
790 private void updateWindowDoorSensorChannels(CapabilityDTO capability) {
791 final Boolean contactState = capability.getCapabilityState().getWindowDoorSensorState();
792 if (contactState != null) {
793 updateState(CHANNEL_CONTACT, toOpenClosedType(contactState));
795 logStateNull(capability);
799 private void updateSmokeDetectorChannels(CapabilityDTO capability) {
800 final Boolean smokeState = capability.getCapabilityState().getSmokeDetectorSensorState();
801 if (smokeState != null) {
802 updateState(CHANNEL_SMOKE, OnOffType.from(smokeState));
804 logStateNull(capability);
808 private void updateAlarmActuatorChannels(CapabilityDTO capability) {
809 final Boolean alarmState = capability.getCapabilityState().getAlarmActuatorState();
810 if (alarmState != null) {
811 updateState(CHANNEL_ALARM, OnOffType.from(alarmState));
813 logStateNull(capability);
817 private void updateMotionDetectionSensorChannels(CapabilityDTO capability) {
818 final Integer motionCount = capability.getCapabilityState().getMotionDetectionSensorState();
819 if (motionCount != null) {
820 logger.debug("Motion state {} -> count {}", motionCount, motionCount);
821 updateState(CHANNEL_MOTION_COUNT, new DecimalType(motionCount));
823 logStateNull(capability);
827 private void updateLuminanceSensorChannels(CapabilityDTO capability) {
828 final Double luminance = capability.getCapabilityState().getLuminanceSensorState();
829 if (luminance != null) {
830 updateState(CHANNEL_LUMINANCE, QuantityType.valueOf(luminance, Units.PERCENT));
832 logStateNull(capability);
836 private void updatePushButtonSensorChannels(CapabilityDTO capability, boolean isChangedByEvent) {
837 final Integer pushCount = capability.getCapabilityState().getPushButtonSensorCounterState();
838 final Integer buttonIndex = capability.getCapabilityState().getPushButtonSensorButtonIndexState();
839 final String type = capability.getCapabilityState().getPushButtonSensorButtonIndexType();
840 logger.debug("Pushbutton index {}, count {}, type {}", buttonIndex, pushCount, type);
841 if (buttonIndex != null && pushCount != null) {
842 if (buttonIndex >= 0 && buttonIndex <= 7) {
843 final int channelIndex = buttonIndex + 1;
844 updateState(String.format(CHANNEL_BUTTON_COUNT, channelIndex), new DecimalType(pushCount));
846 if (isChangedByEvent) {
847 triggerButtonChannels(type, channelIndex);
850 // Button handled so remove state to avoid re-trigger.
851 capability.getCapabilityState().setPushButtonSensorButtonIndexState(null);
852 capability.getCapabilityState().setPushButtonSensorButtonIndexType(null);
855 logStateNull(capability);
859 private void triggerButtonChannels(@Nullable String type, int channelIndex) {
861 if (SHORT_PRESS.equals(type)) {
862 triggerChannel(CHANNEL_BUTTON + channelIndex, CommonTriggerEvents.SHORT_PRESSED);
863 } else if (LONG_PRESS.equals(type)) {
864 triggerChannel(CHANNEL_BUTTON + channelIndex, CommonTriggerEvents.LONG_PRESSED);
867 triggerChannel(CHANNEL_BUTTON + channelIndex, CommonTriggerEvents.PRESSED);
870 private void updateEnergyConsumptionSensorChannels(CapabilityDTO capability) {
871 updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_CONSUMPTION_MONTH_KWH,
872 capability.getCapabilityState().getEnergyConsumptionSensorEnergyConsumptionMonthKWhState(), capability);
873 updateStateForEnergyChannelKiloWattHour(CHANNEL_ABOLUTE_ENERGY_CONSUMPTION,
874 capability.getCapabilityState().getEnergyConsumptionSensorAbsoluteEnergyConsumptionState(), capability);
875 updateStateForEnergyChannelEuro(CHANNEL_ENERGY_CONSUMPTION_MONTH_EURO,
876 capability.getCapabilityState().getEnergyConsumptionSensorEnergyConsumptionMonthEuroState(),
878 updateStateForEnergyChannelEuro(CHANNEL_ENERGY_CONSUMPTION_DAY_EURO,
879 capability.getCapabilityState().getEnergyConsumptionSensorEnergyConsumptionDayEuroState(), capability);
880 updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_CONSUMPTION_DAY_KWH,
881 capability.getCapabilityState().getEnergyConsumptionSensorEnergyConsumptionDayKWhState(), capability);
884 private void updateGenerationMeterEnergySensorChannels(CapabilityDTO capability) {
885 updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_GENERATION_MONTH_KWH,
886 capability.getCapabilityState().getGenerationMeterEnergySensorEnergyPerMonthInKWhState(), capability);
887 updateStateForEnergyChannelKiloWattHour(CHANNEL_TOTAL_ENERGY_GENERATION,
888 capability.getCapabilityState().getGenerationMeterEnergySensorTotalEnergyState(), capability);
889 updateStateForEnergyChannelEuro(CHANNEL_ENERGY_GENERATION_MONTH_EURO,
890 capability.getCapabilityState().getGenerationMeterEnergySensorEnergyPerMonthInEuroState(), capability);
891 updateStateForEnergyChannelEuro(CHANNEL_ENERGY_GENERATION_DAY_EURO,
892 capability.getCapabilityState().getGenerationMeterEnergySensorEnergyPerDayInEuroState(), capability);
893 updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_GENERATION_DAY_KWH,
894 capability.getCapabilityState().getGenerationMeterEnergySensorEnergyPerDayInKWhState(), capability);
897 private void updateTwoWayMeterEnergyConsumptionSensorChannels(CapabilityDTO capability) {
898 updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_MONTH_KWH,
899 capability.getCapabilityState().getTwoWayMeterEnergyConsumptionSensorEnergyPerMonthInKWhState(),
901 updateStateForEnergyChannelKiloWattHour(CHANNEL_TOTAL_ENERGY,
902 capability.getCapabilityState().getTwoWayMeterEnergyConsumptionSensorTotalEnergyState(), capability);
903 updateStateForEnergyChannelEuro(CHANNEL_ENERGY_MONTH_EURO,
904 capability.getCapabilityState().getTwoWayMeterEnergyConsumptionSensorEnergyPerMonthInEuroState(),
906 updateStateForEnergyChannelEuro(CHANNEL_ENERGY_DAY_EURO,
907 capability.getCapabilityState().getTwoWayMeterEnergyConsumptionSensorEnergyPerDayInEuroState(),
909 updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_DAY_KWH,
910 capability.getCapabilityState().getTwoWayMeterEnergyConsumptionSensorEnergyPerDayInKWhState(),
914 private void updateTwoWayMeterEnergyFeedSensorChannels(CapabilityDTO capability) {
915 updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_FEED_MONTH_KWH,
916 capability.getCapabilityState().getTwoWayMeterEnergyFeedSensorEnergyPerMonthInKWhState(), capability);
917 updateStateForEnergyChannelKiloWattHour(CHANNEL_TOTAL_ENERGY_FED,
918 capability.getCapabilityState().getTwoWayMeterEnergyFeedSensorTotalEnergyState(), capability);
919 updateStateForEnergyChannelEuro(CHANNEL_ENERGY_FEED_MONTH_EURO,
920 capability.getCapabilityState().getTwoWayMeterEnergyFeedSensorEnergyPerMonthInEuroState(), capability);
921 updateStateForEnergyChannelEuro(CHANNEL_ENERGY_FEED_DAY_EURO,
922 capability.getCapabilityState().getTwoWayMeterEnergyFeedSensorEnergyPerDayInEuroState(), capability);
923 updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_FEED_DAY_KWH,
924 capability.getCapabilityState().getTwoWayMeterEnergyFeedSensorEnergyPerDayInKWhState(), capability);
927 private void updateStateForEnergyChannelEuro(final String channelId, @Nullable final Double state,
928 final CapabilityDTO capability) {
930 updateState(channelId, new DecimalType(state));
932 logStateNull(capability);
936 private void updateStateForEnergyChannelWatt(final String channelId, @Nullable final Double state,
937 final CapabilityDTO capability) {
939 updateState(channelId, QuantityType.valueOf(state, Units.WATT));
941 logStateNull(capability);
945 private void updateStateForEnergyChannelKiloWattHour(final String channelId, @Nullable final Double state,
946 final CapabilityDTO capability) {
948 updateState(channelId, QuantityType.valueOf(state, Units.KILOWATT_HOUR));
950 logStateNull(capability);
955 * Returns the inverted value. Currently only rollershutter channels are supported.
957 * @param value value to become inverted
958 * @return the value or the inverted value
960 private int invertRollerShutterValueIfConfigured(final int value) {
962 final Channel channel = getThing().getChannel(CHANNEL_ROLLERSHUTTER);
963 if (channel == null) {
964 logger.debug("Channel {} was null! Value not inverted.", CHANNEL_ROLLERSHUTTER);
967 final Boolean invert = (Boolean) channel.getConfiguration().get(INVERT_CHANNEL_PARAMETER);
968 if (invert != null && invert) {
975 * Returns the {@link DeviceDTO} associated with this {@link LivisiDeviceHandler} (referenced by the
976 * {@link LivisiDeviceHandler#deviceId}).
978 * @return the {@link DeviceDTO} or null, if not found or no {@link LivisiBridgeHandler} is available
980 private Optional<DeviceDTO> getDevice() {
981 return getBridgeHandler().flatMap(bridgeHandler -> bridgeHandler.getDeviceById(deviceId));
984 private Optional<LivisiBridgeHandler> registerAtBridgeHandler() {
985 synchronized (this.lock) {
986 if (this.bridgeHandler == null) {
988 final Bridge bridge = getBridge();
989 if (bridge == null) {
990 return Optional.empty();
993 final ThingHandler handler = bridge.getHandler();
994 if (handler instanceof LivisiBridgeHandler) {
995 LivisiBridgeHandler bridgeHandler = (LivisiBridgeHandler) handler;
996 bridgeHandler.registerDeviceStatusListener(deviceId, this);
997 this.bridgeHandler = bridgeHandler;
999 return Optional.empty(); // also called when the handler is NULL
1002 return getBridgeHandler();
1007 * Returns the LIVISI bridge handler.
1009 * @return the {@link LivisiBridgeHandler} or null
1011 private Optional<LivisiBridgeHandler> getBridgeHandler() {
1012 return Optional.ofNullable(this.bridgeHandler);
1015 private boolean isBridgeOnline() {
1017 Bridge bridge = getBridge();
1018 if (bridge != null) {
1019 return ThingStatus.ONLINE == bridge.getStatus();
1024 private void logStateNull(CapabilityDTO capability) {
1025 logger.debug("State for {} is STILL NULL!! cstate-id: {}, capability-id: {}", capability.getType(),
1026 capability.getCapabilityState().getId(), capability.getId());
1029 private static OpenClosedType toOpenClosedType(boolean isOpen) {
1031 return OpenClosedType.OPEN;
1033 return OpenClosedType.CLOSED;