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 dimLevel) {
136 bridgeHandler.commandSetDimLevel(deviceId, dimLevel.intValue());
137 } else if (command instanceof OnOffType) {
138 if (OnOffType.ON.equals(command)) {
139 bridgeHandler.commandSetDimLevel(deviceId, 100);
141 bridgeHandler.commandSetDimLevel(deviceId, 0);
146 private void commandRollerShutter(Command command, LivisiBridgeHandler bridgeHandler) {
147 if (command instanceof DecimalType rollerShutterLevel) {
148 bridgeHandler.commandSetRollerShutterLevel(deviceId,
149 invertRollerShutterValueIfConfigured(rollerShutterLevel.intValue()));
150 } else if (command instanceof OnOffType) {
151 if (OnOffType.ON.equals(command)) {
152 bridgeHandler.commandSetRollerShutterStop(deviceId, ShutterActionType.DOWN);
154 bridgeHandler.commandSetRollerShutterStop(deviceId, ShutterActionType.UP);
156 } else if (command instanceof UpDownType) {
157 if (UpDownType.DOWN.equals(command)) {
158 bridgeHandler.commandSetRollerShutterStop(deviceId, ShutterActionType.DOWN);
160 bridgeHandler.commandSetRollerShutterStop(deviceId, ShutterActionType.UP);
162 } else if (command instanceof StopMoveType) {
163 if (StopMoveType.STOP.equals(command)) {
164 bridgeHandler.commandSetRollerShutterStop(deviceId, ShutterActionType.STOP);
169 private void commandUpdatePointTemperature(Command command, LivisiBridgeHandler bridgeHandler) {
170 if (command instanceof QuantityType temperatureCommand) {
171 final QuantityType<?> pointTemperatureCommand = temperatureCommand.toUnit(SIUnits.CELSIUS);
172 if (pointTemperatureCommand != null) {
173 commandUpdatePointTemperature(pointTemperatureCommand.doubleValue(), bridgeHandler);
175 } else if (command instanceof DecimalType temperatureCommand) {
176 commandUpdatePointTemperature(temperatureCommand.doubleValue(), bridgeHandler);
180 private void commandUpdatePointTemperature(double pointTemperature, LivisiBridgeHandler bridgeHandler) {
181 if (pointTemperature < MIN_TEMPERATURE_CELSIUS) {
182 pointTemperature = MIN_TEMPERATURE_CELSIUS;
184 "pointTemperature set to value {} (instead of value '{}'), because it is the minimal possible value!",
185 MIN_TEMPERATURE_CELSIUS, pointTemperature);
186 } else if (pointTemperature > MAX_TEMPERATURE_CELSIUS) {
187 pointTemperature = MAX_TEMPERATURE_CELSIUS;
189 "pointTemperature set to value {} (instead of value '{}'), because it is the maximal possible value!",
190 MAX_TEMPERATURE_CELSIUS, pointTemperature);
192 bridgeHandler.commandUpdatePointTemperature(deviceId, pointTemperature);
195 private void commandSetOperationMode(Command command, LivisiBridgeHandler bridgeHandler) {
196 if (command instanceof StringType) {
197 final String autoModeCommand = command.toString();
199 if (CapabilityStateDTO.STATE_VALUE_OPERATION_MODE_AUTO.equals(autoModeCommand)) {
200 bridgeHandler.commandSetOperationMode(deviceId, true);
201 } else if (CapabilityStateDTO.STATE_VALUE_OPERATION_MODE_MANUAL.equals(autoModeCommand)) {
202 bridgeHandler.commandSetOperationMode(deviceId, false);
204 logger.warn("Could not set operationMode. Invalid value '{}'! Only '{}' or '{}' allowed.",
205 autoModeCommand, CapabilityStateDTO.STATE_VALUE_OPERATION_MODE_AUTO,
206 CapabilityStateDTO.STATE_VALUE_OPERATION_MODE_MANUAL);
211 private void commandSwitchAlarm(Command command, LivisiBridgeHandler bridgeHandler) {
212 if (command instanceof OnOffType) {
213 bridgeHandler.commandSwitchAlarm(deviceId, OnOffType.ON.equals(command));
218 public void initialize() {
219 logger.debug("Initializing LIVISI SmartHome device handler.");
220 initializeThing(isBridgeOnline());
224 public void dispose() {
225 unregisterListeners(bridgeHandler, deviceId);
228 private static void unregisterListeners(@Nullable LivisiBridgeHandler bridgeHandler, String deviceId) {
229 if (bridgeHandler != null) {
230 bridgeHandler.unregisterDeviceStatusListener(deviceId);
235 public void bridgeStatusChanged(final ThingStatusInfo bridgeStatusInfo) {
236 logger.debug("bridgeStatusChanged {}", bridgeStatusInfo);
237 initializeThing(ThingStatus.ONLINE == bridgeStatusInfo.getStatus());
241 * Initializes the {@link Thing} corresponding to the given status of the bridge.
243 * @param isBridgeOnline true if the bridge thing is online, otherwise false
245 private void initializeThing(final boolean isBridgeOnline) {
246 logger.debug("initializeThing thing {} bridge online status: {}", getThing().getUID(), isBridgeOnline);
247 final String configDeviceId = (String) getConfig().get(PROPERTY_ID);
248 if (configDeviceId != null) {
249 deviceId = configDeviceId;
251 Optional<LivisiBridgeHandler> bridgeHandler = registerAtBridgeHandler();
252 if (bridgeHandler.isPresent()) {
253 if (isBridgeOnline) {
254 initializeProperties();
256 Optional<DeviceDTO> deviceOptional = getDevice();
257 if (deviceOptional.isPresent()) {
258 DeviceDTO device = deviceOptional.get();
259 if (device.isReachable() != null && !device.isReachable()) {
260 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
261 "@text/error.notReachable");
263 updateStatus(ThingStatus.ONLINE);
266 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE, "@text/error.deviceNotFound");
269 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
272 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
273 "@text/error.bridgeHandlerMissing");
276 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/error.deviceIdUnknown");
281 * Initializes all properties of the {@link DeviceDTO}, like vendor, serialnumber etc.
283 private void initializeProperties() {
284 synchronized (this.lock) {
285 final Optional<DeviceDTO> deviceOptional = getDevice();
286 if (deviceOptional.isPresent()) {
287 DeviceDTO device = deviceOptional.get();
289 final Map<String, String> properties = editProperties();
290 properties.put(PROPERTY_ID, device.getId());
291 properties.put(PROPERTY_PROTOCOL_ID, device.getConfig().getProtocolId());
292 if (device.hasSerialNumber()) {
293 properties.put(Thing.PROPERTY_SERIAL_NUMBER, device.getSerialNumber());
295 properties.put(Thing.PROPERTY_VENDOR, device.getManufacturer());
296 properties.put(PROPERTY_VERSION, device.getVersion());
297 if (device.hasLocation()) {
298 properties.put(PROPERTY_LOCATION, device.getLocation().getName());
300 if (device.isBatteryPowered()) {
301 properties.put(PROPERTY_BATTERY_POWERED, "yes");
303 properties.put(PROPERTY_BATTERY_POWERED, "no");
305 if (device.isController()) {
306 properties.put(PROPERTY_DEVICE_TYPE, "Controller");
307 } else if (device.isVirtualDevice()) {
308 properties.put(PROPERTY_DEVICE_TYPE, "Virtual");
309 } else if (device.isRadioDevice()) {
310 properties.put(PROPERTY_DEVICE_TYPE, "Radio");
314 if (DEVICE_RST.equals(device.getType()) || DEVICE_RST2.equals(device.getType())
315 || DEVICE_WRT.equals(device.getType())) {
316 properties.put(PROPERTY_DISPLAY_CURRENT_TEMPERATURE,
317 device.getConfig().getDisplayCurrentTemperature());
321 if (DEVICE_ANALOG_METER.equals(device.getType()) || DEVICE_GENERATION_METER.equals(device.getType())
322 || DEVICE_SMART_METER.equals(device.getType())
323 || DEVICE_TWO_WAY_METER.equals(device.getType())) {
324 properties.put(PROPERTY_METER_ID, device.getConfig().getMeterId());
325 properties.put(PROPERTY_METER_FIRMWARE_VERSION, device.getConfig().getMeterFirmwareVersion());
328 if (device.getConfig().getTimeOfAcceptance() != null) {
329 properties.put(PROPERTY_TIME_OF_ACCEPTANCE, device.getConfig().getTimeOfAcceptance()
330 .format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)));
332 if (device.getConfig().getTimeOfDiscovery() != null) {
333 properties.put(PROPERTY_TIME_OF_DISCOVERY, device.getConfig().getTimeOfDiscovery()
334 .format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)));
337 updateProperties(properties);
339 onDeviceStateChanged(device);
341 logger.debug("initializeProperties: The device with id {} isn't found", deviceId);
347 public void onDeviceStateChanged(final DeviceDTO device) {
348 synchronized (this.lock) {
349 updateChannels(device, false);
354 public void onDeviceStateChanged(final DeviceDTO device, final EventDTO event) {
355 synchronized (this.lock) {
356 if (event.isLinkedtoCapability()) {
357 final String linkedCapabilityId = event.getSourceId();
359 CapabilityDTO capability = device.getCapabilityMap().get(linkedCapabilityId);
360 if (capability != null) {
361 logger.trace("Loaded Capability {}, {} with id {}, device {} from device id {}",
362 capability.getType(), capability.getName(), capability.getId(), capability.getDeviceLink(),
365 if (capability.hasState()) {
366 boolean deviceChanged = updateDevice(event, capability);
368 updateChannels(device, true);
371 logger.debug("Capability {} has no state (yet?) - refreshing device.", capability.getName());
373 Optional<DeviceDTO> deviceOptional = refreshDevice(linkedCapabilityId);
374 deviceOptional.ifPresent((d) -> updateChannels(d, true));
377 } else if (event.isLinkedtoDevice()) {
378 if (device.hasDeviceState()) {
379 updateChannels(device, true);
381 logger.debug("Device {}/{} has no state.", device.getConfig().getName(), device.getId());
387 private Optional<DeviceDTO> refreshDevice(String linkedCapabilityId) {
388 Optional<LivisiBridgeHandler> bridgeHandler = registerAtBridgeHandler();
389 Optional<DeviceDTO> deviceOptional = bridgeHandler.flatMap(bh -> bh.refreshDevice(deviceId));
390 if (deviceOptional.isPresent()) {
391 DeviceDTO device = deviceOptional.get();
392 CapabilityDTO capability = device.getCapabilityMap().get(linkedCapabilityId);
393 if (capability != null && capability.hasState()) {
394 return Optional.of(device);
397 return Optional.empty();
400 private boolean updateDevice(EventDTO event, CapabilityDTO capability) {
401 CapabilityStateDTO capabilityState = capability.getCapabilityState();
404 if (capability.isTypeVariableActuator()) {
405 capabilityState.setVariableActuatorState(event.getProperties().getValue());
408 } else if (capability.isTypeSwitchActuator()) {
409 capabilityState.setSwitchActuatorState(event.getProperties().getOnState());
412 } else if (capability.isTypeDimmerActuator()) {
413 capabilityState.setDimmerActuatorState(event.getProperties().getDimLevel());
415 // RollerShutterActuator
416 } else if (capability.isTypeRollerShutterActuator()) {
417 capabilityState.setRollerShutterActuatorState(event.getProperties().getShutterLevel());
420 } else if (capability.isTypeTemperatureSensor()) {
421 // when values are changed, they come with separate events
422 // values should only updated when they are not null
423 final Double temperature = event.getProperties().getTemperature();
424 final Boolean frostWarning = event.getProperties().getFrostWarning();
425 if (temperature != null) {
426 capabilityState.setTemperatureSensorTemperatureState(temperature);
428 if (frostWarning != null) {
429 capabilityState.setTemperatureSensorFrostWarningState(frostWarning);
432 // ThermostatActuator
433 } else if (capability.isTypeThermostatActuator()) {
434 // when values are changed, they come with separate events
435 // values should only updated when they are not null
437 final Double pointTemperature = event.getProperties().getPointTemperature();
438 final String operationMode = event.getProperties().getOperationMode();
439 final Boolean windowReductionActive = event.getProperties().getWindowReductionActive();
441 if (pointTemperature != null) {
442 capabilityState.setThermostatActuatorPointTemperatureState(pointTemperature);
444 if (operationMode != null) {
445 capabilityState.setThermostatActuatorOperationModeState(operationMode);
447 if (windowReductionActive != null) {
448 capabilityState.setThermostatActuatorWindowReductionActiveState(windowReductionActive);
452 } else if (capability.isTypeHumiditySensor()) {
453 // when values are changed, they come with separate events
454 // values should only updated when they are not null
455 final Double humidity = event.getProperties().getHumidity();
456 final Boolean moldWarning = event.getProperties().getMoldWarning();
457 if (humidity != null) {
458 capabilityState.setHumiditySensorHumidityState(humidity);
460 if (moldWarning != null) {
461 capabilityState.setHumiditySensorMoldWarningState(moldWarning);
465 } else if (capability.isTypeWindowDoorSensor()) {
466 capabilityState.setWindowDoorSensorState(event.getProperties().getIsOpen());
468 // SmokeDetectorSensor
469 } else if (capability.isTypeSmokeDetectorSensor()) {
470 capabilityState.setSmokeDetectorSensorState(event.getProperties().getIsSmokeAlarm());
473 } else if (capability.isTypeAlarmActuator()) {
474 capabilityState.setAlarmActuatorState(event.getProperties().getOnState());
476 // MotionDetectionSensor
477 } else if (capability.isTypeMotionDetectionSensor()) {
478 capabilityState.setMotionDetectionSensorState(event.getProperties().getMotionDetectedCount());
481 } else if (capability.isTypeLuminanceSensor()) {
482 capabilityState.setLuminanceSensorState(event.getProperties().getLuminance());
485 } else if (capability.isTypePushButtonSensor()) {
486 if (event.isButtonPressedEvent()) {
487 // Some devices send both StateChanged and ButtonPressed. But only the ButtonPressed should be handled,
488 // therefore it is checked for button pressed event (button index is set).
489 EventPropertiesDTO properties = event.getProperties();
490 capabilityState.setPushButtonSensorButtonIndexState(properties.getKeyPressButtonIndex());
491 capabilityState.setPushButtonSensorButtonIndexType(properties.getKeyPressType());
492 capabilityState.setPushButtonSensorCounterState(properties.getKeyPressCounter());
495 // EnergyConsumptionSensor
496 } else if (capability.isTypeEnergyConsumptionSensor()) {
497 capabilityState.setEnergyConsumptionSensorEnergyConsumptionMonthKWhState(
498 event.getProperties().getEnergyConsumptionMonthKWh());
499 capabilityState.setEnergyConsumptionSensorAbsoluteEnergyConsumptionState(
500 event.getProperties().getAbsoluteEnergyConsumption());
501 capabilityState.setEnergyConsumptionSensorEnergyConsumptionMonthEuroState(
502 event.getProperties().getEnergyConsumptionMonthEuro());
503 capabilityState.setEnergyConsumptionSensorEnergyConsumptionDayEuroState(
504 event.getProperties().getEnergyConsumptionDayEuro());
505 capabilityState.setEnergyConsumptionSensorEnergyConsumptionDayKWhState(
506 event.getProperties().getEnergyConsumptionDayKWh());
508 // PowerConsumptionSensor
509 } else if (capability.isTypePowerConsumptionSensor()) {
510 capabilityState.setPowerConsumptionSensorPowerConsumptionWattState(
511 event.getProperties().getPowerConsumptionWatt());
513 // GenerationMeterEnergySensor
514 } else if (capability.isTypeGenerationMeterEnergySensor()) {
515 capabilityState.setGenerationMeterEnergySensorEnergyPerMonthInKWhState(
516 event.getProperties().getEnergyPerMonthInKWh());
517 capabilityState.setGenerationMeterEnergySensorTotalEnergyState(event.getProperties().getTotalEnergy());
518 capabilityState.setGenerationMeterEnergySensorEnergyPerMonthInEuroState(
519 event.getProperties().getEnergyPerMonthInEuro());
520 capabilityState.setGenerationMeterEnergySensorEnergyPerDayInEuroState(
521 event.getProperties().getEnergyPerDayInEuro());
523 .setGenerationMeterEnergySensorEnergyPerDayInKWhState(event.getProperties().getEnergyPerDayInKWh());
525 // GenerationMeterPowerConsumptionSensor
526 } else if (capability.isTypeGenerationMeterPowerConsumptionSensor()) {
528 .setGenerationMeterPowerConsumptionSensorPowerInWattState(event.getProperties().getPowerInWatt());
530 // TwoWayMeterEnergyConsumptionSensor
531 } else if (capability.isTypeTwoWayMeterEnergyConsumptionSensor()) {
532 capabilityState.setTwoWayMeterEnergyConsumptionSensorEnergyPerMonthInKWhState(
533 event.getProperties().getEnergyPerMonthInKWh());
535 .setTwoWayMeterEnergyConsumptionSensorTotalEnergyState(event.getProperties().getTotalEnergy());
536 capabilityState.setTwoWayMeterEnergyConsumptionSensorEnergyPerMonthInEuroState(
537 event.getProperties().getEnergyPerMonthInEuro());
538 capabilityState.setTwoWayMeterEnergyConsumptionSensorEnergyPerDayInEuroState(
539 event.getProperties().getEnergyPerDayInEuro());
540 capabilityState.setTwoWayMeterEnergyConsumptionSensorEnergyPerDayInKWhState(
541 event.getProperties().getEnergyPerDayInKWh());
543 // TwoWayMeterEnergyFeedSensor
544 } else if (capability.isTypeTwoWayMeterEnergyFeedSensor()) {
545 capabilityState.setTwoWayMeterEnergyFeedSensorEnergyPerMonthInKWhState(
546 event.getProperties().getEnergyPerMonthInKWh());
547 capabilityState.setTwoWayMeterEnergyFeedSensorTotalEnergyState(event.getProperties().getTotalEnergy());
548 capabilityState.setTwoWayMeterEnergyFeedSensorEnergyPerMonthInEuroState(
549 event.getProperties().getEnergyPerMonthInEuro());
550 capabilityState.setTwoWayMeterEnergyFeedSensorEnergyPerDayInEuroState(
551 event.getProperties().getEnergyPerDayInEuro());
553 .setTwoWayMeterEnergyFeedSensorEnergyPerDayInKWhState(event.getProperties().getEnergyPerDayInKWh());
555 // TwoWayMeterPowerConsumptionSensor
556 } else if (capability.isTypeTwoWayMeterPowerConsumptionSensor()) {
558 .setTwoWayMeterPowerConsumptionSensorPowerInWattState(event.getProperties().getPowerInWatt());
561 logger.debug("Unsupported capability type {}.", capability.getType());
567 private void updateChannels(DeviceDTO device, boolean isChangedByEvent) {
569 final boolean isReachable = updateStatus(device);
572 updateDeviceChannels(device);
575 for (final Entry<String, CapabilityDTO> entry : device.getCapabilityMap().entrySet()) {
576 final CapabilityDTO capability = entry.getValue();
578 logger.debug("->capability:{} ({}/{})", capability.getId(), capability.getType(), capability.getName());
580 if (capability.hasState()) {
581 updateCapabilityChannels(device, capability, isChangedByEvent);
583 logger.debug("Capability not available for device {} ({})", device.getConfig().getName(),
590 private boolean updateStatus(DeviceDTO device) {
591 Boolean reachable = device.isReachable();
592 if (reachable != null) {
594 updateStatus(ThingStatus.ONLINE);
596 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.notReachable");
603 private void updateDeviceChannels(DeviceDTO device) {
604 if (device.isBatteryPowered()) {
605 updateState(CHANNEL_BATTERY_LOW, OnOffType.from(device.hasLowBattery()));
609 private void updateCapabilityChannels(DeviceDTO device, CapabilityDTO capability, boolean isChangedByEvent) {
610 switch (capability.getType()) {
611 case CapabilityDTO.TYPE_VARIABLEACTUATOR:
612 updateVariableActuatorChannels(capability);
614 case CapabilityDTO.TYPE_SWITCHACTUATOR:
615 updateSwitchActuatorChannels(capability);
617 case CapabilityDTO.TYPE_DIMMERACTUATOR:
618 updateDimmerActuatorChannels(capability);
620 case CapabilityDTO.TYPE_ROLLERSHUTTERACTUATOR:
621 updateRollerShutterActuatorChannels(capability);
623 case CapabilityDTO.TYPE_TEMPERATURESENSOR:
624 updateTemperatureSensorChannels(capability);
626 case CapabilityDTO.TYPE_THERMOSTATACTUATOR:
627 updateThermostatActuatorChannels(device, capability);
629 case CapabilityDTO.TYPE_HUMIDITYSENSOR:
630 updateHumiditySensorChannels(capability);
632 case CapabilityDTO.TYPE_WINDOWDOORSENSOR:
633 updateWindowDoorSensorChannels(capability);
635 case CapabilityDTO.TYPE_SMOKEDETECTORSENSOR:
636 updateSmokeDetectorChannels(capability);
638 case CapabilityDTO.TYPE_ALARMACTUATOR:
639 updateAlarmActuatorChannels(capability);
641 case CapabilityDTO.TYPE_MOTIONDETECTIONSENSOR:
642 updateMotionDetectionSensorChannels(capability);
644 case CapabilityDTO.TYPE_LUMINANCESENSOR:
645 updateLuminanceSensorChannels(capability);
647 case CapabilityDTO.TYPE_PUSHBUTTONSENSOR:
648 updatePushButtonSensorChannels(capability, isChangedByEvent);
650 case CapabilityDTO.TYPE_ENERGYCONSUMPTIONSENSOR:
651 updateEnergyConsumptionSensorChannels(capability);
653 case CapabilityDTO.TYPE_POWERCONSUMPTIONSENSOR:
654 updateStateForEnergyChannelWatt(CHANNEL_POWER_CONSUMPTION_WATT,
655 capability.getCapabilityState().getPowerConsumptionSensorPowerConsumptionWattState(),
658 case CapabilityDTO.TYPE_GENERATIONMETERENERGYSENSOR:
659 updateGenerationMeterEnergySensorChannels(capability);
661 case CapabilityDTO.TYPE_GENERATIONMETERPOWERCONSUMPTIONSENSOR:
662 updateStateForEnergyChannelWatt(CHANNEL_POWER_GENERATION_WATT,
663 capability.getCapabilityState().getGenerationMeterPowerConsumptionSensorPowerInWattState(),
666 case CapabilityDTO.TYPE_TWOWAYMETERENERGYCONSUMPTIONSENSOR:
667 updateTwoWayMeterEnergyConsumptionSensorChannels(capability);
669 case CapabilityDTO.TYPE_TWOWAYMETERENERGYFEEDSENSOR:
670 updateTwoWayMeterEnergyFeedSensorChannels(capability);
672 case CapabilityDTO.TYPE_TWOWAYMETERPOWERCONSUMPTIONSENSOR:
673 updateStateForEnergyChannelWatt(CHANNEL_POWER_WATT,
674 capability.getCapabilityState().getTwoWayMeterPowerConsumptionSensorPowerInWattState(),
678 logger.debug("Unsupported capability type {}.", capability.getType());
683 private void updateVariableActuatorChannels(CapabilityDTO capability) {
684 final Boolean variableActuatorState = capability.getCapabilityState().getVariableActuatorState();
685 if (variableActuatorState != null) {
686 updateState(CHANNEL_SWITCH, OnOffType.from(variableActuatorState));
688 logStateNull(capability);
692 private void updateSwitchActuatorChannels(CapabilityDTO capability) {
693 final Boolean switchActuatorState = capability.getCapabilityState().getSwitchActuatorState();
694 if (switchActuatorState != null) {
695 updateState(CHANNEL_SWITCH, OnOffType.from(switchActuatorState));
697 logStateNull(capability);
701 private void updateDimmerActuatorChannels(CapabilityDTO capability) {
702 final Integer dimLevel = capability.getCapabilityState().getDimmerActuatorState();
703 if (dimLevel != null) {
704 logger.debug("Dimlevel state {}", dimLevel);
705 updateState(CHANNEL_DIMMER, new PercentType(dimLevel));
707 logStateNull(capability);
711 private void updateRollerShutterActuatorChannels(CapabilityDTO capability) {
712 Integer rollerShutterLevel = capability.getCapabilityState().getRollerShutterActuatorState();
713 if (rollerShutterLevel != null) {
714 rollerShutterLevel = invertRollerShutterValueIfConfigured(rollerShutterLevel);
715 logger.debug("RollerShutterlevel state {}", rollerShutterLevel);
716 updateState(CHANNEL_ROLLERSHUTTER, new PercentType(rollerShutterLevel));
718 logStateNull(capability);
722 private void updateTemperatureSensorChannels(CapabilityDTO capability) {
724 final Double temperature = capability.getCapabilityState().getTemperatureSensorTemperatureState();
725 if (temperature != null) {
726 logger.debug("-> Temperature sensor state: {}", temperature);
727 updateState(CHANNEL_CURRENT_TEMPERATURE, QuantityType.valueOf(temperature, SIUnits.CELSIUS));
729 logStateNull(capability);
733 final Boolean frostWarning = capability.getCapabilityState().getTemperatureSensorFrostWarningState();
734 if (frostWarning != null) {
735 updateState(CHANNEL_FROST_WARNING, OnOffType.from(frostWarning));
737 logStateNull(capability);
741 private void updateThermostatActuatorChannels(DeviceDTO device, CapabilityDTO capability) {
743 final Double pointTemperature = capability.getCapabilityState().getThermostatActuatorPointTemperatureState();
744 if (pointTemperature != null) {
745 logger.debug("Update CHANNEL_SET_TEMPERATURE: state:{} (DeviceName {}, Capab-ID:{})", pointTemperature,
746 device.getConfig().getName(), capability.getId());
747 updateState(CHANNEL_TARGET_TEMPERATURE, QuantityType.valueOf(pointTemperature, SIUnits.CELSIUS));
749 logStateNull(capability);
753 final String operationMode = capability.getCapabilityState().getThermostatActuatorOperationModeState();
754 if (operationMode != null) {
755 updateState(CHANNEL_OPERATION_MODE, new StringType(operationMode));
757 logStateNull(capability);
760 // window reduction active
761 final Boolean windowReductionActive = capability.getCapabilityState()
762 .getThermostatActuatorWindowReductionActiveState();
763 if (windowReductionActive != null) {
764 updateState(CHANNEL_WINDOW_REDUCTION_ACTIVE, OnOffType.from(windowReductionActive));
766 logStateNull(capability);
770 private void updateHumiditySensorChannels(CapabilityDTO capability) {
772 final Double humidity = capability.getCapabilityState().getHumiditySensorHumidityState();
773 if (humidity != null) {
774 updateState(CHANNEL_HUMIDITY, QuantityType.valueOf(humidity, Units.PERCENT));
776 logStateNull(capability);
780 final Boolean moldWarning = capability.getCapabilityState().getHumiditySensorMoldWarningState();
781 if (moldWarning != null) {
782 updateState(CHANNEL_MOLD_WARNING, OnOffType.from(moldWarning));
784 logStateNull(capability);
788 private void updateWindowDoorSensorChannels(CapabilityDTO capability) {
789 final Boolean contactState = capability.getCapabilityState().getWindowDoorSensorState();
790 if (contactState != null) {
791 updateState(CHANNEL_CONTACT, toOpenClosedType(contactState));
793 logStateNull(capability);
797 private void updateSmokeDetectorChannels(CapabilityDTO capability) {
798 final Boolean smokeState = capability.getCapabilityState().getSmokeDetectorSensorState();
799 if (smokeState != null) {
800 updateState(CHANNEL_SMOKE, OnOffType.from(smokeState));
802 logStateNull(capability);
806 private void updateAlarmActuatorChannels(CapabilityDTO capability) {
807 final Boolean alarmState = capability.getCapabilityState().getAlarmActuatorState();
808 if (alarmState != null) {
809 updateState(CHANNEL_ALARM, OnOffType.from(alarmState));
811 logStateNull(capability);
815 private void updateMotionDetectionSensorChannels(CapabilityDTO capability) {
816 final Integer motionCount = capability.getCapabilityState().getMotionDetectionSensorState();
817 if (motionCount != null) {
818 logger.debug("Motion state {} -> count {}", motionCount, motionCount);
819 updateState(CHANNEL_MOTION_COUNT, new DecimalType(motionCount));
821 logStateNull(capability);
825 private void updateLuminanceSensorChannels(CapabilityDTO capability) {
826 final Double luminance = capability.getCapabilityState().getLuminanceSensorState();
827 if (luminance != null) {
828 updateState(CHANNEL_LUMINANCE, QuantityType.valueOf(luminance, Units.PERCENT));
830 logStateNull(capability);
834 private void updatePushButtonSensorChannels(CapabilityDTO capability, boolean isChangedByEvent) {
835 final Integer pushCount = capability.getCapabilityState().getPushButtonSensorCounterState();
836 final Integer buttonIndex = capability.getCapabilityState().getPushButtonSensorButtonIndexState();
837 final String type = capability.getCapabilityState().getPushButtonSensorButtonIndexType();
838 logger.debug("Pushbutton index {}, count {}, type {}", buttonIndex, pushCount, type);
839 if (buttonIndex != null && pushCount != null) {
840 if (buttonIndex >= 0 && buttonIndex <= 7) {
841 final int channelIndex = buttonIndex + 1;
842 updateState(String.format(CHANNEL_BUTTON_COUNT, channelIndex), new DecimalType(pushCount));
844 if (isChangedByEvent) {
845 triggerButtonChannels(type, channelIndex);
848 // Button handled so remove state to avoid re-trigger.
849 capability.getCapabilityState().setPushButtonSensorButtonIndexState(null);
850 capability.getCapabilityState().setPushButtonSensorButtonIndexType(null);
853 logStateNull(capability);
857 private void triggerButtonChannels(@Nullable String type, int channelIndex) {
859 if (SHORT_PRESS.equals(type)) {
860 triggerChannel(CHANNEL_BUTTON + channelIndex, CommonTriggerEvents.SHORT_PRESSED);
861 } else if (LONG_PRESS.equals(type)) {
862 triggerChannel(CHANNEL_BUTTON + channelIndex, CommonTriggerEvents.LONG_PRESSED);
865 triggerChannel(CHANNEL_BUTTON + channelIndex, CommonTriggerEvents.PRESSED);
868 private void updateEnergyConsumptionSensorChannels(CapabilityDTO capability) {
869 updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_CONSUMPTION_MONTH_KWH,
870 capability.getCapabilityState().getEnergyConsumptionSensorEnergyConsumptionMonthKWhState(), capability);
871 updateStateForEnergyChannelKiloWattHour(CHANNEL_ABOLUTE_ENERGY_CONSUMPTION,
872 capability.getCapabilityState().getEnergyConsumptionSensorAbsoluteEnergyConsumptionState(), capability);
873 updateStateForEnergyChannelEuro(CHANNEL_ENERGY_CONSUMPTION_MONTH_EURO,
874 capability.getCapabilityState().getEnergyConsumptionSensorEnergyConsumptionMonthEuroState(),
876 updateStateForEnergyChannelEuro(CHANNEL_ENERGY_CONSUMPTION_DAY_EURO,
877 capability.getCapabilityState().getEnergyConsumptionSensorEnergyConsumptionDayEuroState(), capability);
878 updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_CONSUMPTION_DAY_KWH,
879 capability.getCapabilityState().getEnergyConsumptionSensorEnergyConsumptionDayKWhState(), capability);
882 private void updateGenerationMeterEnergySensorChannels(CapabilityDTO capability) {
883 updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_GENERATION_MONTH_KWH,
884 capability.getCapabilityState().getGenerationMeterEnergySensorEnergyPerMonthInKWhState(), capability);
885 updateStateForEnergyChannelKiloWattHour(CHANNEL_TOTAL_ENERGY_GENERATION,
886 capability.getCapabilityState().getGenerationMeterEnergySensorTotalEnergyState(), capability);
887 updateStateForEnergyChannelEuro(CHANNEL_ENERGY_GENERATION_MONTH_EURO,
888 capability.getCapabilityState().getGenerationMeterEnergySensorEnergyPerMonthInEuroState(), capability);
889 updateStateForEnergyChannelEuro(CHANNEL_ENERGY_GENERATION_DAY_EURO,
890 capability.getCapabilityState().getGenerationMeterEnergySensorEnergyPerDayInEuroState(), capability);
891 updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_GENERATION_DAY_KWH,
892 capability.getCapabilityState().getGenerationMeterEnergySensorEnergyPerDayInKWhState(), capability);
895 private void updateTwoWayMeterEnergyConsumptionSensorChannels(CapabilityDTO capability) {
896 updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_MONTH_KWH,
897 capability.getCapabilityState().getTwoWayMeterEnergyConsumptionSensorEnergyPerMonthInKWhState(),
899 updateStateForEnergyChannelKiloWattHour(CHANNEL_TOTAL_ENERGY,
900 capability.getCapabilityState().getTwoWayMeterEnergyConsumptionSensorTotalEnergyState(), capability);
901 updateStateForEnergyChannelEuro(CHANNEL_ENERGY_MONTH_EURO,
902 capability.getCapabilityState().getTwoWayMeterEnergyConsumptionSensorEnergyPerMonthInEuroState(),
904 updateStateForEnergyChannelEuro(CHANNEL_ENERGY_DAY_EURO,
905 capability.getCapabilityState().getTwoWayMeterEnergyConsumptionSensorEnergyPerDayInEuroState(),
907 updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_DAY_KWH,
908 capability.getCapabilityState().getTwoWayMeterEnergyConsumptionSensorEnergyPerDayInKWhState(),
912 private void updateTwoWayMeterEnergyFeedSensorChannels(CapabilityDTO capability) {
913 updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_FEED_MONTH_KWH,
914 capability.getCapabilityState().getTwoWayMeterEnergyFeedSensorEnergyPerMonthInKWhState(), capability);
915 updateStateForEnergyChannelKiloWattHour(CHANNEL_TOTAL_ENERGY_FED,
916 capability.getCapabilityState().getTwoWayMeterEnergyFeedSensorTotalEnergyState(), capability);
917 updateStateForEnergyChannelEuro(CHANNEL_ENERGY_FEED_MONTH_EURO,
918 capability.getCapabilityState().getTwoWayMeterEnergyFeedSensorEnergyPerMonthInEuroState(), capability);
919 updateStateForEnergyChannelEuro(CHANNEL_ENERGY_FEED_DAY_EURO,
920 capability.getCapabilityState().getTwoWayMeterEnergyFeedSensorEnergyPerDayInEuroState(), capability);
921 updateStateForEnergyChannelKiloWattHour(CHANNEL_ENERGY_FEED_DAY_KWH,
922 capability.getCapabilityState().getTwoWayMeterEnergyFeedSensorEnergyPerDayInKWhState(), capability);
925 private void updateStateForEnergyChannelEuro(final String channelId, @Nullable final Double state,
926 final CapabilityDTO capability) {
928 updateState(channelId, new DecimalType(state));
930 logStateNull(capability);
934 private void updateStateForEnergyChannelWatt(final String channelId, @Nullable final Double state,
935 final CapabilityDTO capability) {
937 updateState(channelId, QuantityType.valueOf(state, Units.WATT));
939 logStateNull(capability);
943 private void updateStateForEnergyChannelKiloWattHour(final String channelId, @Nullable final Double state,
944 final CapabilityDTO capability) {
946 updateState(channelId, QuantityType.valueOf(state, Units.KILOWATT_HOUR));
948 logStateNull(capability);
953 * Returns the inverted value. Currently only rollershutter channels are supported.
955 * @param value value to become inverted
956 * @return the value or the inverted value
958 private int invertRollerShutterValueIfConfigured(final int value) {
960 final Channel channel = getThing().getChannel(CHANNEL_ROLLERSHUTTER);
961 if (channel == null) {
962 logger.debug("Channel {} was null! Value not inverted.", CHANNEL_ROLLERSHUTTER);
965 final Boolean invert = (Boolean) channel.getConfiguration().get(INVERT_CHANNEL_PARAMETER);
966 if (invert != null && invert) {
973 * Returns the {@link DeviceDTO} associated with this {@link LivisiDeviceHandler} (referenced by the
974 * {@link LivisiDeviceHandler#deviceId}).
976 * @return the {@link DeviceDTO} or null, if not found or no {@link LivisiBridgeHandler} is available
978 private Optional<DeviceDTO> getDevice() {
979 return getBridgeHandler().flatMap(bridgeHandler -> bridgeHandler.getDeviceById(deviceId));
982 private Optional<LivisiBridgeHandler> registerAtBridgeHandler() {
983 synchronized (this.lock) {
984 if (this.bridgeHandler == null) {
986 final Bridge bridge = getBridge();
987 if (bridge == null) {
988 return Optional.empty();
991 final ThingHandler handler = bridge.getHandler();
992 if (handler instanceof LivisiBridgeHandler bridgeHandler) {
993 bridgeHandler.registerDeviceStatusListener(deviceId, this);
994 this.bridgeHandler = bridgeHandler;
996 return Optional.empty(); // also called when the handler is NULL
999 return getBridgeHandler();
1004 * Returns the LIVISI bridge handler.
1006 * @return the {@link LivisiBridgeHandler} or null
1008 private Optional<LivisiBridgeHandler> getBridgeHandler() {
1009 return Optional.ofNullable(this.bridgeHandler);
1012 private boolean isBridgeOnline() {
1014 Bridge bridge = getBridge();
1015 if (bridge != null) {
1016 return ThingStatus.ONLINE == bridge.getStatus();
1021 private void logStateNull(CapabilityDTO capability) {
1022 logger.debug("State for {} is STILL NULL!! cstate-id: {}, capability-id: {}", capability.getType(),
1023 capability.getCapabilityState().getId(), capability.getId());
1026 private static OpenClosedType toOpenClosedType(boolean isOpen) {
1028 return OpenClosedType.OPEN;
1030 return OpenClosedType.CLOSED;