]> git.basschouten.com Git - openhab-addons.git/blob
85733e6e870a1b7098cea9f1a33d5973847c3d53
[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.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("%s://%s".formatted(config.useHttp ? "http" : "https", 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             if (response.battery.chargedEnergy != null) {
201                 updateQtyState(CHANNEL_SENEC_CHARGED_ENERGY_PACK1, response.battery.chargedEnergy[0], 2,
202                         Units.KILOWATT_HOUR, DIVISOR_MILLI_TO_KILO);
203                 updateQtyState(CHANNEL_SENEC_CHARGED_ENERGY_PACK2, response.battery.chargedEnergy[1], 2,
204                         Units.KILOWATT_HOUR, DIVISOR_MILLI_TO_KILO);
205                 updateQtyState(CHANNEL_SENEC_CHARGED_ENERGY_PACK3, response.battery.chargedEnergy[2], 2,
206                         Units.KILOWATT_HOUR, DIVISOR_MILLI_TO_KILO);
207                 updateQtyState(CHANNEL_SENEC_CHARGED_ENERGY_PACK4, response.battery.chargedEnergy[3], 2,
208                         Units.KILOWATT_HOUR, DIVISOR_MILLI_TO_KILO);
209             }
210             if (response.battery.dischargedEnergy != null) {
211                 updateQtyState(CHANNEL_SENEC_DISCHARGED_ENERGY_PACK1, response.battery.dischargedEnergy[0], 2,
212                         Units.KILOWATT_HOUR, DIVISOR_MILLI_TO_KILO);
213                 updateQtyState(CHANNEL_SENEC_DISCHARGED_ENERGY_PACK2, response.battery.dischargedEnergy[1], 2,
214                         Units.KILOWATT_HOUR, DIVISOR_MILLI_TO_KILO);
215                 updateQtyState(CHANNEL_SENEC_DISCHARGED_ENERGY_PACK3, response.battery.dischargedEnergy[2], 2,
216                         Units.KILOWATT_HOUR, DIVISOR_MILLI_TO_KILO);
217                 updateQtyState(CHANNEL_SENEC_DISCHARGED_ENERGY_PACK4, response.battery.dischargedEnergy[3], 2,
218                         Units.KILOWATT_HOUR, DIVISOR_MILLI_TO_KILO);
219             }
220             if (response.battery.cycles != null) {
221                 updateDecimalState(CHANNEL_SENEC_CYCLES_PACK1, response.battery.cycles[0]);
222                 updateDecimalState(CHANNEL_SENEC_CYCLES_PACK2, response.battery.cycles[1]);
223                 updateDecimalState(CHANNEL_SENEC_CYCLES_PACK3, response.battery.cycles[2]);
224                 updateDecimalState(CHANNEL_SENEC_CYCLES_PACK4, response.battery.cycles[3]);
225             }
226             if (response.battery.current != null) {
227                 updateQtyState(CHANNEL_SENEC_CURRENT_PACK1, response.battery.current[0], 2, Units.AMPERE);
228                 updateQtyState(CHANNEL_SENEC_CURRENT_PACK2, response.battery.current[1], 2, Units.AMPERE);
229                 updateQtyState(CHANNEL_SENEC_CURRENT_PACK3, response.battery.current[2], 2, Units.AMPERE);
230                 updateQtyState(CHANNEL_SENEC_CURRENT_PACK4, response.battery.current[3], 2, Units.AMPERE);
231             }
232             if (response.battery.voltage != null) {
233                 updateQtyState(CHANNEL_SENEC_VOLTAGE_PACK1, response.battery.voltage[0], 2, Units.VOLT);
234                 updateQtyState(CHANNEL_SENEC_VOLTAGE_PACK2, response.battery.voltage[1], 2, Units.VOLT);
235                 updateQtyState(CHANNEL_SENEC_VOLTAGE_PACK3, response.battery.voltage[2], 2, Units.VOLT);
236                 updateQtyState(CHANNEL_SENEC_VOLTAGE_PACK4, response.battery.voltage[3], 2, Units.VOLT);
237             }
238             if (response.battery.maxCellVoltage != null) {
239                 updateQtyState(CHANNEL_SENEC_MAX_CELL_VOLTAGE_PACK1, response.battery.maxCellVoltage[0], 3, Units.VOLT,
240                         DIVISOR_MILLI_TO_ISO);
241                 updateQtyState(CHANNEL_SENEC_MAX_CELL_VOLTAGE_PACK2, response.battery.maxCellVoltage[1], 3, Units.VOLT,
242                         DIVISOR_MILLI_TO_ISO);
243                 updateQtyState(CHANNEL_SENEC_MAX_CELL_VOLTAGE_PACK3, response.battery.maxCellVoltage[2], 3, Units.VOLT,
244                         DIVISOR_MILLI_TO_ISO);
245                 updateQtyState(CHANNEL_SENEC_MAX_CELL_VOLTAGE_PACK4, response.battery.maxCellVoltage[3], 3, Units.VOLT,
246                         DIVISOR_MILLI_TO_ISO);
247             }
248             if (response.battery.minCellVoltage != null) {
249                 updateQtyState(CHANNEL_SENEC_MIN_CELL_VOLTAGE_PACK1, response.battery.minCellVoltage[0], 3, Units.VOLT,
250                         DIVISOR_MILLI_TO_ISO);
251                 updateQtyState(CHANNEL_SENEC_MIN_CELL_VOLTAGE_PACK2, response.battery.minCellVoltage[1], 3, Units.VOLT,
252                         DIVISOR_MILLI_TO_ISO);
253                 updateQtyState(CHANNEL_SENEC_MIN_CELL_VOLTAGE_PACK3, response.battery.minCellVoltage[2], 3, Units.VOLT,
254                         DIVISOR_MILLI_TO_ISO);
255                 updateQtyState(CHANNEL_SENEC_MIN_CELL_VOLTAGE_PACK4, response.battery.minCellVoltage[3], 3, Units.VOLT,
256                         DIVISOR_MILLI_TO_ISO);
257             }
258
259             if (response.temperature != null) {
260                 updateQtyState(CHANNEL_SENEC_BATTERY_TEMPERATURE, response.temperature.batteryTemperature, 0,
261                         SIUnits.CELSIUS);
262                 updateQtyState(CHANNEL_SENEC_CASE_TEMPERATURE, response.temperature.caseTemperature, 0,
263                         SIUnits.CELSIUS);
264                 updateQtyState(CHANNEL_SENEC_MCU_TEMPERATURE, response.temperature.mcuTemperature, 0, SIUnits.CELSIUS);
265             }
266
267             if (response.wallbox != null && response.wallbox.state != null) {
268                 updateStringStateFromInt(CHANNEL_SENEC_WALLBOX1_STATE, response.wallbox.state[0],
269                         SenecWallboxStatus::descriptionFromCode);
270                 updateDecimalState(CHANNEL_SENEC_WALLBOX1_STATE_VALUE, response.wallbox.state[0]);
271                 updateQtyState(CHANNEL_SENEC_WALLBOX1_CHARGING_CURRENT_PH1, response.wallbox.l1ChargingCurrent[0], 2,
272                         Units.AMPERE);
273                 updateQtyState(CHANNEL_SENEC_WALLBOX1_CHARGING_CURRENT_PH2, response.wallbox.l2ChargingCurrent[0], 2,
274                         Units.AMPERE);
275                 updateQtyState(CHANNEL_SENEC_WALLBOX1_CHARGING_CURRENT_PH3, response.wallbox.l3ChargingCurrent[0], 2,
276                         Units.AMPERE);
277                 updateQtyState(CHANNEL_SENEC_WALLBOX1_CHARGING_POWER, response.wallbox.chargingPower[0], 2, Units.WATT);
278             }
279
280             updateStatus(ThingStatus.ONLINE);
281         } catch (JsonParseException | IOException | InterruptedException | TimeoutException | ExecutionException e) {
282             if (response == null) {
283                 logger.trace("Faulty response: is null");
284             } else {
285                 logger.trace("Faulty response: {}", response.toString());
286             }
287             logger.warn("Error refreshing source '{}'", getThing().getUID(), e);
288             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
289                     "Could not connect to Senec web interface:" + e.getMessage());
290         }
291
292         return Boolean.TRUE;
293     }
294
295     protected void updateStringStateFromInt(String channelName, String senecValue,
296             Function<Integer, String> converter) {
297         Channel channel = getThing().getChannel(channelName);
298         if (channel != null) {
299             Integer value = getSenecValue(senecValue).intValue();
300             updateState(channel.getUID(), new StringType(converter.apply(value)));
301         }
302     }
303
304     protected void updateDecimalState(String channelName, String senecValue) {
305         Channel channel = getThing().getChannel(channelName);
306         if (channel != null) {
307             BigDecimal value = getSenecValue(senecValue);
308             updateState(channel.getUID(), new DecimalType(value.intValue()));
309         }
310     }
311
312     protected <Q extends Quantity<Q>> void updateQtyState(String channelName, String senecValue, int scale,
313             Unit<Q> unit) {
314         updateQtyState(channelName, senecValue, scale, unit, null);
315     }
316
317     protected <Q extends Quantity<Q>> void updateQtyState(String channelName, String senecValue, int scale,
318             Unit<Q> unit, @Nullable BigDecimal divisor) {
319         Channel channel = getThing().getChannel(channelName);
320         if (channel == null) {
321             return;
322         }
323         BigDecimal value = getSenecValue(senecValue);
324         if (divisor != null) {
325             value = value.divide(divisor, scale, RoundingMode.HALF_UP);
326         } else {
327             value = value.setScale(scale, RoundingMode.HALF_UP);
328         }
329         updateState(channel.getUID(), new QuantityType<>(value, unit));
330     }
331
332     protected BigDecimal getSenecValue(String value) {
333         String[] type = value.split("_");
334
335         if (type[0] != null) {
336             if (type[0].startsWith(VALUE_TYPE_DECIMAL)) {
337                 return new BigDecimal(Long.valueOf(type[1], 16));
338             } else if (type[0].startsWith(VALUE_TYPE_INT1)) {
339                 Integer val = Integer.valueOf(type[1], 16);
340                 if ((val & 0x8000) > 0) {
341                     val = val - 0x10000;
342                 }
343                 return new BigDecimal(val);
344             } else if (type[0].startsWith(VALUE_TYPE_INT3)) {
345                 Long val = Long.valueOf(type[1], 16);
346                 if ((Math.abs(val & 0x80000000)) > 0) {
347                     val = val - 0x100000000L;
348                 }
349                 return new BigDecimal(val);
350             } else if (type[0].startsWith(VALUE_TYPE_INT8)) {
351                 Long val = Long.valueOf(type[1], 16);
352                 if ((val & 0x80) > 0) {
353                     val = val - 0x100;
354                 }
355                 return new BigDecimal(val);
356             } else if (VALUE_TYPE_FLOAT.equalsIgnoreCase(type[0])) {
357                 return parseFloatValue(type[1]);
358             }
359         }
360
361         logger.warn("Unknown value type [{}]", type[0]);
362         return BigDecimal.ZERO;
363     }
364
365     /**
366      * Parse the hex coded float value of Senec device and return as BigDecimal
367      *
368      * @param value String with hex float
369      * @return BigDecimal with float value
370      */
371     private static BigDecimal parseFloatValue(String value) {
372         // sample: value = 43E26188
373
374         float f = Float.intBitsToFloat(Integer.parseUnsignedInt(value, 16));
375         return new BigDecimal(f);
376     }
377
378     protected void updatePowerLimitationStatus(Channel channel, boolean status, int duration) {
379         if (this.limitationStatus != null) {
380             if (this.limitationStatus.state == status) {
381                 long stateSince = new Date().getTime() - this.limitationStatus.time;
382
383                 if (((int) (stateSince / 1000)) < duration) {
384                     // skip updating state (possible flapping state)
385                     return;
386                 } else {
387                     logger.debug("{} longer than required duration {}", status, duration);
388                 }
389             } else {
390                 this.limitationStatus.state = status;
391                 this.limitationStatus.time = new Date().getTime();
392
393                 // skip updating state (state changed, possible flapping state)
394                 return;
395             }
396         } else {
397             this.limitationStatus = new PowerLimitationStatusDTO();
398             this.limitationStatus.state = status;
399         }
400
401         logger.debug("Updating power limitation state {}", status);
402         updateState(channel.getUID(), OnOffType.from(status));
403     }
404
405     protected void updateGridPowerValues(BigDecimal gridTotalValue) {
406         BigDecimal gridTotal = gridTotalValue.setScale(2, RoundingMode.HALF_UP);
407
408         Channel channelGridPower = getThing().getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_GRID_POWER);
409         if (channelGridPower != null) {
410             updateState(channelGridPower.getUID(), new QuantityType<>(gridTotal, Units.WATT));
411         }
412
413         Channel channelGridPowerSupply = getThing()
414                 .getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_GRID_POWER_SUPPLY);
415         if (channelGridPowerSupply != null) {
416             BigDecimal gridSupply = gridTotal.compareTo(BigDecimal.ZERO) < 0 ? gridTotal.abs() : BigDecimal.ZERO;
417             updateState(channelGridPowerSupply.getUID(), new QuantityType<>(gridSupply, Units.WATT));
418         }
419
420         Channel channelGridPowerDraw = getThing().getChannel(SenecHomeBindingConstants.CHANNEL_SENEC_GRID_POWER_DRAW);
421         if (channelGridPowerDraw != null) {
422             BigDecimal gridDraw = gridTotal.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : gridTotal.abs();
423             updateState(channelGridPowerDraw.getUID(), new QuantityType<>(gridDraw, Units.WATT));
424         }
425     }
426 }