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.kostalinverter.internal.secondgeneration;
15 import java.math.BigDecimal;
16 import java.security.NoSuchAlgorithmException;
17 import java.util.ArrayList;
18 import java.util.List;
19 import java.util.concurrent.ExecutionException;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22 import java.util.concurrent.TimeoutException;
24 import javax.measure.Unit;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.eclipse.jetty.client.HttpClient;
29 import org.openhab.core.library.types.OnOffType;
30 import org.openhab.core.library.types.QuantityType;
31 import org.openhab.core.library.types.StringType;
32 import org.openhab.core.thing.ChannelUID;
33 import org.openhab.core.thing.Thing;
34 import org.openhab.core.thing.ThingStatus;
35 import org.openhab.core.thing.ThingStatusDetail;
36 import org.openhab.core.thing.binding.BaseThingHandler;
37 import org.openhab.core.types.Command;
38 import org.openhab.core.types.State;
39 import org.openhab.core.types.UnDefType;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
43 import com.google.gson.Gson;
44 import com.google.gson.GsonBuilder;
47 * The {@link SecondGenerationHandler} is responsible for handling commands, which are
48 * sent to one of the channels, and initiation and refreshing regarded to second generation part of the binding.
51 * @author Christian Schneider - Initial contribution
52 * @author Christoph Weitkamp - Incorporated new QuantityType (Units of Measurement)
53 * @author Örjan Backsell - Redesigned regarding Piko1020, Piko New Generation
56 public class SecondGenerationHandler extends BaseThingHandler {
58 private final Logger logger = LoggerFactory.getLogger(SecondGenerationHandler.class);
60 private @Nullable ScheduledFuture<?> secondGenerationPoller;
62 private final HttpClient httpClient;
64 private List<SecondGenerationChannelConfiguration> channelConfigs = new ArrayList<>();
65 private List<SecondGenerationChannelConfiguration> channelConfigsExt = new ArrayList<>();
66 private List<SecondGenerationChannelConfiguration> channelConfigsExtExt = new ArrayList<>();
67 private List<SecondGenerationChannelConfiguration> channelConfigsConfigurable = new ArrayList<>();
68 private List<SecondGenerationChannelConfiguration> channelConfigsAll = new ArrayList<>();
70 private List<String> channelPostsTemp = new ArrayList<>();
71 private List<String> channelPostsTempExt = new ArrayList<>();
72 private List<String> channelPostsTempExtExt = new ArrayList<>();
73 private List<String> channelPostsTempAll = new ArrayList<>();
75 private SecondGenerationInverterConfig inverterConfig = new SecondGenerationInverterConfig();
76 private Gson gson = new GsonBuilder().setPrettyPrinting().create();
78 public SecondGenerationHandler(Thing thing, HttpClient httpClient) {
80 this.httpClient = httpClient;
84 public void handleCommand(ChannelUID channelUID, Command command) {
85 String url = inverterConfig.url;
86 String username = inverterConfig.username;
87 String password = inverterConfig.password;
88 String valueConfiguration = "";
89 String dxsEntriesConf = "";
91 if (inverterConfig.hasBattery) {
92 switch (channelUID.getId()) {
93 case SecondGenerationBindingConstants.CHANNEL_BATTERYUSAGECONSUMPTIONSET:
94 valueConfiguration = command.toString();
95 dxsEntriesConf = "33556249";
96 preSetExecuteConfigurationChanges(httpClient, url, username, password, dxsEntriesConf,
99 case SecondGenerationBindingConstants.CHANNEL_BATTERYUSAGESTRATEGYSET:
100 valueConfiguration = command.toString();
101 dxsEntriesConf = "83888896";
102 preSetExecuteConfigurationChanges(httpClient, url, username, password, dxsEntriesConf,
105 case SecondGenerationBindingConstants.CHANNEL_SMARTBATTERYCONTROLSET:
106 if (command.toString().equals("ON")) {
107 valueConfiguration = "true";
109 if (command.toString().equals("OFF")) {
110 valueConfiguration = "false";
112 dxsEntriesConf = "33556484";
113 preSetExecuteConfigurationChanges(httpClient, url, username, password, dxsEntriesConf,
116 case SecondGenerationBindingConstants.CHANNEL_BATTERYCHARGETIMEFROMSET:
117 valueConfiguration = command.toString();
118 String valueConfigurationFromTransformed = String.valueOf(stringToSeconds(valueConfiguration));
119 dxsEntriesConf = "33556239";
120 preSetExecuteConfigurationChanges(httpClient, url, username, password, dxsEntriesConf,
121 valueConfigurationFromTransformed);
123 case SecondGenerationBindingConstants.CHANNEL_BATTERYCHARGETIMETOSET:
124 valueConfiguration = command.toString();
125 String valueConfigurationToTransformed = String.valueOf(stringToSeconds(valueConfiguration));
126 dxsEntriesConf = "33556240";
127 preSetExecuteConfigurationChanges(httpClient, url, username, password, dxsEntriesConf,
128 valueConfigurationToTransformed);
130 case SecondGenerationBindingConstants.CHANNEL_MAXDEPTHOFDISCHARGESET:
131 valueConfiguration = command.toString();
132 dxsEntriesConf = "33556247";
133 preSetExecuteConfigurationChanges(httpClient, url, username, password, dxsEntriesConf,
136 case SecondGenerationBindingConstants.CHANNEL_SHADOWMANAGEMENTSET:
137 valueConfiguration = command.toString();
138 dxsEntriesConf = "33556483";
139 preSetExecuteConfigurationChanges(httpClient, url, username, password, dxsEntriesConf,
142 case SecondGenerationBindingConstants.CHANNEL_EXTERNALMODULECONTROLSET:
143 valueConfiguration = command.toString();
144 dxsEntriesConf = "33556482";
145 preSetExecuteConfigurationChanges(httpClient, url, username, password, dxsEntriesConf,
153 public void initialize() {
154 // Set channel configuration parameters
155 channelConfigs = SecondGenerationChannelConfiguration.getChannelConfiguration();
156 channelConfigsExt = SecondGenerationChannelConfiguration.getChannelConfigurationExt();
157 channelConfigsExtExt = SecondGenerationChannelConfiguration.getChannelConfigurationExtExt();
158 channelConfigsConfigurable = SecondGenerationChannelConfiguration.getChannelConfigurationConfigurable();
160 // Set inverter configuration parameters
161 inverterConfig = getConfigAs(SecondGenerationInverterConfig.class);
163 // Temporary value during initializing
164 updateStatus(ThingStatus.UNKNOWN);
166 // Start update as configured
167 secondGenerationPoller = scheduler.scheduleWithFixedDelay(() -> {
170 updateStatus(ThingStatus.ONLINE);
171 } catch (RuntimeException scheduleWithFixedDelayException) {
172 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
173 scheduleWithFixedDelayException.getClass().getName() + ":"
174 + scheduleWithFixedDelayException.getMessage());
176 }, 0, inverterConfig.refreshInterval, TimeUnit.SECONDS);
180 public void dispose() {
181 final ScheduledFuture<?> secondGenerationLocalPoller = secondGenerationPoller;
183 if (secondGenerationLocalPoller != null) {
184 secondGenerationLocalPoller.cancel(true);
185 secondGenerationPoller = null;
189 private void refresh() {
190 // Build posts for dxsEntries part
191 String dxsEntriesCall = inverterConfig.url + "/api/dxs.json?dxsEntries=" + channelConfigs.get(0).dxsEntries;
192 for (int i = 1; i < channelConfigs.size(); i++) {
193 dxsEntriesCall += ("&dxsEntries=" + channelConfigs.get(i).dxsEntries);
195 String jsonDxsEntriesResponse = callURL(dxsEntriesCall, httpClient);
196 SecondGenerationDxsEntriesContainerDTO dxsEntriesContainer = gson.fromJson(jsonDxsEntriesResponse,
197 SecondGenerationDxsEntriesContainerDTO.class);
199 String[] channelPosts = new String[23];
200 int channelPostsCounter = 0;
201 for (SecondGenerationDxsEntries dxsentries : dxsEntriesContainer.dxsEntries) {
202 channelPosts[channelPostsCounter] = dxsentries.getName();
203 channelPostsCounter++;
205 channelPostsTemp = List.of(channelPosts);
207 // Build posts for dxsEntriesExt part
208 String dxsEntriesCallExt = inverterConfig.url + "/api/dxs.json?dxsEntries="
209 + channelConfigsExt.get(0).dxsEntries;
210 for (int i = 1; i < channelConfigs.size(); i++) {
211 dxsEntriesCallExt += ("&dxsEntries=" + channelConfigsExt.get(i).dxsEntries);
213 String jsonDxsEntriesResponseExt = callURL(dxsEntriesCallExt, httpClient);
214 SecondGenerationDxsEntriesContainerDTO dxsEntriesContainerExt = gson.fromJson(jsonDxsEntriesResponseExt,
215 SecondGenerationDxsEntriesContainerDTO.class);
216 String[] channelPostsExt = new String[23];
217 int channelPostsCounterExt = 0;
218 for (SecondGenerationDxsEntries dxsentriesExt : dxsEntriesContainerExt.dxsEntries) {
219 channelPostsExt[channelPostsCounterExt] = dxsentriesExt.getName();
220 channelPostsCounterExt++;
222 channelPostsTempExt = List.of(channelPostsExt);
224 // Build posts for dxsEntriesExtExt part
225 String dxsEntriesCallExtExt = inverterConfig.url + "/api/dxs.json?dxsEntries="
226 + channelConfigsExtExt.get(0).dxsEntries;
227 for (int i = 1; i < channelConfigsExtExt.size(); i++) {
228 dxsEntriesCallExtExt += ("&dxsEntries=" + channelConfigsExtExt.get(i).dxsEntries);
230 String jsonDxsEntriesResponseExtExt = callURL(dxsEntriesCallExtExt, httpClient);
231 SecondGenerationDxsEntriesContainerDTO dxsEntriesContainerExtExt = gson.fromJson(jsonDxsEntriesResponseExtExt,
232 SecondGenerationDxsEntriesContainerDTO.class);
233 String[] channelPostsExtExt = new String[3];
234 int channelPostsCounterExtExt = 0;
235 for (SecondGenerationDxsEntries dxsentriesExtExt : dxsEntriesContainerExtExt.dxsEntries) {
236 channelPostsExtExt[channelPostsCounterExtExt] = dxsentriesExtExt.getName();
237 channelPostsCounterExtExt++;
239 channelPostsTempExtExt = List.of(channelPostsExtExt);
241 // Concatenate posts for all parts except configurable channels
242 channelPostsTempAll = combinePostsLists(channelPostsTemp, channelPostsTempExt, channelPostsTempExtExt);
243 String[] channelPostsTempAll1 = channelPostsTempAll.toArray(new String[0]);
245 // Build posts for dxsEntriesConfigureable part
246 String[] channelPostsConfigurable = new String[5];
247 if (inverterConfig.hasBattery) {
248 String dxsEntriesCallConfigurable = inverterConfig.url + "/api/dxs.json?dxsEntries="
249 + channelConfigsConfigurable.get(0).dxsEntries;
250 for (int i = 1; i < channelConfigsConfigurable.size(); i++) {
251 dxsEntriesCallConfigurable += ("&dxsEntries=" + channelConfigsConfigurable.get(i).dxsEntries);
253 String jsonDxsEntriesResponseConfigurable = callURL(dxsEntriesCallConfigurable, httpClient);
254 SecondGenerationDxsEntriesContainerDTO dxsEntriesContainerConfigurable = gson
255 .fromJson(jsonDxsEntriesResponseConfigurable, SecondGenerationDxsEntriesContainerDTO.class);
256 int channelPostsCounterConfigurable = 0;
257 for (SecondGenerationDxsEntries dxsentriesConfigurable : dxsEntriesContainerConfigurable.dxsEntries) {
258 channelPostsConfigurable[channelPostsCounterConfigurable] = dxsentriesConfigurable.getName();
259 channelPostsCounterConfigurable++;
263 // Create and update actual values for non-configurable channels
264 if (!inverterConfig.hasBattery) {
265 channelConfigsAll = combineChannelConfigLists(channelConfigs, channelConfigsExt, channelConfigsExtExt);
266 int channelValuesCounterAll = 0;
267 for (SecondGenerationChannelConfiguration cConfig : channelConfigsAll) {
268 String channel = cConfig.id;
269 updateState(channel, getState(channelPostsTempAll1[channelValuesCounterAll], cConfig.unit));
270 channelValuesCounterAll++;
273 // Create and update actual values for all channels
274 if (inverterConfig.hasBattery) {
275 // Part for updating non-configurable channels
276 channelConfigsAll = combineChannelConfigLists(channelConfigs, channelConfigsExt, channelConfigsExtExt);
277 // Update the non-configurable channels
278 int channelValuesCounterAll = 0;
279 for (SecondGenerationChannelConfiguration cConfig : channelConfigsAll) {
280 String channel = cConfig.id;
281 updateState(channel, getState(channelPostsTempAll1[channelValuesCounterAll], cConfig.unit));
282 channelValuesCounterAll++;
285 // Part for updating configurable channels
286 int channelValuesCounterConfigurable = 0;
287 for (SecondGenerationChannelConfiguration cConfig : channelConfigsConfigurable) {
288 String channel = cConfig.id;
289 String value = channelPostsConfigurable[channelValuesCounterConfigurable];
290 int dxsEntriesCheckCounter = 3;
291 if (cConfig.dxsEntries.equals("33556484")) {
292 dxsEntriesCheckCounter = 1;
294 if (cConfig.dxsEntries.equals("33556482")) {
295 dxsEntriesCheckCounter = 2;
297 switch (dxsEntriesCheckCounter) {
299 if ("false".equals(value)) {
300 updateState(channel, OnOffType.OFF);
302 if ("true".equals(value)) {
303 updateState(channel, OnOffType.ON);
305 channelValuesCounterConfigurable++;
308 if ("false".equals(value)) {
309 State stateFalse = new StringType("0");
310 updateState(channel, stateFalse);
312 if ("true".equals(value)) {
313 State stateTrue = new StringType("1");
314 updateState(channel, stateTrue);
316 channelValuesCounterConfigurable++;
319 State stateOther = getState(channelPostsConfigurable[channelValuesCounterConfigurable],
321 updateState(channel, stateOther);
322 channelValuesCounterConfigurable++;
329 // Help method of handleCommand to with SecondGenerationConfigurationHandler.executeConfigurationChanges method send
330 // configuration changes.
331 private final void preSetExecuteConfigurationChanges(HttpClient httpClientHandleCommand, String url,
332 String username, String password, String dxsEntriesConf, String valueConfiguration) {
334 SecondGenerationConfigurationHandler.executeConfigurationChanges(httpClientHandleCommand, url, username,
335 password, dxsEntriesConf, valueConfiguration);
336 } catch (InterruptedException e) {
337 Thread.currentThread().interrupt();
338 logger.debug("Connection to inverter interrupted during configuration");
339 } catch (ExecutionException | TimeoutException | NoSuchAlgorithmException e) {
340 logger.debug("Connection to inverter disturbed during configuration");
344 // Method callURL connect to inverter for value scraping
345 private final String callURL(String dxsEntriesCall, HttpClient httpClient) {
346 String jsonDxsResponse = "";
348 jsonDxsResponse = httpClient.GET(dxsEntriesCall).getContentAsString().replace("null", "0.000000");
349 } catch (InterruptedException e2) {
350 Thread.currentThread().interrupt();
351 logger.debug("Connection to inverter interrupted during scrape");
352 } catch (ExecutionException | TimeoutException e2) {
353 logger.debug("Connection to inverter disturbed during scrape");
355 return jsonDxsResponse;
358 // Method getState is used for non-configurable values
359 private State getState(String value, @Nullable Unit<?> unit) {
361 return new StringType(value);
364 return new QuantityType<>(new BigDecimal(value), unit);
365 } catch (NumberFormatException getStateException) {
366 logger.debug("Error parsing value '{}: {}'", value, getStateException.getMessage());
367 return UnDefType.UNDEF;
372 // Method stringToSeconds transform given time in 00:16 syntax to seconds syntax
373 private static long stringToSeconds(String stringTime) {
374 long secondsMin = Long.parseLong(stringTime.substring(3, 5)) * 60;
375 long secondsHrs = Long.parseLong(stringTime.substring(0, 2)) * 3600;
376 return secondsMin + secondsHrs;
379 // Method to concatenate channelConfigs Lists to one List
381 private final List<SecondGenerationChannelConfiguration> combineChannelConfigLists(
382 List<SecondGenerationChannelConfiguration>... args) {
383 List<SecondGenerationChannelConfiguration> combinedChannelConfigLists = new ArrayList<>();
384 for (List<SecondGenerationChannelConfiguration> list : args) {
385 for (SecondGenerationChannelConfiguration i : list) {
386 combinedChannelConfigLists.add(i);
389 return combinedChannelConfigLists;
392 // Method to concatenate channelPosts Lists to one List
394 private final List<String> combinePostsLists(List<String>... args) {
395 List<String> combinedPostsLists = new ArrayList<>();
396 for (List<String> list : args) {
397 for (String i : list) {
398 combinedPostsLists.add(i);
401 return combinedPostsLists;