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.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() {
191 // Build posts for dxsEntries part
192 String dxsEntriesCall = inverterConfig.url + "/api/dxs.json?dxsEntries=" + channelConfigs.get(0).dxsEntries;
193 for (int i = 1; i < channelConfigs.size(); i++) {
194 dxsEntriesCall += ("&dxsEntries=" + channelConfigs.get(i).dxsEntries);
196 String jsonDxsEntriesResponse = callURL(dxsEntriesCall, httpClient);
197 SecondGenerationDxsEntriesContainerDTO dxsEntriesContainer = gson.fromJson(jsonDxsEntriesResponse,
198 SecondGenerationDxsEntriesContainerDTO.class);
200 String[] channelPosts = new String[23];
201 int channelPostsCounter = 0;
202 for (SecondGenerationDxsEntries dxsentries : dxsEntriesContainer.dxsEntries) {
203 channelPosts[channelPostsCounter] = dxsentries.getName();
204 channelPostsCounter++;
206 channelPostsTemp = List.of(channelPosts);
208 // Build posts for dxsEntriesExt part
209 String dxsEntriesCallExt = inverterConfig.url + "/api/dxs.json?dxsEntries="
210 + channelConfigsExt.get(0).dxsEntries;
211 for (int i = 1; i < channelConfigs.size(); i++) {
212 dxsEntriesCallExt += ("&dxsEntries=" + channelConfigsExt.get(i).dxsEntries);
214 String jsonDxsEntriesResponseExt = callURL(dxsEntriesCallExt, httpClient);
215 SecondGenerationDxsEntriesContainerDTO dxsEntriesContainerExt = gson.fromJson(jsonDxsEntriesResponseExt,
216 SecondGenerationDxsEntriesContainerDTO.class);
217 String[] channelPostsExt = new String[23];
218 int channelPostsCounterExt = 0;
219 for (SecondGenerationDxsEntries dxsentriesExt : dxsEntriesContainerExt.dxsEntries) {
220 channelPostsExt[channelPostsCounterExt] = dxsentriesExt.getName();
221 channelPostsCounterExt++;
223 channelPostsTempExt = List.of(channelPostsExt);
225 // Build posts for dxsEntriesExtExt part
226 String dxsEntriesCallExtExt = inverterConfig.url + "/api/dxs.json?dxsEntries="
227 + channelConfigsExtExt.get(0).dxsEntries;
228 for (int i = 1; i < channelConfigsExtExt.size(); i++) {
229 dxsEntriesCallExtExt += ("&dxsEntries=" + channelConfigsExtExt.get(i).dxsEntries);
231 String jsonDxsEntriesResponseExtExt = callURL(dxsEntriesCallExtExt, httpClient);
232 SecondGenerationDxsEntriesContainerDTO dxsEntriesContainerExtExt = gson
233 .fromJson(jsonDxsEntriesResponseExtExt, SecondGenerationDxsEntriesContainerDTO.class);
234 String[] channelPostsExtExt = new String[3];
235 int channelPostsCounterExtExt = 0;
236 for (SecondGenerationDxsEntries dxsentriesExtExt : dxsEntriesContainerExtExt.dxsEntries) {
237 channelPostsExtExt[channelPostsCounterExtExt] = dxsentriesExtExt.getName();
238 channelPostsCounterExtExt++;
240 channelPostsTempExtExt = List.of(channelPostsExtExt);
242 // Concatenate posts for all parts except configurable channels
243 channelPostsTempAll = combinePostsLists(channelPostsTemp, channelPostsTempExt, channelPostsTempExtExt);
244 String[] channelPostsTempAll1 = channelPostsTempAll.toArray(new String[0]);
246 // Build posts for dxsEntriesConfigureable part
247 String[] channelPostsConfigurable = new String[5];
248 if (inverterConfig.hasBattery) {
249 String dxsEntriesCallConfigurable = inverterConfig.url + "/api/dxs.json?dxsEntries="
250 + channelConfigsConfigurable.get(0).dxsEntries;
251 for (int i = 1; i < channelConfigsConfigurable.size(); i++) {
252 dxsEntriesCallConfigurable += ("&dxsEntries=" + channelConfigsConfigurable.get(i).dxsEntries);
254 String jsonDxsEntriesResponseConfigurable = callURL(dxsEntriesCallConfigurable, httpClient);
255 SecondGenerationDxsEntriesContainerDTO dxsEntriesContainerConfigurable = gson
256 .fromJson(jsonDxsEntriesResponseConfigurable, SecondGenerationDxsEntriesContainerDTO.class);
257 int channelPostsCounterConfigurable = 0;
258 for (SecondGenerationDxsEntries dxsentriesConfigurable : dxsEntriesContainerConfigurable.dxsEntries) {
259 channelPostsConfigurable[channelPostsCounterConfigurable] = dxsentriesConfigurable.getName();
260 channelPostsCounterConfigurable++;
264 // Create and update actual values for non-configurable channels
265 if (!inverterConfig.hasBattery) {
266 channelConfigsAll = combineChannelConfigLists(channelConfigs, channelConfigsExt, channelConfigsExtExt);
267 int channelValuesCounterAll = 0;
268 for (SecondGenerationChannelConfiguration cConfig : channelConfigsAll) {
269 String channel = cConfig.id;
270 updateState(channel, getState(channelPostsTempAll1[channelValuesCounterAll], cConfig.unit));
271 channelValuesCounterAll++;
274 // Create and update actual values for all channels
275 if (inverterConfig.hasBattery) {
276 // Part for updating non-configurable channels
277 channelConfigsAll = combineChannelConfigLists(channelConfigs, channelConfigsExt, channelConfigsExtExt);
278 // Update the non-configurable channels
279 int channelValuesCounterAll = 0;
280 for (SecondGenerationChannelConfiguration cConfig : channelConfigsAll) {
281 String channel = cConfig.id;
282 updateState(channel, getState(channelPostsTempAll1[channelValuesCounterAll], cConfig.unit));
283 channelValuesCounterAll++;
286 // Part for updating configurable channels
287 int channelValuesCounterConfigurable = 0;
288 for (SecondGenerationChannelConfiguration cConfig : channelConfigsConfigurable) {
289 String channel = cConfig.id;
290 String value = channelPostsConfigurable[channelValuesCounterConfigurable];
291 int dxsEntriesCheckCounter = 3;
292 if (cConfig.dxsEntries.equals("33556484")) {
293 dxsEntriesCheckCounter = 1;
295 if (cConfig.dxsEntries.equals("33556482")) {
296 dxsEntriesCheckCounter = 2;
298 switch (dxsEntriesCheckCounter) {
300 if ("false".equals(value)) {
301 updateState(channel, OnOffType.OFF);
303 if ("true".equals(value)) {
304 updateState(channel, OnOffType.ON);
306 channelValuesCounterConfigurable++;
309 if ("false".equals(value)) {
310 State stateFalse = new StringType("0");
311 updateState(channel, stateFalse);
313 if ("true".equals(value)) {
314 State stateTrue = new StringType("1");
315 updateState(channel, stateTrue);
317 channelValuesCounterConfigurable++;
320 State stateOther = getState(channelPostsConfigurable[channelValuesCounterConfigurable],
322 updateState(channel, stateOther);
323 channelValuesCounterConfigurable++;
328 } catch (final RuntimeException e) {
329 logger.debug("Updating inverter status failed: ", e);
330 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
334 // Help method of handleCommand to with SecondGenerationConfigurationHandler.executeConfigurationChanges method send
335 // configuration changes.
336 private final void preSetExecuteConfigurationChanges(HttpClient httpClientHandleCommand, String url,
337 String username, String password, String dxsEntriesConf, String valueConfiguration) {
339 SecondGenerationConfigurationHandler.executeConfigurationChanges(httpClientHandleCommand, url, username,
340 password, dxsEntriesConf, valueConfiguration);
341 } catch (InterruptedException | ExecutionException | TimeoutException | NoSuchAlgorithmException e) {
342 logger.debug("Connection to inverter disturbed during configuration");
346 // Method callURL connect to inverter for value scraping
347 private final String callURL(String dxsEntriesCall, HttpClient httpClient) {
348 String jsonDxsResponse = "";
350 jsonDxsResponse = httpClient.GET(dxsEntriesCall).getContentAsString();
351 } catch (InterruptedException | ExecutionException | TimeoutException e2) {
352 logger.debug("Connection to inverter disturbed during scrape");
354 return jsonDxsResponse;
357 // Method getState is used for non-configurable values
358 private State getState(String value, @Nullable Unit<?> unit) {
360 return new StringType(value);
363 return new QuantityType<>(new BigDecimal(value), unit);
364 } catch (NumberFormatException getStateException) {
365 logger.debug("Error parsing value '{}: {}'", value, getStateException.getMessage());
366 return UnDefType.UNDEF;
371 // Method stringToSeconds transform given time in 00:16 syntax to seconds syntax
372 private static long stringToSeconds(String stringTime) {
373 long secondsMin = Long.parseLong(stringTime.substring(3, 5)) * 60;
374 long secondsHrs = Long.parseLong(stringTime.substring(0, 2)) * 3600;
375 return secondsMin + secondsHrs;
378 // Method to concatenate channelConfigs Lists to one List
380 private final List<SecondGenerationChannelConfiguration> combineChannelConfigLists(
381 List<SecondGenerationChannelConfiguration>... args) {
382 List<SecondGenerationChannelConfiguration> combinedChannelConfigLists = new ArrayList<>();
383 for (List<SecondGenerationChannelConfiguration> list : args) {
384 for (SecondGenerationChannelConfiguration i : list) {
385 combinedChannelConfigLists.add(i);
388 return combinedChannelConfigLists;
391 // Method to concatenate channelPosts Lists to one List
393 private final List<String> combinePostsLists(List<String>... args) {
394 List<String> combinedPostsLists = new ArrayList<>();
395 for (List<String> list : args) {
396 for (String i : list) {
397 combinedPostsLists.add(i);
400 return combinedPostsLists;