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