2 * Copyright (c) 2010-2022 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);
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);
373 logger.debug("Capability {} has no state (yet?) - refreshing device.", capability.getName());
375 Optional<DeviceDTO> deviceOptional = refreshDevice(linkedCapabilityId);
376 deviceOptional.ifPresent(this::updateChannels);
379 } else if (event.isLinkedtoDevice()) {
380 if (device.hasDeviceState()) {
381 updateChannels(device);
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) {
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);
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) {
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);
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) {
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;
845 if (SHORT_PRESS.equals(type)) {
846 triggerChannel(CHANNEL_BUTTON + channelIndex, CommonTriggerEvents.SHORT_PRESSED);
847 } else if (LONG_PRESS.equals(type)) {
848 triggerChannel(CHANNEL_BUTTON + channelIndex, CommonTriggerEvents.LONG_PRESSED);
851 triggerChannel(CHANNEL_BUTTON + channelIndex, CommonTriggerEvents.PRESSED);
852 updateState(String.format(CHANNEL_BUTTON_COUNT, channelIndex), new DecimalType(pushCount));
854 // Button handled so remove state to avoid re-trigger.
855 capability.getCapabilityState().setPushButtonSensorButtonIndexState(null);
856 capability.getCapabilityState().setPushButtonSensorButtonIndexType(null);
858 logStateNull(capability);
862 private void updateEnergyConsumptionSensorChannels(CapabilityDTO capability) {
863 updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_CONSUMPTION_MONTH_KWH,
864 capability.getCapabilityState().getEnergyConsumptionSensorEnergyConsumptionMonthKWhState(), capability);
865 updateStateForEnergyChannelKiloWattHour(CHANNEL_ABOLUTE_ENERGY_CONSUMPTION,
866 capability.getCapabilityState().getEnergyConsumptionSensorAbsoluteEnergyConsumptionState(), capability);
867 updateStateForEnergyChannelEuro(CHANNEL_ENERGY_CONSUMPTION_MONTH_EURO,
868 capability.getCapabilityState().getEnergyConsumptionSensorEnergyConsumptionMonthEuroState(),
870 updateStateForEnergyChannelEuro(CHANNEL_ENERGY_CONSUMPTION_DAY_EURO,
871 capability.getCapabilityState().getEnergyConsumptionSensorEnergyConsumptionDayEuroState(), capability);
872 updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_CONSUMPTION_DAY_KWH,
873 capability.getCapabilityState().getEnergyConsumptionSensorEnergyConsumptionDayKWhState(), capability);
876 private void updateGenerationMeterEnergySensorChannels(CapabilityDTO capability) {
877 updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_GENERATION_MONTH_KWH,
878 capability.getCapabilityState().getGenerationMeterEnergySensorEnergyPerMonthInKWhState(), capability);
879 updateStateForEnergyChannelKiloWattHour(CHANNEL_TOTAL_ENERGY_GENERATION,
880 capability.getCapabilityState().getGenerationMeterEnergySensorTotalEnergyState(), capability);
881 updateStateForEnergyChannelEuro(CHANNEL_ENERGY_GENERATION_MONTH_EURO,
882 capability.getCapabilityState().getGenerationMeterEnergySensorEnergyPerMonthInEuroState(), capability);
883 updateStateForEnergyChannelEuro(CHANNEL_ENERGY_GENERATION_DAY_EURO,
884 capability.getCapabilityState().getGenerationMeterEnergySensorEnergyPerDayInEuroState(), capability);
885 updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_GENERATION_DAY_KWH,
886 capability.getCapabilityState().getGenerationMeterEnergySensorEnergyPerDayInKWhState(), capability);
889 private void updateTwoWayMeterEnergyConsumptionSensorChannels(CapabilityDTO capability) {
890 updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_MONTH_KWH,
891 capability.getCapabilityState().getTwoWayMeterEnergyConsumptionSensorEnergyPerMonthInKWhState(),
893 updateStateForEnergyChannelKiloWattHour(CHANNEL_TOTAL_ENERGY,
894 capability.getCapabilityState().getTwoWayMeterEnergyConsumptionSensorTotalEnergyState(), capability);
895 updateStateForEnergyChannelEuro(CHANNEL_ENERGY_MONTH_EURO,
896 capability.getCapabilityState().getTwoWayMeterEnergyConsumptionSensorEnergyPerMonthInEuroState(),
898 updateStateForEnergyChannelEuro(CHANNEL_ENERGY_DAY_EURO,
899 capability.getCapabilityState().getTwoWayMeterEnergyConsumptionSensorEnergyPerDayInEuroState(),
901 updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_DAY_KWH,
902 capability.getCapabilityState().getTwoWayMeterEnergyConsumptionSensorEnergyPerDayInKWhState(),
906 private void updateTwoWayMeterEnergyFeedSensorChannels(CapabilityDTO capability) {
907 updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_FEED_MONTH_KWH,
908 capability.getCapabilityState().getTwoWayMeterEnergyFeedSensorEnergyPerMonthInKWhState(), capability);
909 updateStateForEnergyChannelKiloWattHour(CHANNEL_TOTAL_ENERGY_FED,
910 capability.getCapabilityState().getTwoWayMeterEnergyFeedSensorTotalEnergyState(), capability);
911 updateStateForEnergyChannelEuro(CHANNEL_ENERGY_FEED_MONTH_EURO,
912 capability.getCapabilityState().getTwoWayMeterEnergyFeedSensorEnergyPerMonthInEuroState(), capability);
913 updateStateForEnergyChannelEuro(CHANNEL_ENERGY_FEED_DAY_EURO,
914 capability.getCapabilityState().getTwoWayMeterEnergyFeedSensorEnergyPerDayInEuroState(), capability);
915 updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_FEED_DAY_KWH,
916 capability.getCapabilityState().getTwoWayMeterEnergyFeedSensorEnergyPerDayInKWhState(), capability);
919 private void updateStateForEnergyChannelEuro(final String channelId, @Nullable final Double state,
920 final CapabilityDTO capability) {
922 updateState(channelId, new DecimalType(state));
924 logStateNull(capability);
928 private void updateStateForEnergyChannelWatt(final String channelId, @Nullable final Double state,
929 final CapabilityDTO capability) {
931 updateState(channelId, QuantityType.valueOf(state, Units.WATT));
933 logStateNull(capability);
937 private void updateStateForEnergyChannelKiloWattHour(final String channelId, @Nullable final Double state,
938 final CapabilityDTO capability) {
940 updateState(channelId, QuantityType.valueOf(state, Units.KILOWATT_HOUR));
942 logStateNull(capability);
947 * Returns the inverted value. Currently only rollershutter channels are supported.
949 * @param value value to become inverted
950 * @return the value or the inverted value
952 private int invertRollerShutterValueIfConfigured(final int value) {
954 final Channel channel = getThing().getChannel(CHANNEL_ROLLERSHUTTER);
955 if (channel == null) {
956 logger.debug("Channel {} was null! Value not inverted.", CHANNEL_ROLLERSHUTTER);
959 final Boolean invert = (Boolean) channel.getConfiguration().get(INVERT_CHANNEL_PARAMETER);
960 if (invert != null && invert) {
967 * Returns the {@link DeviceDTO} associated with this {@link LivisiDeviceHandler} (referenced by the
968 * {@link LivisiDeviceHandler#deviceId}).
970 * @return the {@link DeviceDTO} or null, if not found or no {@link LivisiBridgeHandler} is available
972 private Optional<DeviceDTO> getDevice() {
973 return getBridgeHandler().flatMap(bridgeHandler -> bridgeHandler.getDeviceById(deviceId));
976 private Optional<LivisiBridgeHandler> registerAtBridgeHandler() {
977 synchronized (this.lock) {
978 if (this.bridgeHandler == null) {
980 final Bridge bridge = getBridge();
981 if (bridge == null) {
982 return Optional.empty();
985 final ThingHandler handler = bridge.getHandler();
986 if (handler instanceof LivisiBridgeHandler) {
987 LivisiBridgeHandler bridgeHandler = (LivisiBridgeHandler) handler;
988 bridgeHandler.registerDeviceStatusListener(deviceId, this);
989 this.bridgeHandler = bridgeHandler;
991 return Optional.empty(); // also called when the handler is NULL
994 return getBridgeHandler();
999 * Returns the LIVISI bridge handler.
1001 * @return the {@link LivisiBridgeHandler} or null
1003 private Optional<LivisiBridgeHandler> getBridgeHandler() {
1004 return Optional.ofNullable(this.bridgeHandler);
1007 private boolean isBridgeOnline() {
1009 Bridge bridge = getBridge();
1010 if (bridge != null) {
1011 return ThingStatus.ONLINE == bridge.getStatus();
1016 private void logStateNull(CapabilityDTO capability) {
1017 logger.debug("State for {} is STILL NULL!! cstate-id: {}, capability-id: {}", capability.getType(),
1018 capability.getCapabilityState().getId(), capability.getId());
1021 private static OpenClosedType toOpenClosedType(boolean isOpen) {
1023 return OpenClosedType.OPEN;
1025 return OpenClosedType.CLOSED;