]> git.basschouten.com Git - openhab-addons.git/blob
91b4ee6baf23108aa2e81a8e09ba5650861a8978
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.senechome.internal;
14
15 import static org.openhab.binding.senechome.internal.SenecHomeBindingConstants.*;
16 import static org.openhab.core.types.RefreshType.REFRESH;
17
18 import java.io.IOException;
19 import java.math.BigDecimal;
20 import java.math.RoundingMode;
21 import java.time.Duration;
22 import java.util.Date;
23 import java.util.concurrent.ExecutionException;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
26 import java.util.concurrent.TimeoutException;
27 import java.util.function.Function;
28
29 import javax.measure.Quantity;
30 import javax.measure.Unit;
31
32 import org.eclipse.jdt.annotation.NonNullByDefault;
33 import org.eclipse.jdt.annotation.Nullable;
34 import org.eclipse.jetty.client.HttpClient;
35 import org.openhab.binding.senechome.internal.json.SenecHomeResponse;
36 import org.openhab.core.cache.ExpiringCache;
37 import org.openhab.core.library.types.DecimalType;
38 import org.openhab.core.library.types.OnOffType;
39 import org.openhab.core.library.types.QuantityType;
40 import org.openhab.core.library.types.StringType;
41 import org.openhab.core.library.unit.SIUnits;
42 import org.openhab.core.library.unit.Units;
43 import org.openhab.core.thing.Channel;
44 import org.openhab.core.thing.ChannelUID;
45 import org.openhab.core.thing.Thing;
46 import org.openhab.core.thing.ThingStatus;
47 import org.openhab.core.thing.ThingStatusDetail;
48 import org.openhab.core.thing.binding.BaseThingHandler;
49 import org.openhab.core.types.Command;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 import com.google.gson.JsonParseException;
54
55 /**
56  * The {@link SenecHomeHandler} is responsible for handling commands, which are
57  * sent to one of the channels.
58  *
59  * @author Steven Schwarznau - Initial contribution
60  * @author Erwin Guib - added more channels, added some convenience methods to reduce code duplication
61  */
62 @NonNullByDefault
63 public class SenecHomeHandler extends BaseThingHandler {
64     private final Logger logger = LoggerFactory.getLogger(SenecHomeHandler.class);
65
66     // divisor to transform from milli to kilo UNIT (e.g. mW => kW)
67     private static final BigDecimal DIVISOR_MILLI_TO_KILO = BigDecimal.valueOf(1000000);
68     // divisor to transform from milli to "iso" UNIT (e.g. mV => V)
69     private static final BigDecimal DIVISOR_MILLI_TO_ISO = BigDecimal.valueOf(1000);
70     // divisor to transform from "iso" to kilo UNIT (e.g. W => kW)
71     private static final BigDecimal DIVISOR_ISO_TO_KILO = BigDecimal.valueOf(1000);
72     // ix (x=1,3,8) types => hex encoded integer value
73     private static final String VALUE_TYPE_INT1 = "i1";
74     public static final String VALUE_TYPE_INT3 = "i3";
75     public static final String VALUE_TYPE_INT8 = "i8";
76     // ux (x=1,3,6,8) types => hex encoded unsigned value
77     private static final String VALUE_TYPE_DECIMAL = "u";
78     // fl => hex encoded float
79     private static final String VALUE_TYPE_FLOAT = "fl";
80     // st => string
81     // public static final String VALUE_TYPE_STRING = "st";
82
83     private @Nullable ScheduledFuture<?> refreshJob;
84     private @Nullable PowerLimitationStatusDTO limitationStatus = null;
85     private final @Nullable SenecHomeApi senecHomeApi;
86     private SenecHomeConfigurationDTO config = new SenecHomeConfigurationDTO();
87     private final ExpiringCache<Boolean> refreshCache = new ExpiringCache<>(Duration.ofSeconds(5), this::refreshState);
88
89     public SenecHomeHandler(Thing thing, HttpClient httpClient) {
90         super(thing);
91         this.senecHomeApi = new SenecHomeApi(httpClient);
92     }
93
94     @Override
95     public void handleRemoval() {
96         stopJobIfRunning();
97         super.handleRemoval();
98     }
99
100     @Override
101     public void handleCommand(ChannelUID channelUID, Command command) {
102         if (command == REFRESH) {
103             logger.debug("Refreshing {}", channelUID);
104             refresh();
105         } else {
106             logger.trace("The SenecHome-Binding is a read-only binding and can not handle commands");
107         }
108     }
109
110     @Override
111     public void dispose() {
112         stopJobIfRunning();
113         super.dispose();
114     }
115
116     protected void stopJobIfRunning() {
117         final ScheduledFuture<?> refreshJob = this.refreshJob;
118         if (refreshJob != null && !refreshJob.isCancelled()) {
119             refreshJob.cancel(true);
120             this.refreshJob = null;
121         }
122     }
123
124     @Override
125     public void initialize() {
126         config = getConfigAs(SenecHomeConfigurationDTO.class);
127         senecHomeApi.setHostname(config.hostname);
128         refreshJob = scheduler.scheduleWithFixedDelay(this::refresh, 0, config.refreshInterval, TimeUnit.SECONDS);
129         limitationStatus = null;
130     }
131
132     private void refresh() {
133         refreshCache.getValue();
134     }
135
136     public @Nullable Boolean refreshState() {
137         SenecHomeResponse response = null;
138         try {
139             response = senecHomeApi.getStatistics();
140             logger.trace("received {}", response);
141
142             BigDecimal pvLimitation = new BigDecimal(100).subtract(getSenecValue(response.power.powerLimitation))
143                     .setScale(0, RoundingMode.HALF_UP);
144             updateState(CHANNEL_SENEC_POWER_LIMITATION, new QuantityType<>(pvLimitation, Units.PERCENT));
145
146             Channel channelLimitationState = getThing().getChannel(CHANNEL_SENEC_POWER_LIMITATION_STATE);
147             if (channelLimitationState != null) {
148                 updatePowerLimitationStatus(channelLimitationState,
149                         (100 - pvLimitation.intValue()) <= config.limitationTresholdValue, config.limitationDuration);
150             }
151             if (response.power.currentPerMpp != null) {
152                 updateQtyState(CHANNEL_SENEC_CURRENT_MPP1, response.power.currentPerMpp[0], 2, Units.AMPERE);
153                 updateQtyState(CHANNEL_SENEC_CURRENT_MPP2, response.power.currentPerMpp[1], 2, Units.AMPERE);
154                 if (response.power.currentPerMpp.length > 2) {
155                     // only Home V3 duo
156                     updateQtyState(CHANNEL_SENEC_CURRENT_MPP3, response.power.currentPerMpp[2], 2, Units.AMPERE);
157                 }
158             }
159             if (response.power.powerPerMpp != null) {
160                 updateQtyState(CHANNEL_SENEC_POWER_MPP1, response.power.powerPerMpp[0], 2, Units.WATT);
161                 updateQtyState(CHANNEL_SENEC_POWER_MPP2, response.power.powerPerMpp[1], 2, Units.WATT);
162                 if (response.power.powerPerMpp.length > 2) {
163                     updateQtyState(CHANNEL_SENEC_POWER_MPP3, response.power.powerPerMpp[2], 2, Units.WATT);
164                 }
165             }
166             if (response.power.voltagePerMpp != null) {
167                 updateQtyState(CHANNEL_SENEC_VOLTAGE_MPP1, response.power.voltagePerMpp[0], 2, Units.VOLT);
168                 updateQtyState(CHANNEL_SENEC_VOLTAGE_MPP2, response.power.voltagePerMpp[1], 2, Units.VOLT);
169                 if (response.power.voltagePerMpp.length > 2) {
170                     updateQtyState(CHANNEL_SENEC_VOLTAGE_MPP3, response.power.voltagePerMpp[2], 2, Units.VOLT);
171                 }
172             }
173
174             updateQtyState(CHANNEL_SENEC_POWER_CONSUMPTION, response.energy.housePowerConsumption, 2, Units.WATT);
175             updateQtyState(CHANNEL_SENEC_ENERGY_PRODUCTION, response.energy.inverterPowerGeneration, 2, Units.WATT);
176             updateQtyState(CHANNEL_SENEC_BATTERY_POWER, response.energy.batteryPower, 2, Units.WATT);
177             updateQtyState(CHANNEL_SENEC_BATTERY_CURRENT, response.energy.batteryCurrent, 2, Units.AMPERE);
178             updateQtyState(CHANNEL_SENEC_BATTERY_VOLTAGE, response.energy.batteryVoltage, 2, Units.VOLT);
179             updateStringStateFromInt(CHANNEL_SENEC_SYSTEM_STATE, response.energy.systemState,
180                     SenecSystemStatus::descriptionFromCode);
181             updateDecimalState(CHANNEL_SENEC_SYSTEM_STATE_VALUE, response.energy.systemState);
182             updateQtyState(CHANNEL_SENEC_BATTERY_FUEL_CHARGE, response.energy.batteryFuelCharge, 0, Units.PERCENT);
183
184             updateGridPowerValues(getSenecValue(response.grid.currentGridValue));
185             updateQtyState(CHANNEL_SENEC_GRID_CURRENT_PH1, response.grid.currentGridCurrentPerPhase[0], 2,
186                     Units.AMPERE);
187             updateQtyState(CHANNEL_SENEC_GRID_CURRENT_PH2, response.grid.currentGridCurrentPerPhase[1], 2,
188                     Units.AMPERE);
189             updateQtyState(CHANNEL_SENEC_GRID_CURRENT_PH3, response.grid.currentGridCurrentPerPhase[2], 2,
190                     Units.AMPERE);
191             updateQtyState(CHANNEL_SENEC_GRID_POWER_PH1, response.grid.currentGridPowerPerPhase[0], 2, Units.WATT);
192             updateQtyState(CHANNEL_SENEC_GRID_POWER_PH2, response.grid.currentGridPowerPerPhase[1], 2, Units.WATT);
193             updateQtyState(CHANNEL_SENEC_GRID_POWER_PH3, response.grid.currentGridPowerPerPhase[2], 2, Units.WATT);
194
195             updateQtyState(CHANNEL_SENEC_GRID_VOLTAGE_PH1, response.grid.currentGridVoltagePerPhase[0], 2, Units.VOLT);
196             updateQtyState(CHANNEL_SENEC_GRID_VOLTAGE_PH2, response.grid.currentGridVoltagePerPhase[1], 2, Units.VOLT);
197             updateQtyState(CHANNEL_SENEC_GRID_VOLTAGE_PH3, response.grid.currentGridVoltagePerPhase[2], 2, Units.VOLT);
198             updateQtyState(CHANNEL_SENEC_GRID_FREQUENCY, response.grid.currentGridFrequency, 2, Units.HERTZ);
199
200             updateQtyState(CHANNEL_SENEC_LIVE_BAT_CHARGE, response.statistics.liveBatCharge, 2, Units.KILOWATT_HOUR);
201             updateQtyState(CHANNEL_SENEC_LIVE_BAT_DISCHARGE, response.statistics.liveBatDischarge, 2,
202                     Units.KILOWATT_HOUR);
203             updateQtyState(CHANNEL_SENEC_LIVE_GRID_IMPORT, response.statistics.liveGridImport, 2, Units.KILOWATT_HOUR);
204             updateQtyState(CHANNEL_SENEC_LIVE_GRID_EXPORT, response.statistics.liveGridExport, 2, Units.KILOWATT_HOUR);
205             updateQtyState(CHANNEL_SENEC_LIVE_HOUSE_CONSUMPTION, response.statistics.liveHouseConsumption, 2,
206                     Units.KILOWATT_HOUR);
207             updateQtyState(CHANNEL_SENEC_LIVE_POWER_GENERATOR, response.statistics.livePowerGenerator, 2,
208                     Units.KILOWATT_HOUR);
209             if (response.statistics.liveWallboxEnergy != null) {
210                 updateQtyState(CHANNEL_SENEC_LIVE_ENERGY_WALLBOX1, response.statistics.liveWallboxEnergy[0], 2,
211                         Units.KILOWATT_HOUR, DIVISOR_ISO_TO_KILO);
212             }
213
214             if (response.battery.chargedEnergy != null) {
215                 updateQtyState(CHANNEL_SENEC_CHARGED_ENERGY_PACK1, response.battery.chargedEnergy[0], 2,
216                         Units.KILOWATT_HOUR, DIVISOR_MILLI_TO_KILO);
217                 updateQtyState(CHANNEL_SENEC_CHARGED_ENERGY_PACK2, response.battery.chargedEnergy[1], 2,
218                         Units.KILOWATT_HOUR, DIVISOR_MILLI_TO_KILO);
219                 updateQtyState(CHANNEL_SENEC_CHARGED_ENERGY_PACK3, response.battery.chargedEnergy[2], 2,
220                         Units.KILOWATT_HOUR, DIVISOR_MILLI_TO_KILO);
221                 updateQtyState(CHANNEL_SENEC_CHARGED_ENERGY_PACK4, response.battery.chargedEnergy[3], 2,
222                         Units.KILOWATT_HOUR, DIVISOR_MILLI_TO_KILO);
223             }
224             if (response.battery.dischargedEnergy != null) {
225                 updateQtyState(CHANNEL_SENEC_DISCHARGED_ENERGY_PACK1, response.battery.dischargedEnergy[0], 2,
226                         Units.KILOWATT_HOUR, DIVISOR_MILLI_TO_KILO);
227                 updateQtyState(CHANNEL_SENEC_DISCHARGED_ENERGY_PACK2, response.battery.dischargedEnergy[1], 2,
228                         Units.KILOWATT_HOUR, DIVISOR_MILLI_TO_KILO);
229                 updateQtyState(CHANNEL_SENEC_DISCHARGED_ENERGY_PACK3, response.battery.dischargedEnergy[2], 2,
230                         Units.KILOWATT_HOUR, DIVISOR_MILLI_TO_KILO);
231                 updateQtyState(CHANNEL_SENEC_DISCHARGED_ENERGY_PACK4, response.battery.dischargedEnergy[3], 2,
232                         Units.KILOWATT_HOUR, DIVISOR_MILLI_TO_KILO);
233             }
234             if (response.battery.cycles != null) {
235                 updateDecimalState(CHANNEL_SENEC_CYCLES_PACK1, response.battery.cycles[0]);
236                 updateDecimalState(CHANNEL_SENEC_CYCLES_PACK2, response.battery.cycles[1]);
237                 updateDecimalState(CHANNEL_SENEC_CYCLES_PACK3, response.battery.cycles[2]);
238                 updateDecimalState(CHANNEL_SENEC_CYCLES_PACK4, response.battery.cycles[3]);
239             }
240             if (response.battery.current != null) {
241                 updateQtyState(CHANNEL_SENEC_CURRENT_PACK1, response.battery.current[0], 2, Units.AMPERE);
242                 updateQtyState(CHANNEL_SENEC_CURRENT_PACK2, response.battery.current[1], 2, Units.AMPERE);
243                 updateQtyState(CHANNEL_SENEC_CURRENT_PACK3, response.battery.current[2], 2, Units.AMPERE);
244                 updateQtyState(CHANNEL_SENEC_CURRENT_PACK4, response.battery.current[3], 2, Units.AMPERE);
245             }
246             if (response.battery.voltage != null) {
247                 updateQtyState(CHANNEL_SENEC_VOLTAGE_PACK1, response.battery.voltage[0], 2, Units.VOLT);
248                 updateQtyState(CHANNEL_SENEC_VOLTAGE_PACK2, response.battery.voltage[1], 2, Units.VOLT);
249                 updateQtyState(CHANNEL_SENEC_VOLTAGE_PACK3, response.battery.voltage[2], 2, Units.VOLT);
250                 updateQtyState(CHANNEL_SENEC_VOLTAGE_PACK4, response.battery.voltage[3], 2, Units.VOLT);
251             }
252             if (response.battery.maxCellVoltage != null) {
253                 updateQtyState(CHANNEL_SENEC_MAX_CELL_VOLTAGE_PACK1, response.battery.maxCellVoltage[0], 3, Units.VOLT,
254                         DIVISOR_MILLI_TO_ISO);
255                 updateQtyState(CHANNEL_SENEC_MAX_CELL_VOLTAGE_PACK2, response.battery.maxCellVoltage[1], 3, Units.VOLT,
256                         DIVISOR_MILLI_TO_ISO);
257                 updateQtyState(CHANNEL_SENEC_MAX_CELL_VOLTAGE_PACK3, response.battery.maxCellVoltage[2], 3, Units.VOLT,
258                         DIVISOR_MILLI_TO_ISO);
259                 updateQtyState(CHANNEL_SENEC_MAX_CELL_VOLTAGE_PACK4, response.battery.maxCellVoltage[3], 3, Units.VOLT,
260                         DIVISOR_MILLI_TO_ISO);
261             }
262             if (response.battery.minCellVoltage != null) {
263                 updateQtyState(CHANNEL_SENEC_MIN_CELL_VOLTAGE_PACK1, response.battery.minCellVoltage[0], 3, Units.VOLT,
264                         DIVISOR_MILLI_TO_ISO);
265                 updateQtyState(CHANNEL_SENEC_MIN_CELL_VOLTAGE_PACK2, response.battery.minCellVoltage[1], 3, Units.VOLT,
266                         DIVISOR_MILLI_TO_ISO);
267                 updateQtyState(CHANNEL_SENEC_MIN_CELL_VOLTAGE_PACK3, response.battery.minCellVoltage[2], 3, Units.VOLT,
268                         DIVISOR_MILLI_TO_ISO);
269                 updateQtyState(CHANNEL_SENEC_MIN_CELL_VOLTAGE_PACK4, response.battery.minCellVoltage[3], 3, Units.VOLT,
270                         DIVISOR_MILLI_TO_ISO);
271             }
272
273             if (response.temperature != null) {
274                 updateQtyState(CHANNEL_SENEC_BATTERY_TEMPERATURE, response.temperature.batteryTemperature, 0,
275                         SIUnits.CELSIUS);
276                 updateQtyState(CHANNEL_SENEC_CASE_TEMPERATURE, response.temperature.caseTemperature, 0,
277                         SIUnits.CELSIUS);
278                 updateQtyState(CHANNEL_SENEC_MCU_TEMPERATURE, response.temperature.mcuTemperature, 0, SIUnits.CELSIUS);
279             }
280
281             if (response.wallbox != null && response.wallbox.state != null) {
282                 updateStringStateFromInt(CHANNEL_SENEC_WALLBOX1_STATE, response.wallbox.state[0],
283                         SenecWallboxStatus::descriptionFromCode);
284                 updateDecimalState(CHANNEL_SENEC_WALLBOX1_STATE_VALUE, response.wallbox.state[0]);
285                 updateQtyState(CHANNEL_SENEC_WALLBOX1_CHARGING_CURRENT_PH1, response.wallbox.l1ChargingCurrent[0], 2,
286                         Units.AMPERE);
287                 updateQtyState(CHANNEL_SENEC_WALLBOX1_CHARGING_CURRENT_PH2, response.wallbox.l2ChargingCurrent[0], 2,
288                         Units.AMPERE);
289                 updateQtyState(CHANNEL_SENEC_WALLBOX1_CHARGING_CURRENT_PH3, response.wallbox.l3ChargingCurrent[0], 2,
290                         Units.AMPERE);
291                 updateQtyState(CHANNEL_SENEC_WALLBOX1_CHARGING_POWER, response.wallbox.chargingPower[0], 2, Units.WATT);
292             }
293
294             updateStatus(ThingStatus.ONLINE);
295         } catch (JsonParseException | IOException | InterruptedException | TimeoutException | ExecutionException e) {
296             if (response == null) {
297                 logger.trace("Faulty response: is null");
298             } else {
299                 logger.trace("Faulty response: {}", response.toString());
300             }
301             logger.warn("Error refreshing source '{}'", getThing().getUID(), e);
302             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
303                     "Could not connect to Senec web interface:" + e.getMessage());
304         }
305
306         return Boolean.TRUE;
307     }
308
309     protected void updateStringStateFromInt(String channelName, String senecValue,
310             Function<Integer, String> converter) {
311         Channel channel = getThing().getChannel(channelName);
312         if (channel != null) {
313             Integer value = getSenecValue(senecValue).intValue();
314             updateState(channel.getUID(), new StringType(converter.apply(value)));
315         }
316     }
317
318     protected void updateDecimalState(String channelName, String senecValue) {
319         Channel channel = getThing().getChannel(channelName);
320         if (channel != null) {
321             BigDecimal value = getSenecValue(senecValue);
322             updateState(channel.getUID(), new DecimalType(value.intValue()));
323         }
324     }
325
326     protected <Q extends Quantity<Q>> void updateQtyState(String channelName, String senecValue, int scale,
327             Unit<Q> unit) {
328         updateQtyState(channelName, senecValue, scale, unit, null);
329     }
330
331     protected <Q extends Quantity<Q>> void updateQtyState(String channelName, String senecValue, int scale,
332             Unit<Q> unit, @Nullable BigDecimal divisor) {
333         Channel channel = getThing().getChannel(channelName);
334         if (channel == null) {
335             return;
336         }
337         BigDecimal value = getSenecValue(senecValue);
338         if (divisor != null) {
339             value = value.divide(divisor, scale, RoundingMode.HALF_UP);
340         } else {
341             value = value.setScale(scale, RoundingMode.HALF_UP);
342         }
343         updateState(channel.getUID(), new QuantityType<Q>(value, unit));
344     }
345
346     protected BigDecimal getSenecValue(String value) {
347         String[] type = value.split("_");
348
349         if (type[0] != null) {
350             if (type[0].startsWith(VALUE_TYPE_DECIMAL)) {
351                 return new BigDecimal(Long.valueOf(type[1], 16));
352             } else if (type[0].startsWith(VALUE_TYPE_INT1)) {
353                 Integer val = Integer.valueOf(type[1], 16);
354                 if ((val & 0x8000) > 0) {
355                     val = val - 0x10000;
356                 }
357                 return new BigDecimal(val);
358             } else if (type[0].startsWith(VALUE_TYPE_INT3)) {
359                 Long val = Long.valueOf(type[1], 16);
360                 if ((Math.abs(val & 0x80000000)) > 0) {
361                     val = val - 0x100000000L;
362                 }
363                 return new BigDecimal(val);
364             } else if (type[0].startsWith(VALUE_TYPE_INT8)) {
365                 Long val = Long.valueOf(type[1], 16);
366                 if ((val & 0x80) > 0) {
367                     val = val - 0x100;
368                 }
369                 return new BigDecimal(val);
370             } else if (VALUE_TYPE_FLOAT.equalsIgnoreCase(type[0])) {
371                 return parseFloatValue(type[1]);
372             }
373         }
374
375         logger.warn("Unknown value type [{}]", type[0]);
376         return BigDecimal.ZERO;
377     }
378
379     /**
380      * Parse the hex coded float value of Senec device and return as BigDecimal
381      *
382      * @param value String with hex float
383      * @return BigDecimal with float value
384      */
385     private static BigDecimal parseFloatValue(String value) {
386         // sample: value = 43E26188
387
388         float f = Float.intBitsToFloat(Integer.parseUnsignedInt(value, 16));
389         return new BigDecimal(f);
390     }
391
392     protected void updatePowerLimitationStatus(Channel channel, boolean status, int duration) {
393         if (this.limitationStatus != null) {
394             if (this.limitationStatus.state == status) {
395                 long stateSince = new Date().getTime() - this.limitationStatus.time;
396
397                 if (((int) (stateSince / 1000)) < duration) {
398                     // skip updating state (possible flapping state)
399                     return;
400                 } else {
401                     logger.debug("{} longer than required duration {}", status, duration);
402                 }
403             } else {
404                 this.limitationStatus.state = status;
405                 this.limitationStatus.time = new Date().getTime();
406
407                 // skip updating state (state changed, possible flapping state)
408                 return;
409             }
410         } else {
411             this.limitationStatus = new PowerLimitationStatusDTO();
412             this.limitationStatus.state = status;
413         }
414
415         logger.debug("Updating power limitation state {}", status);
416         updateState(channel.getUID(), status ? OnOffType.ON : OnOffType.OFF);
417     }
418
419     protected void updateGridPowerValues(BigDecimal gridTotalValue) {
420         BigDecimal gridTotal = gridTotalValue.setScale(2, RoundingMode.HALF_UP);
421
422         Channel channelGridPower = getThing().getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_GRID_POWER);
423         if (channelGridPower != null) {
424             updateState(channelGridPower.getUID(), new QuantityType<>(gridTotal, Units.WATT));
425         }
426
427         Channel channelGridPowerSupply = getThing()
428                 .getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_GRID_POWER_SUPPLY);
429         if (channelGridPowerSupply != null) {
430             BigDecimal gridSupply = gridTotal.compareTo(BigDecimal.ZERO) < 0 ? gridTotal.abs() : BigDecimal.ZERO;
431             updateState(channelGridPowerSupply.getUID(), new QuantityType<>(gridSupply, Units.WATT));
432         }
433
434         Channel channelGridPowerDraw = getThing().getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_GRID_POWER_DRAW);
435         if (channelGridPowerDraw != null) {
436             BigDecimal gridDraw = gridTotal.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : gridTotal.abs();
437             updateState(channelGridPowerDraw.getUID(), new QuantityType<>(gridDraw, Units.WATT));
438         }
439     }
440 }