]> git.basschouten.com Git - openhab-addons.git/blob
f352fc2a6edbc831cab0804e95ca0a0d2c22b6c5
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.intesis.internal.handler;
14
15 import static org.openhab.binding.intesis.internal.IntesisBindingConstants.*;
16 import static org.openhab.core.thing.Thing.*;
17
18 import java.util.ArrayList;
19 import java.util.Collection;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.concurrent.ScheduledFuture;
24 import java.util.concurrent.TimeUnit;
25 import java.util.function.Consumer;
26 import java.util.function.UnaryOperator;
27 import java.util.stream.Collectors;
28
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.eclipse.jetty.client.HttpClient;
32 import org.openhab.binding.intesis.internal.IntesisDynamicStateDescriptionProvider;
33 import org.openhab.binding.intesis.internal.api.IntesisHomeHttpApi;
34 import org.openhab.binding.intesis.internal.config.IntesisHomeConfiguration;
35 import org.openhab.binding.intesis.internal.enums.IntesisHomeModeEnum;
36 import org.openhab.binding.intesis.internal.gson.IntesisHomeJSonDTO.Data;
37 import org.openhab.binding.intesis.internal.gson.IntesisHomeJSonDTO.Datapoints;
38 import org.openhab.binding.intesis.internal.gson.IntesisHomeJSonDTO.Descr;
39 import org.openhab.binding.intesis.internal.gson.IntesisHomeJSonDTO.Dp;
40 import org.openhab.binding.intesis.internal.gson.IntesisHomeJSonDTO.Dpval;
41 import org.openhab.binding.intesis.internal.gson.IntesisHomeJSonDTO.Id;
42 import org.openhab.binding.intesis.internal.gson.IntesisHomeJSonDTO.Info;
43 import org.openhab.binding.intesis.internal.gson.IntesisHomeJSonDTO.Response;
44 import org.openhab.core.library.types.DecimalType;
45 import org.openhab.core.library.types.OnOffType;
46 import org.openhab.core.library.types.QuantityType;
47 import org.openhab.core.library.types.StringType;
48 import org.openhab.core.library.unit.SIUnits;
49 import org.openhab.core.thing.Channel;
50 import org.openhab.core.thing.ChannelUID;
51 import org.openhab.core.thing.Thing;
52 import org.openhab.core.thing.ThingStatus;
53 import org.openhab.core.thing.ThingStatusDetail;
54 import org.openhab.core.thing.binding.BaseThingHandler;
55 import org.openhab.core.thing.binding.builder.ChannelBuilder;
56 import org.openhab.core.thing.binding.builder.ThingBuilder;
57 import org.openhab.core.thing.type.ChannelKind;
58 import org.openhab.core.thing.type.ChannelTypeUID;
59 import org.openhab.core.types.Command;
60 import org.openhab.core.types.RefreshType;
61 import org.openhab.core.types.State;
62 import org.openhab.core.types.StateOption;
63 import org.slf4j.Logger;
64 import org.slf4j.LoggerFactory;
65
66 import com.google.gson.Gson;
67 import com.google.gson.JsonSyntaxException;
68
69 /**
70  * The {@link IntesisHomeHandler} is responsible for handling commands, which are
71  * sent to one of the channels.
72  *
73  * @author Hans-Jörg Merk - Initial contribution
74  */
75 @NonNullByDefault
76 public class IntesisHomeHandler extends BaseThingHandler {
77
78     private final Logger logger = LoggerFactory.getLogger(IntesisHomeHandler.class);
79     private final IntesisHomeHttpApi api;
80
81     private final Map<String, String> properties = new HashMap<>();
82
83     private final IntesisDynamicStateDescriptionProvider intesisStateDescriptionProvider;
84
85     private final Gson gson = new Gson();
86
87     private IntesisHomeConfiguration config = new IntesisHomeConfiguration();
88
89     private @Nullable ScheduledFuture<?> refreshJob;
90
91     public IntesisHomeHandler(final Thing thing, final HttpClient httpClient,
92             IntesisDynamicStateDescriptionProvider intesisStateDescriptionProvider) {
93         super(thing);
94         this.api = new IntesisHomeHttpApi(config, httpClient);
95         this.intesisStateDescriptionProvider = intesisStateDescriptionProvider;
96     }
97
98     @Override
99     public void initialize() {
100         updateStatus(ThingStatus.UNKNOWN);
101         config = getConfigAs(IntesisHomeConfiguration.class);
102         if (config.ipAddress.isEmpty() && config.password.isEmpty()) {
103             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "IP-Address and password not set");
104             return;
105         } else if (config.ipAddress.isEmpty()) {
106             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "IP-Address not set");
107             return;
108         } else if (config.password.isEmpty()) {
109             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Password not set");
110             return;
111         } else {
112             // start background initialization:
113             scheduler.submit(() -> {
114                 populateProperties();
115                 // query available dataPoints and build dynamic channels
116                 postRequestInSession(sessionId -> "{\"command\":\"getavailabledatapoints\",\"data\":{\"sessionID\":\""
117                         + sessionId + "\"}}", this::handleDataPointsResponse);
118                 updateProperties(properties);
119             });
120         }
121     }
122
123     @Override
124     public void dispose() {
125         logger.debug("IntesisHomeHandler disposed.");
126         final ScheduledFuture<?> refreshJob = this.refreshJob;
127
128         if (refreshJob != null) {
129             refreshJob.cancel(true);
130             this.refreshJob = null;
131         }
132     }
133
134     @Override
135     public void handleCommand(ChannelUID channelUID, Command command) {
136         int uid = 0;
137         int value = 0;
138         String channelId = channelUID.getId();
139         if (command instanceof RefreshType) {
140             getAllUidValues();
141         } else {
142             switch (channelId) {
143                 case CHANNEL_TYPE_POWER:
144                     uid = 1;
145                     value = command.equals(OnOffType.OFF) ? 0 : 1;
146                     break;
147                 case CHANNEL_TYPE_MODE:
148                     uid = 2;
149                     value = IntesisHomeModeEnum.valueOf(command.toString()).getMode();
150                     break;
151                 case CHANNEL_TYPE_FANSPEED:
152                     uid = 4;
153                     if (("AUTO").equals(command.toString())) {
154                         value = 0;
155                     } else {
156                         value = Integer.parseInt(command.toString());
157                     }
158                     break;
159                 case CHANNEL_TYPE_VANESUD:
160                 case CHANNEL_TYPE_VANESLR:
161                     switch (command.toString()) {
162                         case "AUTO":
163                             value = 0;
164                             break;
165                         case "1":
166                         case "2":
167                         case "3":
168                         case "4":
169                         case "5":
170                         case "6":
171                         case "7":
172                         case "8":
173                         case "9":
174                             value = Integer.parseInt(command.toString());
175                             break;
176                         case "SWING":
177                             value = 10;
178                             break;
179                         case "SWIRL":
180                             value = 11;
181                             break;
182                         case "WIDE":
183                             value = 12;
184                             break;
185                     }
186                     switch (channelId) {
187                         case CHANNEL_TYPE_VANESUD:
188                             uid = 5;
189                             break;
190                         case CHANNEL_TYPE_VANESLR:
191                             uid = 6;
192                             break;
193                     }
194                     break;
195                 case CHANNEL_TYPE_TARGETTEMP:
196                     uid = 9;
197                     if (command instanceof QuantityType) {
198                         QuantityType<?> newVal = (QuantityType<?>) command;
199                         newVal = newVal.toUnit(SIUnits.CELSIUS);
200                         if (newVal != null) {
201                             value = newVal.intValue() * 10;
202                         }
203                     }
204                     break;
205             }
206         }
207         if (uid != 0) {
208             final int uId = uid;
209             final int newValue = value;
210             scheduler.submit(() -> {
211                 postRequestInSession(
212                         sessionId -> "{\"command\":\"setdatapointvalue\",\"data\":{\"sessionID\":\"" + sessionId
213                                 + "\", \"uid\":" + uId + ",\"value\":" + newValue + "}}",
214                         r -> updateStatus(ThingStatus.ONLINE));
215             });
216         }
217     }
218
219     public @Nullable String login() {
220         // lambda's can't modify local variables, so we use an array here to get around the issue
221         String[] sessionId = new String[1];
222         postRequest(
223                 "{\"command\":\"login\",\"data\":{\"username\":\"Admin\",\"password\":\"" + config.password + "\"}}",
224                 resp -> {
225                     Data data = gson.fromJson(resp.data, Data.class);
226                     if (data != null) {
227                         Id id = gson.fromJson(data.id, Id.class);
228                         if (id != null) {
229                             sessionId[0] = id.sessionID.toString();
230                         }
231                     }
232                 });
233         if (sessionId[0] != null && !sessionId[0].isEmpty()) {
234             updateStatus(ThingStatus.ONLINE);
235             return sessionId[0];
236         } else {
237             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "SessionId not received");
238             return null;
239         }
240     }
241
242     public @Nullable String logout(String sessionId) {
243         String contentString = "{\"command\":\"logout\",\"data\":{\"sessionID\":\"" + sessionId + "\"}}";
244         String response = api.postRequest(config.ipAddress, contentString);
245         return response;
246     }
247
248     public void populateProperties() {
249         postRequest("{\"command\":\"getinfo\",\"data\":\"\"}", resp -> {
250             Data data = gson.fromJson(resp.data, Data.class);
251             if (data != null) {
252                 Info info = gson.fromJson(data.info, Info.class);
253                 if (info != null) {
254                     properties.put(PROPERTY_VENDOR, "Intesis");
255                     properties.put(PROPERTY_MODEL_ID, info.deviceModel);
256                     properties.put(PROPERTY_SERIAL_NUMBER, info.sn);
257                     properties.put(PROPERTY_FIRMWARE_VERSION, info.fwVersion);
258                     properties.put(PROPERTY_MAC_ADDRESS, info.wlanSTAMAC);
259                     updateStatus(ThingStatus.ONLINE);
260                 }
261             }
262         });
263     }
264
265     public void getWiFiSignal() {
266         postRequest("{\"command\":\"getinfo\",\"data\":\"\"}", resp -> {
267             Data data = gson.fromJson(resp.data, Data.class);
268             if (data != null) {
269                 Info info = gson.fromJson(data.info, Info.class);
270                 if (info != null) {
271                     String rssi = info.rssi;
272                     int dbm = Integer.valueOf(rssi);
273                     int strength = -1;
274                     if (dbm > -60) {
275                         strength = 4;
276                     } else if (dbm > -70) {
277                         strength = 3;
278                     } else if (dbm > -80) {
279                         strength = 2;
280                     } else if (dbm > -90) {
281                         strength = 1;
282                     } else {
283                         strength = 0;
284                     }
285                     DecimalType signalStrength = new DecimalType(strength);
286                     updateState(CHANNEL_TYPE_RSSI, signalStrength);
287
288                 }
289             }
290         });
291     }
292
293     public void addChannel(String channelId, String itemType, @Nullable final Collection<String> options) {
294         if (thing.getChannel(channelId) == null) {
295             logger.trace("Channel '{}' for UID to be added", channelId);
296             ThingBuilder thingBuilder = editThing();
297             final ChannelTypeUID channelTypeUID = new ChannelTypeUID(BINDING_ID, channelId);
298             Channel channel = ChannelBuilder.create(new ChannelUID(getThing().getUID(), channelId), itemType)
299                     .withType(channelTypeUID).withKind(ChannelKind.STATE).build();
300             thingBuilder.withChannel(channel);
301             updateThing(thingBuilder.build());
302         }
303         if (options != null) {
304             final List<StateOption> stateOptions = options.stream()
305                     .map(e -> new StateOption(e, e.substring(0, 1) + e.substring(1).toLowerCase()))
306                     .collect(Collectors.toList());
307             logger.trace("StateOptions : '{}'", stateOptions);
308             intesisStateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), channelId),
309                     stateOptions);
310         }
311     }
312
313     private void postRequest(String request, Consumer<Response> handler) {
314         try {
315             logger.trace("request : '{}'", request);
316             String response = api.postRequest(config.ipAddress, request);
317             if (response != null) {
318                 Response resp = gson.fromJson(response, Response.class);
319                 if (resp != null) {
320                     boolean success = resp.success;
321                     if (success) {
322                         handler.accept(resp);
323                     } else {
324                         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
325                                 "Request unsuccessful");
326                     }
327                 }
328             } else {
329                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "No Response");
330             }
331         } catch (JsonSyntaxException e) {
332             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
333         }
334     }
335
336     private void postRequestInSession(UnaryOperator<String> requestFactory, Consumer<Response> handler) {
337         String sessionId = login();
338         if (sessionId != null) {
339             try {
340                 String request = requestFactory.apply(sessionId);
341                 postRequest(request, handler);
342             } finally {
343                 logout(sessionId);
344             }
345         }
346     }
347
348     private void handleDataPointsResponse(Response response) {
349         try {
350             Data data = gson.fromJson(response.data, Data.class);
351             if (data != null) {
352                 Dp dp = gson.fromJson(data.dp, Dp.class);
353                 if (dp != null) {
354                     Datapoints[] datapoints = gson.fromJson(dp.datapoints, Datapoints[].class);
355                     if (datapoints != null) {
356                         for (Datapoints datapoint : datapoints) {
357                             Descr descr = gson.fromJson(datapoint.descr, Descr.class);
358                             String channelId = "";
359                             String itemType = "String";
360                             switch (datapoint.uid) {
361                                 case 2:
362                                     if (descr != null) {
363                                         List<String> opModes = new ArrayList<>();
364                                         for (String modString : descr.states) {
365                                             switch (modString) {
366                                                 case "0":
367                                                     opModes.add("AUTO");
368                                                     break;
369                                                 case "1":
370                                                     opModes.add("HEAT");
371                                                     break;
372                                                 case "2":
373                                                     opModes.add("DRY");
374                                                     break;
375                                                 case "3":
376                                                     opModes.add("FAN");
377                                                     break;
378                                                 case "4":
379                                                     opModes.add("COOL");
380                                                     break;
381                                             }
382                                             properties.put("supported modes", opModes.toString());
383                                             channelId = CHANNEL_TYPE_MODE;
384                                             addChannel(channelId, itemType, opModes);
385                                         }
386                                     }
387                                     break;
388                                 case 4:
389                                     if (descr != null) {
390                                         List<String> fanLevels = new ArrayList<>();
391                                         for (String fanString : descr.states) {
392                                             if ("AUTO".contentEquals(fanString)) {
393                                                 fanLevels.add("AUTO");
394                                             } else {
395                                                 fanLevels.add(fanString);
396                                             }
397                                         }
398                                         properties.put("supported fan levels", fanLevels.toString());
399                                         channelId = CHANNEL_TYPE_FANSPEED;
400                                         addChannel(channelId, itemType, fanLevels);
401                                     }
402                                     break;
403                                 case 5:
404                                 case 6:
405                                     List<String> swingModes = new ArrayList<>();
406                                     if (descr != null) {
407                                         for (String swingString : descr.states) {
408                                             if ("AUTO".contentEquals(swingString)) {
409                                                 swingModes.add("AUTO");
410                                             } else if ("10".contentEquals(swingString)) {
411                                                 swingModes.add("SWING");
412                                             } else if ("11".contentEquals(swingString)) {
413                                                 swingModes.add("SWIRL");
414                                             } else if ("12".contentEquals(swingString)) {
415                                                 swingModes.add("WIDE");
416                                             } else {
417                                                 swingModes.add(swingString);
418                                             }
419
420                                         }
421                                     }
422                                     switch (datapoint.uid) {
423                                         case 5:
424                                             channelId = CHANNEL_TYPE_VANESUD;
425                                             properties.put("supported vane up/down modes", swingModes.toString());
426                                             addChannel(channelId, itemType, swingModes);
427                                             break;
428                                         case 6:
429                                             channelId = CHANNEL_TYPE_VANESLR;
430                                             properties.put("supported vane left/right modes", swingModes.toString());
431                                             addChannel(channelId, itemType, swingModes);
432                                             break;
433                                     }
434                                     break;
435                                 case 9:
436                                     channelId = CHANNEL_TYPE_TARGETTEMP;
437                                     itemType = "Number:Temperature";
438                                     addChannel(channelId, itemType, null);
439                                     break;
440                                 case 10:
441                                     channelId = CHANNEL_TYPE_AMBIENTTEMP;
442                                     itemType = "Number:Temperature";
443                                     addChannel(channelId, itemType, null);
444                                     break;
445                                 case 14:
446                                     channelId = CHANNEL_TYPE_ERRORSTATUS;
447                                     itemType = "Switch";
448                                     addChannel(channelId, itemType, null);
449                                     break;
450                                 case 15:
451                                     channelId = CHANNEL_TYPE_ERRORCODE;
452                                     itemType = "String";
453                                     addChannel(channelId, itemType, null);
454                                     break;
455                                 case 37:
456                                     channelId = CHANNEL_TYPE_OUTDOORTEMP;
457                                     itemType = "Number:Temperature";
458                                     addChannel(channelId, itemType, null);
459                                     break;
460                             }
461                         }
462                     }
463                 }
464             }
465         } catch (JsonSyntaxException e) {
466             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
467         }
468         logger.trace("Start Refresh Job");
469         refreshJob = scheduler.scheduleWithFixedDelay(this::getAllUidValues, 0, config.pollingInterval,
470                 TimeUnit.SECONDS);
471     }
472
473     /**
474      * Update device status and all channels
475      */
476     private void getAllUidValues() {
477         postRequestInSession(sessionId -> "{\"command\":\"getdatapointvalue\",\"data\":{\"sessionID\":\"" + sessionId
478                 + "\", \"uid\":\"all\"}}", this::handleDataPointValues);
479         getWiFiSignal();
480     }
481
482     private void handleDataPointValues(Response response) {
483         try {
484             Data data = gson.fromJson(response.data, Data.class);
485             if (data != null) {
486                 Dpval[] dpval = gson.fromJson(data.dpval, Dpval[].class);
487                 if (dpval != null) {
488                     for (Dpval element : dpval) {
489                         logger.trace("UID : {} ; value : {}", element.uid, element.value);
490                         switch (element.uid) {
491                             case 1:
492                                 updateState(CHANNEL_TYPE_POWER,
493                                         String.valueOf(element.value).equals("0") ? OnOffType.OFF : OnOffType.ON);
494                                 break;
495                             case 2:
496                                 switch (element.value) {
497                                     case 0:
498                                         updateState(CHANNEL_TYPE_MODE, StringType.valueOf("AUTO"));
499                                         break;
500                                     case 1:
501                                         updateState(CHANNEL_TYPE_MODE, StringType.valueOf("HEAT"));
502                                         break;
503                                     case 2:
504                                         updateState(CHANNEL_TYPE_MODE, StringType.valueOf("DRY"));
505                                         break;
506                                     case 3:
507                                         updateState(CHANNEL_TYPE_MODE, StringType.valueOf("FAN"));
508                                         break;
509                                     case 4:
510                                         updateState(CHANNEL_TYPE_MODE, StringType.valueOf("COOL"));
511                                         break;
512                                 }
513                                 break;
514                             case 4:
515                                 if ((element.value) == 0) {
516                                     updateState(CHANNEL_TYPE_FANSPEED, StringType.valueOf("AUTO"));
517                                 } else {
518                                     updateState(CHANNEL_TYPE_FANSPEED,
519                                             StringType.valueOf(String.valueOf(element.value)));
520                                 }
521                                 break;
522                             case 5:
523                             case 6:
524                                 State state;
525                                 if ((element.value) == 0) {
526                                     state = StringType.valueOf("AUTO");
527                                 } else if ((element.value) == 10) {
528                                     state = StringType.valueOf("SWING");
529                                 } else if ((element.value) == 11) {
530                                     state = StringType.valueOf("SWIRL");
531                                 } else if ((element.value) == 12) {
532                                     state = StringType.valueOf("WIDE");
533                                 } else {
534                                     state = StringType.valueOf(String.valueOf(element.value));
535                                 }
536                                 switch (element.uid) {
537                                     case 5:
538                                         updateState(CHANNEL_TYPE_VANESUD, state);
539                                         break;
540                                     case 6:
541                                         updateState(CHANNEL_TYPE_VANESLR, state);
542                                         break;
543                                 }
544                                 break;
545                             case 9:
546                                 int unit = Math.round((element.value) / 10);
547                                 State stateValue = QuantityType.valueOf(unit, SIUnits.CELSIUS);
548                                 updateState(CHANNEL_TYPE_TARGETTEMP, stateValue);
549                                 break;
550                             case 10:
551                                 unit = Math.round((element.value) / 10);
552                                 stateValue = QuantityType.valueOf(unit, SIUnits.CELSIUS);
553                                 updateState(CHANNEL_TYPE_AMBIENTTEMP, stateValue);
554                                 break;
555                             case 14:
556                                 updateState(CHANNEL_TYPE_ERRORSTATUS,
557                                         String.valueOf(element.value).equals("0") ? OnOffType.OFF : OnOffType.ON);
558                                 break;
559                             case 15:
560                                 updateState(CHANNEL_TYPE_ERRORCODE, StringType.valueOf(String.valueOf(element.value)));
561                                 break;
562                             case 37:
563                                 unit = Math.round((element.value) / 10);
564                                 stateValue = QuantityType.valueOf(unit, SIUnits.CELSIUS);
565                                 updateState(CHANNEL_TYPE_OUTDOORTEMP, stateValue);
566                                 break;
567                         }
568                     }
569                 }
570             }
571         } catch (JsonSyntaxException e) {
572             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
573         }
574     }
575 }