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.util.ArrayList;
17 import java.util.List;
18 import java.util.concurrent.ExecutionException;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
21 import java.util.concurrent.TimeoutException;
23 import javax.measure.Unit;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.eclipse.jetty.client.HttpClient;
28 import org.openhab.core.library.types.OnOffType;
29 import org.openhab.core.library.types.QuantityType;
30 import org.openhab.core.library.types.StringType;
31 import org.openhab.core.thing.ChannelUID;
32 import org.openhab.core.thing.Thing;
33 import org.openhab.core.thing.ThingStatus;
34 import org.openhab.core.thing.ThingStatusDetail;
35 import org.openhab.core.thing.binding.BaseThingHandler;
36 import org.openhab.core.types.Command;
37 import org.openhab.core.types.State;
38 import org.openhab.core.types.UnDefType;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
42 import com.google.gson.Gson;
43 import com.google.gson.GsonBuilder;
46 * The {@link SecondGenerationHandler} is responsible for handling commands, which are
47 * sent to one of the channels, and initiation and refreshing regarded to second generation part of the binding.
50 * @author Christian Schneider - Initial contribution
51 * @author Christoph Weitkamp - Incorporated new QuantityType (Units of Measurement)
52 * @author Örjan Backsell - Redesigned regarding Piko1020, Piko New Generation
55 public class SecondGenerationHandler extends BaseThingHandler {
57 private final Logger logger = LoggerFactory.getLogger(SecondGenerationHandler.class);
59 private @Nullable ScheduledFuture<?> secondGenerationPoller;
61 private final HttpClient httpClient;
63 private List<SecondGenerationChannelConfiguration> channelConfigs = new ArrayList<>();
64 private List<SecondGenerationChannelConfiguration> channelConfigsExt = new ArrayList<>();
65 private List<SecondGenerationChannelConfiguration> channelConfigsExtExt = new ArrayList<>();
66 private List<SecondGenerationChannelConfiguration> channelConfigsConfigurable = new ArrayList<>();
67 private List<SecondGenerationChannelConfiguration> channelConfigsAll = new ArrayList<>();
69 private List<String> channelPostsTemp = new ArrayList<>();
70 private List<String> channelPostsTempExt = new ArrayList<>();
71 private List<String> channelPostsTempExtExt = new ArrayList<>();
72 private List<String> channelPostsTempAll = new ArrayList<>();
74 private SecondGenerationInverterConfig inverterConfig = new SecondGenerationInverterConfig();
75 private Gson gson = new GsonBuilder().setPrettyPrinting().create();
77 public SecondGenerationHandler(Thing thing, HttpClient httpClient) {
79 this.httpClient = httpClient;
83 public void handleCommand(ChannelUID channelUID, Command command) {
84 String url = inverterConfig.url;
85 String username = inverterConfig.username;
86 String password = inverterConfig.password;
87 String valueConfiguration = "";
88 String dxsEntriesConf = "";
90 if (inverterConfig.hasBattery) {
91 switch (channelUID.getId()) {
92 case SecondGenerationBindingConstants.CHANNEL_BATTERYUSAGECONSUMPTIONSET:
93 valueConfiguration = command.toString();
94 dxsEntriesConf = "33556249";
95 preSetExecuteConfigurationChanges(httpClient, url, username, password, dxsEntriesConf,
98 case SecondGenerationBindingConstants.CHANNEL_BATTERYUSAGESTRATEGYSET:
99 valueConfiguration = command.toString();
100 dxsEntriesConf = "83888896";
101 preSetExecuteConfigurationChanges(httpClient, url, username, password, dxsEntriesConf,
104 case SecondGenerationBindingConstants.CHANNEL_SMARTBATTERYCONTROLSET:
105 if (command.toString().equals("ON")) {
106 valueConfiguration = "true";
108 if (command.toString().equals("OFF")) {
109 valueConfiguration = "false";
111 dxsEntriesConf = "33556484";
112 preSetExecuteConfigurationChanges(httpClient, url, username, password, dxsEntriesConf,
115 case SecondGenerationBindingConstants.CHANNEL_BATTERYCHARGETIMEFROMSET:
116 valueConfiguration = command.toString();
117 String valueConfigurationFromTransformed = String.valueOf(stringToSeconds(valueConfiguration));
118 dxsEntriesConf = "33556239";
119 preSetExecuteConfigurationChanges(httpClient, url, username, password, dxsEntriesConf,
120 valueConfigurationFromTransformed);
122 case SecondGenerationBindingConstants.CHANNEL_BATTERYCHARGETIMETOSET:
123 valueConfiguration = command.toString();
124 String valueConfigurationToTransformed = String.valueOf(stringToSeconds(valueConfiguration));
125 dxsEntriesConf = "33556240";
126 preSetExecuteConfigurationChanges(httpClient, url, username, password, dxsEntriesConf,
127 valueConfigurationToTransformed);
129 case SecondGenerationBindingConstants.CHANNEL_MAXDEPTHOFDISCHARGESET:
130 valueConfiguration = command.toString();
131 dxsEntriesConf = "33556247";
132 preSetExecuteConfigurationChanges(httpClient, url, username, password, dxsEntriesConf,
135 case SecondGenerationBindingConstants.CHANNEL_SHADOWMANAGEMENTSET:
136 valueConfiguration = command.toString();
137 dxsEntriesConf = "33556483";
138 preSetExecuteConfigurationChanges(httpClient, url, username, password, dxsEntriesConf,
141 case SecondGenerationBindingConstants.CHANNEL_EXTERNALMODULECONTROLSET:
142 valueConfiguration = command.toString();
143 dxsEntriesConf = "33556482";
144 preSetExecuteConfigurationChanges(httpClient, url, username, password, dxsEntriesConf,
152 public void initialize() {
153 // Set channel configuration parameters
154 channelConfigs = SecondGenerationChannelConfiguration.getChannelConfiguration();
155 channelConfigsExt = SecondGenerationChannelConfiguration.getChannelConfigurationExt();
156 channelConfigsExtExt = SecondGenerationChannelConfiguration.getChannelConfigurationExtExt();
157 channelConfigsConfigurable = SecondGenerationChannelConfiguration.getChannelConfigurationConfigurable();
159 // Set inverter configuration parameters
160 final SecondGenerationInverterConfig inverterConfig = getConfigAs(SecondGenerationInverterConfig.class);
161 this.inverterConfig = inverterConfig;
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());
175 } catch (InterruptedException interruptedException) {
176 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
177 interruptedException.getClass().getName() + ":" + interruptedException.getMessage());
178 } catch (ExecutionException executionException) {
179 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
180 executionException.getClass().getName() + ":" + executionException.getMessage());
181 } catch (TimeoutException timeoutException) {
182 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
183 timeoutException.getClass().getName() + ":" + timeoutException.getMessage());
185 }, 0, SecondGenerationInverterConfig.REFRESHINTERVAL_SEC, TimeUnit.SECONDS);
189 public void dispose() {
190 final ScheduledFuture<?> secondGenerationLocalPoller = secondGenerationPoller;
192 if (secondGenerationLocalPoller != null) {
193 secondGenerationLocalPoller.cancel(true);
194 secondGenerationPoller = null;
198 private void refresh() throws InterruptedException, ExecutionException, TimeoutException {
199 // Build posts for dxsEntries part
200 String dxsEntriesCall = inverterConfig.url + "/api/dxs.json?dxsEntries=" + channelConfigs.get(0).dxsEntries;
201 for (int i = 1; i < channelConfigs.size(); i++) {
202 dxsEntriesCall += ("&dxsEntries=" + channelConfigs.get(i).dxsEntries);
204 String jsonDxsEntriesResponse = callURL(dxsEntriesCall);
205 SecondGenerationDxsEntriesContainerDTO dxsEntriesContainer = gson.fromJson(jsonDxsEntriesResponse,
206 SecondGenerationDxsEntriesContainerDTO.class);
208 String[] channelPosts = new String[23];
209 int channelPostsCounter = 0;
210 for (SecondGenerationDxsEntries dxsentries : dxsEntriesContainer.dxsEntries) {
211 channelPosts[channelPostsCounter] = dxsentries.getName();
212 channelPostsCounter++;
214 channelPostsTemp = List.of(channelPosts);
216 // Build posts for dxsEntriesExt part
217 String dxsEntriesCallExt = inverterConfig.url + "/api/dxs.json?dxsEntries="
218 + channelConfigsExt.get(0).dxsEntries;
219 for (int i = 1; i < channelConfigs.size(); i++) {
220 dxsEntriesCallExt += ("&dxsEntries=" + channelConfigsExt.get(i).dxsEntries);
222 String jsonDxsEntriesResponseExt = callURL(dxsEntriesCallExt);
223 SecondGenerationDxsEntriesContainerDTO dxsEntriesContainerExt = gson.fromJson(jsonDxsEntriesResponseExt,
224 SecondGenerationDxsEntriesContainerDTO.class);
225 String[] channelPostsExt = new String[23];
226 int channelPostsCounterExt = 0;
227 for (SecondGenerationDxsEntries dxsentriesExt : dxsEntriesContainerExt.dxsEntries) {
228 channelPostsExt[channelPostsCounterExt] = dxsentriesExt.getName();
229 channelPostsCounterExt++;
231 channelPostsTempExt = List.of(channelPostsExt);
233 // Build posts for dxsEntriesExtExt part
234 String dxsEntriesCallExtExt = inverterConfig.url + "/api/dxs.json?dxsEntries="
235 + channelConfigsExtExt.get(0).dxsEntries;
236 for (int i = 1; i < channelConfigsExtExt.size(); i++) {
237 dxsEntriesCallExtExt += ("&dxsEntries=" + channelConfigsExtExt.get(i).dxsEntries);
239 String jsonDxsEntriesResponseExtExt = callURL(dxsEntriesCallExtExt);
240 SecondGenerationDxsEntriesContainerDTO dxsEntriesContainerExtExt = gson.fromJson(jsonDxsEntriesResponseExtExt,
241 SecondGenerationDxsEntriesContainerDTO.class);
242 String[] channelPostsExtExt = new String[3];
243 int channelPostsCounterExtExt = 0;
244 for (SecondGenerationDxsEntries dxsentriesExtExt : dxsEntriesContainerExtExt.dxsEntries) {
245 channelPostsExtExt[channelPostsCounterExtExt] = dxsentriesExtExt.getName();
246 channelPostsCounterExtExt++;
248 channelPostsTempExtExt = List.of(channelPostsExtExt);
250 // Concatenate posts for all parts except configurable channels
251 channelPostsTempAll = combinePostsLists(channelPostsTemp, channelPostsTempExt, channelPostsTempExtExt);
252 String[] channelPostsTempAll1 = channelPostsTempAll.toArray(new String[0]);
254 // Build posts for dxsEntriesConfigureable part
255 String dxsEntriesCallConfigurable = inverterConfig.url + "/api/dxs.json?dxsEntries="
256 + channelConfigsConfigurable.get(0).dxsEntries;
257 for (int i = 1; i < channelConfigsConfigurable.size(); i++) {
258 dxsEntriesCallConfigurable += ("&dxsEntries=" + channelConfigsConfigurable.get(i).dxsEntries);
260 String jsonDxsEntriesResponseConfigurable = callURL(dxsEntriesCallConfigurable);
261 SecondGenerationDxsEntriesContainerDTO dxsEntriesContainerConfigurable = gson
262 .fromJson(jsonDxsEntriesResponseConfigurable, SecondGenerationDxsEntriesContainerDTO.class);
263 String[] channelPostsConfigurable = new String[5];
264 int channelPostsCounterConfigurable = 0;
265 for (SecondGenerationDxsEntries dxsentriesConfigurable : dxsEntriesContainerConfigurable.dxsEntries) {
266 channelPostsConfigurable[channelPostsCounterConfigurable] = dxsentriesConfigurable.getName();
267 channelPostsCounterConfigurable++;
270 // Create and update actual values for non-configurable channels
271 if (!inverterConfig.hasBattery) {
272 channelConfigsAll = combineChannelConfigLists(channelConfigs, channelConfigsExt, channelConfigsExtExt);
273 int channelValuesCounterAll = 0;
274 for (SecondGenerationChannelConfiguration cConfig : channelConfigsAll) {
275 String channel = cConfig.id;
276 updateState(channel, getState(channelPostsTempAll1[channelValuesCounterAll], cConfig.unit));
277 channelValuesCounterAll++;
280 // Create and update actual values for all channels
281 if (inverterConfig.hasBattery) {
282 // Part for updating non-configurable channels
283 channelConfigsAll = combineChannelConfigLists(channelConfigs, channelConfigsExt, channelConfigsExtExt);
284 // Update the non-configurable channels
285 int channelValuesCounterAll = 0;
286 for (SecondGenerationChannelConfiguration cConfig : channelConfigsAll) {
287 String channel = cConfig.id;
288 updateState(channel, getState(channelPostsTempAll1[channelValuesCounterAll], cConfig.unit));
289 channelValuesCounterAll++;
292 // Part for updating configurable channels
293 int channelValuesCounterConfigurable = 0;
294 for (SecondGenerationChannelConfiguration cConfig : channelConfigsConfigurable) {
295 String channel = cConfig.id;
296 String value = channelPostsConfigurable[channelValuesCounterConfigurable];
297 int dxsEntriesCheckCounter = 3;
298 if (cConfig.dxsEntries.equals("33556484")) {
299 dxsEntriesCheckCounter = 1;
301 if (cConfig.dxsEntries.equals("33556482")) {
302 dxsEntriesCheckCounter = 2;
304 switch (dxsEntriesCheckCounter) {
306 if (value.equals("false")) {
307 updateState(channel, OnOffType.OFF);
309 if (value.equals("true")) {
310 updateState(channel, OnOffType.ON);
312 channelValuesCounterConfigurable++;
315 if (value.equals("false")) {
316 State stateFalse = new StringType("0");
317 updateState(channel, stateFalse);
319 if (value.equals("true")) {
320 State stateTrue = new StringType("1");
321 updateState(channel, stateTrue);
323 channelValuesCounterConfigurable++;
326 State stateOther = getState(channelPostsConfigurable[channelValuesCounterConfigurable],
328 updateState(channel, stateOther);
329 channelValuesCounterConfigurable++;
336 // Help method of handleCommand to with SecondGenerationConfigurationHandler.executeConfigurationChanges method send
337 // configuration changes.
338 private final void preSetExecuteConfigurationChanges(HttpClient httpClient, String url, String username,
339 String password, String dxsEntriesConf, String valueConfiguration) {
341 SecondGenerationConfigurationHandler.executeConfigurationChanges(httpClient, url, username, password,
342 dxsEntriesConf, valueConfiguration);
343 } catch (Exception handleCommandException) {
344 logger.debug("Handle command for {} on channel {}: {}: {}: {}: {}", thing.getUID(), httpClient, url,
345 dxsEntriesConf, valueConfiguration, handleCommandException.getMessage());
349 // Method callURL connect to inverter for value scraping
350 private final String callURL(String dxsEntriesCall)
351 throws InterruptedException, ExecutionException, TimeoutException {
352 String jsonDxsResponse = httpClient.GET(dxsEntriesCall).getContentAsString();
353 return jsonDxsResponse;
356 // Method getState is used for non-configurable values
357 private State getState(String value, @Nullable Unit<?> unit) {
359 return new StringType(value);
362 return new QuantityType<>(new BigDecimal(value), unit);
363 } catch (NumberFormatException getStateException) {
364 logger.debug("Error parsing value '{}: {}'", value, getStateException.getMessage());
365 return UnDefType.UNDEF;
370 // Method stringToSeconds transform given time in 00:16 syntax to seconds syntax
371 private static long stringToSeconds(String stringTime) {
372 long secondsMin = Long.parseLong(stringTime.substring(3, 5)) * 60;
373 long secondsHrs = Long.parseLong(stringTime.substring(0, 2)) * 3600;
374 return secondsMin + secondsHrs;
377 // Method to concatenate channelConfigs Lists to one List
379 private final List<SecondGenerationChannelConfiguration> combineChannelConfigLists(
380 List<SecondGenerationChannelConfiguration>... args) {
381 List<SecondGenerationChannelConfiguration> combinedChannelConfigLists = new ArrayList<>();
382 for (List<SecondGenerationChannelConfiguration> list : args) {
383 for (SecondGenerationChannelConfiguration i : list) {
384 combinedChannelConfigLists.add(i);
387 return combinedChannelConfigLists;
390 // Method to concatenate channelPosts Lists to one List
392 private final List<String> combinePostsLists(List<String>... args) {
393 List<String> combinedPostsLists = new ArrayList<>();
394 for (List<String> list : args) {
395 for (String i : list) {
396 combinedPostsLists.add(i);
399 return combinedPostsLists;