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.solarwatt.internal.handler;
15 import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.*;
17 import java.math.BigDecimal;
18 import java.util.ArrayList;
19 import java.util.List;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.solarwatt.internal.channel.SolarwattChannelTypeProvider;
25 import org.openhab.binding.solarwatt.internal.domain.model.Device;
26 import org.openhab.binding.solarwatt.internal.domain.model.EVStation;
27 import org.openhab.binding.solarwatt.internal.domain.model.Location;
28 import org.openhab.binding.solarwatt.internal.domain.model.PowerMeter;
29 import org.openhab.core.library.types.QuantityType;
30 import org.openhab.core.library.unit.Units;
31 import org.openhab.core.thing.Thing;
32 import org.openhab.core.types.State;
35 * The concrete device handlers process the location specific commands.
37 * @author Sven Carstens - Initial contribution
40 public class LocationHandler extends SimpleDeviceHandler {
42 public LocationHandler(Thing thing, SolarwattChannelTypeProvider channelTypeProvider) {
43 super(thing, channelTypeProvider);
47 * Add calculated states for unmetered consum.
49 * First calculate and then call super to submit the state
52 protected void updateDeviceChannels() {
53 // add calculated states
54 this.updateCalculated();
57 super.updateDeviceChannels();
61 * Add calculated channels for unmetered consum.
63 * First calculate and then call super to submit the channels
66 protected void initDeviceChannels() {
67 // add calculated channels
68 final EnergyManagerHandler bridgeHandler = this.getEnergyManagerHandler();
69 if (bridgeHandler != null) {
70 // update the unmetered channel via subtracting the outerconsumers
71 // from the powerConsumed
72 Location locationDevice = (Location) this.getDevice();
73 if (locationDevice != null) {
74 locationDevice.addChannel(CHANNEL_POWER_DIRECT_CONSUMED.getChannelName(), Units.WATT,
75 Device.WATT_CATEGORY, false);
76 locationDevice.addChannel(CHANNEL_WORK_DIRECT_CONSUMED.getChannelName(), Units.WATT_HOUR,
77 Device.WATT_HOUR_CATEGORY, false);
78 locationDevice.addChannel(CHANNEL_POWER_CONSUMED_UNMETERED.getChannelName(), Units.WATT,
79 Device.WATT_CATEGORY, false);
80 locationDevice.addChannel(CHANNEL_WORK_CONSUMED_UNMETERED.getChannelName(), Units.WATT_HOUR,
81 Device.WATT_HOUR_CATEGORY, false);
85 // submit all channels
86 super.initDeviceChannels();
89 private void updateCalculated() {
90 final EnergyManagerHandler bridgeHandler = this.getEnergyManagerHandler();
91 final Location locationDevice = (Location) this.getDevice();
92 if (bridgeHandler != null && locationDevice != null) {
93 this.calculateDirectConsumption(locationDevice);
94 this.calculateUnmeteredConsumption(bridgeHandler, locationDevice);
98 private void calculateUnmeteredConsumption(EnergyManagerHandler bridgeHandler, Location locationDevice) {
99 // update the unmetered channels via subtracting
100 // the outerconsumers from the powerConsumed
101 BigDecimal powerConsumed = locationDevice.getBigDecimalFromChannel(CHANNEL_POWER_CONSUMED.getChannelName());
102 BigDecimal workConsumed = locationDevice.getBigDecimalFromChannel(CHANNEL_WORK_CONSUMED.getChannelName());
104 final List<BigDecimal> powerOuter = new ArrayList<>();
105 final List<BigDecimal> workOuter = new ArrayList<>();
107 Set<String> outerConsumers = locationDevice.getDevicesMap().getOuterConsumer();
108 if (outerConsumers != null) {
109 outerConsumers.stream().map(bridgeHandler::getDeviceFromGuid).forEach(outerDevice -> {
110 if (outerDevice instanceof PowerMeter) {
111 powerOuter.add(outerDevice.getBigDecimalFromChannel(CHANNEL_POWER_IN.getChannelName()));
112 workOuter.add(outerDevice.getBigDecimalFromChannel(CHANNEL_WORK_IN.getChannelName()));
113 } else if (outerDevice instanceof EVStation) {
114 powerOuter.add(outerDevice.getBigDecimalFromChannel(CHANNEL_POWER_AC_IN.getChannelName()));
115 workOuter.add(outerDevice.getBigDecimalFromChannel(CHANNEL_WORK_AC_IN.getChannelName()));
119 BigDecimal powerConsumedUnmetered = powerOuter.stream().reduce(powerConsumed, BigDecimal::subtract);
120 if (powerConsumedUnmetered.compareTo(BigDecimal.ONE) > 0) {
121 // sometimes the powerConsumed is exactly the power of the unmetered devices
122 // the resulting zero for unmetered consumption is not correct
123 locationDevice.addStateBigDecimal(CHANNEL_POWER_CONSUMED_UNMETERED, powerConsumedUnmetered, Units.WATT);
125 locationDevice.addStateBigDecimal(CHANNEL_WORK_CONSUMED_UNMETERED,
126 workOuter.stream().reduce(workConsumed, BigDecimal::subtract), Units.WATT_HOUR);
130 private void calculateDirectConsumption(Location locationDevice) {
131 // calculate direct consumption for display purposes
132 locationDevice.addState(CHANNEL_POWER_DIRECT_CONSUMED.getChannelName(),
133 this.calculateQuantityDifference(locationDevice.getState(CHANNEL_POWER_SELF_CONSUMED.getChannelName()),
134 locationDevice.getState(CHANNEL_POWER_BUFFERED_FROM_PRODUCERS.getChannelName())));
136 locationDevice.addState(CHANNEL_WORK_DIRECT_CONSUMED.getChannelName(),
137 this.calculateQuantityDifference(locationDevice.getState(CHANNEL_WORK_SELF_CONSUMED.getChannelName()),
138 locationDevice.getState(CHANNEL_WORK_BUFFERED_FROM_PRODUCERS.getChannelName())));
142 * Helper to generate a new state calculated from the difference between two states.
144 * channelTarget = channelValue - channelSubtract
146 * @param stateValue value from which we subtract
147 * @param stateSubtract value to substrct
148 * @return {@link State} of calculated value
150 private @Nullable State calculateQuantityDifference(@Nullable State stateValue, @Nullable State stateSubtract) {
151 if (stateValue != null && stateSubtract != null) {
152 @SuppressWarnings("rawtypes")
153 QuantityType quantityValue = stateValue.as(QuantityType.class);
154 @SuppressWarnings("rawtypes")
155 QuantityType quantitySubtract = stateSubtract.as(QuantityType.class);
157 if (quantityValue != null && quantitySubtract != null) {
158 return quantityValue.subtract(quantitySubtract);