]> git.basschouten.com Git - openhab-addons.git/blob
bb7731f477ef22c22bd775c8ca0765ef1e5d5a18
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.miio.internal.handler;
14
15 import static org.openhab.binding.miio.internal.MiIoBindingConstants.*;
16
17 import java.awt.Color;
18 import java.io.IOException;
19 import java.net.URL;
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.LinkedHashSet;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Map.Entry;
26 import java.util.concurrent.TimeUnit;
27
28 import javax.measure.Unit;
29 import javax.measure.format.ParserException;
30
31 import org.eclipse.jdt.annotation.NonNullByDefault;
32 import org.eclipse.jdt.annotation.Nullable;
33 import org.openhab.binding.miio.internal.MiIoBindingConfiguration;
34 import org.openhab.binding.miio.internal.MiIoCommand;
35 import org.openhab.binding.miio.internal.MiIoQuantiyTypes;
36 import org.openhab.binding.miio.internal.MiIoSendCommand;
37 import org.openhab.binding.miio.internal.Utils;
38 import org.openhab.binding.miio.internal.basic.ActionConditions;
39 import org.openhab.binding.miio.internal.basic.BasicChannelTypeProvider;
40 import org.openhab.binding.miio.internal.basic.CommandParameterType;
41 import org.openhab.binding.miio.internal.basic.Conversions;
42 import org.openhab.binding.miio.internal.basic.MiIoBasicChannel;
43 import org.openhab.binding.miio.internal.basic.MiIoBasicDevice;
44 import org.openhab.binding.miio.internal.basic.MiIoDatabaseWatchService;
45 import org.openhab.binding.miio.internal.basic.MiIoDeviceAction;
46 import org.openhab.binding.miio.internal.basic.MiIoDeviceActionCondition;
47 import org.openhab.binding.miio.internal.cloud.CloudConnector;
48 import org.openhab.binding.miio.internal.transport.MiIoAsyncCommunication;
49 import org.openhab.core.cache.ExpiringCache;
50 import org.openhab.core.library.types.DecimalType;
51 import org.openhab.core.library.types.HSBType;
52 import org.openhab.core.library.types.OnOffType;
53 import org.openhab.core.library.types.PercentType;
54 import org.openhab.core.library.types.QuantityType;
55 import org.openhab.core.library.types.StringType;
56 import org.openhab.core.library.unit.SIUnits;
57 import org.openhab.core.library.unit.Units;
58 import org.openhab.core.thing.Channel;
59 import org.openhab.core.thing.ChannelUID;
60 import org.openhab.core.thing.Thing;
61 import org.openhab.core.thing.binding.builder.ChannelBuilder;
62 import org.openhab.core.thing.binding.builder.ThingBuilder;
63 import org.openhab.core.thing.type.ChannelTypeRegistry;
64 import org.openhab.core.thing.type.ChannelTypeUID;
65 import org.openhab.core.types.Command;
66 import org.openhab.core.types.RefreshType;
67 import org.slf4j.Logger;
68 import org.slf4j.LoggerFactory;
69
70 import com.google.gson.Gson;
71 import com.google.gson.GsonBuilder;
72 import com.google.gson.JsonArray;
73 import com.google.gson.JsonElement;
74 import com.google.gson.JsonIOException;
75 import com.google.gson.JsonObject;
76 import com.google.gson.JsonPrimitive;
77 import com.google.gson.JsonSyntaxException;
78
79 /**
80  * The {@link MiIoBasicHandler} is responsible for handling commands, which are
81  * sent to one of the channels.
82  *
83  * @author Marcel Verpaalen - Initial contribution
84  */
85 @NonNullByDefault
86 public class MiIoBasicHandler extends MiIoAbstractHandler {
87     private final Logger logger = LoggerFactory.getLogger(MiIoBasicHandler.class);
88     private boolean hasChannelStructure;
89
90     private final ExpiringCache<Boolean> updateDataCache = new ExpiringCache<>(CACHE_EXPIRY, () -> {
91         miIoScheduler.schedule(this::updateData, 0, TimeUnit.SECONDS);
92         return true;
93     });
94
95     List<MiIoBasicChannel> refreshList = new ArrayList<>();
96     private Map<String, MiIoBasicChannel> refreshListCustomCommands = new HashMap<>();
97
98     private @Nullable MiIoBasicDevice miioDevice;
99     private Map<ChannelUID, MiIoBasicChannel> actions = new HashMap<>();
100     private ChannelTypeRegistry channelTypeRegistry;
101     private BasicChannelTypeProvider basicChannelTypeProvider;
102
103     public MiIoBasicHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService,
104             CloudConnector cloudConnector, ChannelTypeRegistry channelTypeRegistry,
105             BasicChannelTypeProvider basicChannelTypeProvider) {
106         super(thing, miIoDatabaseWatchService, cloudConnector);
107         this.channelTypeRegistry = channelTypeRegistry;
108         this.basicChannelTypeProvider = basicChannelTypeProvider;
109     }
110
111     @Override
112     public void initialize() {
113         super.initialize();
114         hasChannelStructure = false;
115         isIdentified = false;
116         refreshList = new ArrayList<>();
117         refreshListCustomCommands = new HashMap<>();
118     }
119
120     @Override
121     public void handleCommand(ChannelUID channelUID, Command receivedCommand) {
122         Command command = receivedCommand;
123         if (command == RefreshType.REFRESH) {
124             if (updateDataCache.isExpired()) {
125                 logger.debug("Refreshing {}", channelUID);
126                 updateDataCache.getValue();
127             } else {
128                 logger.debug("Refresh {} skipped. Already refreshing", channelUID);
129             }
130             return;
131         }
132         if (channelUID.getId().equals(CHANNEL_COMMAND)) {
133             cmds.put(sendCommand(command.toString()), command.toString());
134             return;
135         }
136         logger.debug("Locating action for {} channel '{}': '{}'", getThing().getUID(), channelUID.getId(), command);
137         if (!actions.isEmpty()) {
138             final MiIoBasicChannel miIoBasicChannel = actions.get(channelUID);
139             if (miIoBasicChannel != null) {
140                 int valuePos = 0;
141                 for (MiIoDeviceAction action : miIoBasicChannel.getActions()) {
142                     @Nullable
143                     JsonElement value = null;
144                     JsonArray parameters = action.getParameters().deepCopy();
145                     for (int i = 0; i < action.getParameters().size(); i++) {
146                         JsonElement p = action.getParameters().get(i);
147                         if (p.isJsonPrimitive() && p.getAsString().toLowerCase().contains("$value$")) {
148                             valuePos = i;
149                             break;
150                         }
151                     }
152                     String cmd = action.getCommand();
153                     CommandParameterType paramType = action.getparameterType();
154                     if (command instanceof QuantityType) {
155                         QuantityType<?> qtc = null;
156                         try {
157                             if (!miIoBasicChannel.getUnit().isBlank()) {
158                                 Unit<?> unit = MiIoQuantiyTypes.get(miIoBasicChannel.getUnit());
159                                 if (unit != null) {
160                                     qtc = ((QuantityType<?>) command).toUnit(unit);
161                                 }
162                             }
163                         } catch (ParserException e) {
164                             // swallow
165                         }
166                         if (qtc != null) {
167                             command = new DecimalType(qtc.toBigDecimal());
168                         } else {
169                             logger.debug("Could not convert QuantityType to '{}'", miIoBasicChannel.getUnit());
170                             command = new DecimalType(((QuantityType<?>) command).toBigDecimal());
171                         }
172                     }
173                     if (paramType == CommandParameterType.COLOR) {
174                         if (command instanceof HSBType) {
175                             HSBType hsb = (HSBType) command;
176                             Color color = Color.getHSBColor(hsb.getHue().floatValue() / 360,
177                                     hsb.getSaturation().floatValue() / 100, hsb.getBrightness().floatValue() / 100);
178                             value = new JsonPrimitive(
179                                     (color.getRed() << 16) + (color.getGreen() << 8) + color.getBlue());
180                         } else if (command instanceof DecimalType) {
181                             // actually brightness is being set instead of a color
182                             value = new JsonPrimitive(((DecimalType) command).toBigDecimal());
183                         } else if (command instanceof OnOffType) {
184                             value = new JsonPrimitive(command == OnOffType.ON ? 100 : 0);
185                         } else {
186                             logger.debug("Unsupported command for COLOR: {}", command);
187                         }
188                     } else if (command instanceof OnOffType) {
189                         if (paramType == CommandParameterType.ONOFF) {
190                             value = new JsonPrimitive(command == OnOffType.ON ? "on" : "off");
191                         } else if (paramType == CommandParameterType.ONOFFPARA) {
192                             cmd = cmd.replace("*", command == OnOffType.ON ? "on" : "off");
193                             value = new JsonArray();
194                         } else if (paramType == CommandParameterType.ONOFFBOOL) {
195                             boolean boolCommand = command == OnOffType.ON;
196                             value = new JsonPrimitive(boolCommand);
197                         } else if (paramType == CommandParameterType.ONOFFBOOLSTRING) {
198                             value = new JsonPrimitive(command == OnOffType.ON ? "true" : "false");
199                         }
200                     } else if (command instanceof DecimalType) {
201                         value = new JsonPrimitive(((DecimalType) command).toBigDecimal());
202                     } else if (command instanceof StringType) {
203                         if (paramType == CommandParameterType.STRING) {
204                             value = new JsonPrimitive(command.toString().toLowerCase());
205                         } else if (paramType == CommandParameterType.CUSTOMSTRING) {
206                             value = new JsonPrimitive(parameters.get(valuePos).getAsString().replace("$value",
207                                     command.toString().toLowerCase()));
208                         }
209                     } else {
210                         value = new JsonPrimitive(command.toString().toLowerCase());
211                     }
212                     if (paramType == CommandParameterType.EMPTY) {
213                         value = new JsonArray();
214                     }
215                     final MiIoDeviceActionCondition miIoDeviceActionCondition = action.getCondition();
216                     if (miIoDeviceActionCondition != null) {
217                         value = ActionConditions.executeAction(miIoDeviceActionCondition, deviceVariables, value,
218                                 command);
219                     }
220                     // Check for miot channel
221                     if (value != null) {
222                         if (action.isMiOtAction()) {
223                             value = miotActionTransform(action, miIoBasicChannel, value);
224                         } else if (miIoBasicChannel.isMiOt()) {
225                             value = miotTransform(miIoBasicChannel, value);
226                         }
227                     }
228                     if (paramType != CommandParameterType.NONE && paramType != CommandParameterType.ONOFFPARA
229                             && value != null) {
230                         if (parameters.size() > 0) {
231                             parameters.set(valuePos, value);
232                         } else {
233                             parameters.add(value);
234                         }
235                     }
236                     if (action.isMiOtAction() && parameters.size() > 0 && parameters.get(0).isJsonObject()) {
237                         // hack as unlike any other commands miot actions parameters appear to be send as a json object
238                         // instead of a json array
239                         cmd = cmd + parameters.get(0).getAsJsonObject().toString();
240                     } else {
241                         cmd = cmd + parameters.toString();
242                     }
243                     if (value != null) {
244                         logger.debug("Sending command {}", cmd);
245                         sendCommand(cmd);
246                     } else {
247                         if (miIoDeviceActionCondition != null) {
248                             logger.debug("Conditional command {} not send, condition '{}' not met", cmd,
249                                     miIoDeviceActionCondition.getName());
250                         } else {
251                             logger.debug("Command not send. Value null");
252                         }
253                     }
254                 }
255             } else {
256                 logger.debug("Channel Id {} not in mapping.", channelUID.getId());
257                 if (logger.isTraceEnabled()) {
258                     for (Entry<ChannelUID, MiIoBasicChannel> a : actions.entrySet()) {
259                         logger.trace("Available entries: {} : {}", a.getKey(), a.getValue().getFriendlyName());
260                     }
261                 }
262             }
263             updateDataCache.invalidateValue();
264             miIoScheduler.schedule(() -> {
265                 updateData();
266             }, 3000, TimeUnit.MILLISECONDS);
267         } else {
268             logger.debug("Actions not loaded yet, or none available");
269         }
270     }
271
272     private @Nullable JsonElement miotTransform(MiIoBasicChannel miIoBasicChannel, @Nullable JsonElement value) {
273         JsonObject json = new JsonObject();
274         json.addProperty("did", miIoBasicChannel.getChannel());
275         json.addProperty("siid", miIoBasicChannel.getSiid());
276         json.addProperty("piid", miIoBasicChannel.getPiid());
277         json.add("value", value);
278         return json;
279     }
280
281     private @Nullable JsonElement miotActionTransform(MiIoDeviceAction action, MiIoBasicChannel miIoBasicChannel,
282             @Nullable JsonElement value) {
283         JsonObject json = new JsonObject();
284         json.addProperty("did", miIoBasicChannel.getChannel());
285         json.addProperty("siid", action.getSiid());
286         json.addProperty("aiid", action.getAiid());
287         if (value != null) {
288             json.add("in", value);
289         }
290         return json;
291     }
292
293     @Override
294     protected synchronized void updateData() {
295         logger.debug("Periodic update for '{}' ({})", getThing().getUID().toString(), getThing().getThingTypeUID());
296         final MiIoAsyncCommunication miioCom = getConnection();
297         try {
298             if (!hasConnection() || skipUpdate() || miioCom == null) {
299                 return;
300             }
301             checkChannelStructure();
302             if (!isIdentified) {
303                 sendCommand(MiIoCommand.MIIO_INFO);
304             }
305             final MiIoBasicDevice midevice = miioDevice;
306             if (midevice != null) {
307                 refreshProperties(midevice);
308                 refreshCustomProperties(midevice);
309                 refreshNetwork();
310             }
311         } catch (Exception e) {
312             logger.debug("Error while updating '{}': ", getThing().getUID().toString(), e);
313         }
314     }
315
316     private void refreshCustomProperties(MiIoBasicDevice midevice) {
317         for (MiIoBasicChannel miChannel : refreshListCustomCommands.values()) {
318             sendCommand(miChannel.getChannelCustomRefreshCommand());
319         }
320     }
321
322     private boolean refreshProperties(MiIoBasicDevice device) {
323         MiIoCommand command = MiIoCommand.getCommand(device.getDevice().getPropertyMethod());
324         int maxProperties = device.getDevice().getMaxProperties();
325         JsonArray getPropString = new JsonArray();
326         for (MiIoBasicChannel miChannel : refreshList) {
327             if (!isLinked(miChannel.getChannel())) {
328                 logger.debug("Skip refresh of channel {} for {} as it is not linked", miChannel.getChannel(),
329                         getThing().getUID());
330                 continue;
331             }
332             JsonElement property;
333             if (miChannel.isMiOt()) {
334                 JsonObject json = new JsonObject();
335                 json.addProperty("did", miChannel.getProperty());
336                 json.addProperty("siid", miChannel.getSiid());
337                 json.addProperty("piid", miChannel.getPiid());
338                 property = json;
339             } else {
340                 property = new JsonPrimitive(miChannel.getProperty());
341             }
342             getPropString.add(property);
343             if (getPropString.size() >= maxProperties) {
344                 sendRefreshProperties(command, getPropString);
345                 getPropString = new JsonArray();
346             }
347         }
348         if (getPropString.size() > 0) {
349             sendRefreshProperties(command, getPropString);
350         }
351         return true;
352     }
353
354     private void sendRefreshProperties(MiIoCommand command, JsonArray getPropString) {
355         sendCommand(command, getPropString.toString());
356     }
357
358     /**
359      * Checks if the channel structure has been build already based on the model data. If not build it.
360      */
361     private void checkChannelStructure() {
362         final MiIoBindingConfiguration configuration = this.configuration;
363         if (configuration == null) {
364             return;
365         }
366         if (!hasChannelStructure) {
367             if (configuration.model == null || configuration.model.isEmpty()) {
368                 logger.debug("Model needs to be determined");
369                 isIdentified = false;
370             } else {
371                 hasChannelStructure = buildChannelStructure(configuration.model);
372             }
373         }
374         if (hasChannelStructure) {
375             refreshList = new ArrayList<>();
376             final MiIoBasicDevice miioDevice = this.miioDevice;
377             if (miioDevice != null) {
378                 for (MiIoBasicChannel miChannel : miioDevice.getDevice().getChannels()) {
379                     if (miChannel.getRefresh()) {
380                         if (miChannel.getChannelCustomRefreshCommand().isBlank()) {
381                             refreshList.add(miChannel);
382                         } else {
383                             String i = miChannel.getChannelCustomRefreshCommand().split("\\[")[0];
384                             refreshListCustomCommands.put(i.trim(), miChannel);
385                         }
386                     }
387                 }
388             }
389
390         }
391     }
392
393     private boolean buildChannelStructure(String deviceName) {
394         logger.debug("Building Channel Structure for {} - Model: {}", getThing().getUID().toString(), deviceName);
395         URL fn = miIoDatabaseWatchService.getDatabaseUrl(deviceName);
396         if (fn == null) {
397             logger.warn("Database entry for model '{}' cannot be found.", deviceName);
398             return false;
399         }
400         try {
401             JsonObject deviceMapping = Utils.convertFileToJSON(fn);
402             logger.debug("Using device database: {} for device {}", fn.getFile(), deviceName);
403             Gson gson = new GsonBuilder().serializeNulls().create();
404             miioDevice = gson.fromJson(deviceMapping, MiIoBasicDevice.class);
405             for (Channel ch : getThing().getChannels()) {
406                 logger.debug("Current thing channels {}, type: {}", ch.getUID(), ch.getChannelTypeUID());
407             }
408             ThingBuilder thingBuilder = editThing();
409             int channelsAdded = 0;
410
411             // make a map of the actions
412             actions = new HashMap<>();
413             final MiIoBasicDevice device = this.miioDevice;
414             if (device != null) {
415                 for (Channel cn : getThing().getChannels()) {
416                     logger.trace("Channel '{}' for thing {} already exist... removing", cn.getUID(),
417                             getThing().getUID());
418                     if (!PERSISTENT_CHANNELS.contains(cn.getUID().getId().toString())) {
419                         thingBuilder.withoutChannels(cn);
420                     }
421                 }
422                 for (MiIoBasicChannel miChannel : device.getDevice().getChannels()) {
423                     logger.debug("properties {}", miChannel);
424                     if (!miChannel.getType().isEmpty()) {
425                         basicChannelTypeProvider.addChannelType(miChannel, deviceName);
426                         ChannelUID channelUID = addChannel(thingBuilder, miChannel, deviceName);
427                         if (channelUID != null) {
428                             actions.put(channelUID, miChannel);
429                             channelsAdded++;
430                         } else {
431                             logger.debug("Channel for {} ({}) not loaded", miChannel.getChannel(),
432                                     miChannel.getFriendlyName());
433                         }
434                     } else {
435                         logger.debug("Channel {} ({}), not loaded, missing type", miChannel.getChannel(),
436                                 miChannel.getFriendlyName());
437                     }
438                 }
439             }
440             // only update if channels were added/removed
441             if (channelsAdded > 0) {
442                 logger.debug("Current thing channels added: {}", channelsAdded);
443                 updateThing(thingBuilder.build());
444             }
445             return true;
446         } catch (JsonIOException | JsonSyntaxException e) {
447             logger.warn("Error parsing database Json", e);
448         } catch (IOException e) {
449             logger.warn("Error reading database file", e);
450         } catch (Exception e) {
451             logger.warn("Error creating channel structure", e);
452         }
453         return false;
454     }
455
456     private @Nullable ChannelUID addChannel(ThingBuilder thingBuilder, MiIoBasicChannel miChannel, String model) {
457         String channel = miChannel.getChannel();
458         String dataType = miChannel.getType();
459         if (channel.isEmpty() || dataType.isEmpty()) {
460             logger.info("Channel '{}', UID '{}' cannot be added incorrectly configured database. ", channel,
461                     getThing().getUID());
462             return null;
463         }
464         ChannelUID channelUID = new ChannelUID(getThing().getUID(), channel);
465         ChannelBuilder newChannel = ChannelBuilder.create(channelUID, dataType).withLabel(miChannel.getFriendlyName());
466         boolean useGeneratedChannelType = false;
467         if (!miChannel.getChannelType().isBlank()) {
468             ChannelTypeUID channelTypeUID = new ChannelTypeUID(miChannel.getChannelType());
469             if (channelTypeRegistry.getChannelType(channelTypeUID) != null) {
470                 newChannel = newChannel.withType(channelTypeUID);
471                 final LinkedHashSet<String> tags = miChannel.getTags();
472                 if (tags != null && tags.size() > 0) {
473                     newChannel.withDefaultTags(tags);
474                 }
475             } else {
476                 logger.debug("ChannelType '{}' is not available. Check the Json file for {}", channelTypeUID, model);
477                 useGeneratedChannelType = true;
478             }
479         } else {
480             useGeneratedChannelType = true;
481         }
482         if (useGeneratedChannelType) {
483             newChannel = newChannel
484                     .withType(new ChannelTypeUID(BINDING_ID, model.toUpperCase().replace(".", "_") + "_" + channel));
485             final LinkedHashSet<String> tags = miChannel.getTags();
486             if (tags != null && tags.size() > 0) {
487                 newChannel.withDefaultTags(tags);
488             }
489         }
490         thingBuilder.withChannel(newChannel.build());
491         return channelUID;
492     }
493
494     private @Nullable MiIoBasicChannel getChannel(String parameter) {
495         for (MiIoBasicChannel refreshEntry : refreshList) {
496             if (refreshEntry.getProperty().equals(parameter)) {
497                 return refreshEntry;
498             }
499         }
500         logger.trace("Did not find channel for {} in {}", parameter, refreshList);
501         return null;
502     }
503
504     private void updatePropsFromJsonArray(MiIoSendCommand response) {
505         JsonArray res = response.getResult().getAsJsonArray();
506         JsonArray para = parser.parse(response.getCommandString()).getAsJsonObject().get("params").getAsJsonArray();
507         if (res.size() != para.size()) {
508             logger.debug("Unexpected size different. Request size {},  response size {}. (Req: {}, Resp:{})",
509                     para.size(), res.size(), para, res);
510             return;
511         }
512         for (int i = 0; i < para.size(); i++) {
513             // This is a miot parameter
514             String param;
515             final JsonElement paraElement = para.get(i);
516             if (paraElement.isJsonObject()) { // miot channel
517                 param = paraElement.getAsJsonObject().get("did").getAsString();
518             } else {
519                 param = paraElement.getAsString();
520             }
521             JsonElement val = res.get(i);
522             if (val.isJsonNull()) {
523                 logger.debug("Property '{}' returned null (is it supported?).", param);
524                 continue;
525             } else if (val.isJsonObject()) { // miot channel
526                 val = val.getAsJsonObject().get("value");
527             }
528             MiIoBasicChannel basicChannel = getChannel(param);
529             updateChannel(basicChannel, param, val);
530         }
531     }
532
533     private void updatePropsFromJsonObject(MiIoSendCommand response) {
534         JsonObject res = response.getResult().getAsJsonObject();
535         for (Object k : res.keySet()) {
536             String param = (String) k;
537             JsonElement val = res.get(param);
538             if (val.isJsonNull()) {
539                 logger.debug("Property '{}' returned null (is it supported?).", param);
540                 continue;
541             }
542             MiIoBasicChannel basicChannel = getChannel(param);
543             updateChannel(basicChannel, param, val);
544         }
545     }
546
547     private void updateChannel(@Nullable MiIoBasicChannel basicChannel, String param, JsonElement value) {
548         JsonElement val = value;
549         if (basicChannel == null) {
550             logger.debug("Channel not found for {}", param);
551             return;
552         }
553         final String transformation = basicChannel.getTransfortmation();
554         if (transformation != null) {
555             JsonElement transformed = Conversions.execute(transformation, val);
556             logger.debug("Transformed with '{}': {} {} -> {} ", transformation, basicChannel.getFriendlyName(), val,
557                     transformed);
558             val = transformed;
559         }
560         try {
561             String[] chType = basicChannel.getType().toLowerCase().split(":");
562             switch (chType[0]) {
563                 case "number":
564                     quantityTypeUpdate(basicChannel, val, chType.length > 1 ? chType[1] : "");
565                     break;
566                 case "dimmer":
567                     updateState(basicChannel.getChannel(), new PercentType(val.getAsBigDecimal()));
568                     break;
569                 case "string":
570                     updateState(basicChannel.getChannel(), new StringType(val.getAsString()));
571                     break;
572                 case "switch":
573                     updateState(basicChannel.getChannel(), val.getAsString().toLowerCase().equals("on")
574                             || val.getAsString().toLowerCase().equals("true") ? OnOffType.ON : OnOffType.OFF);
575                     break;
576                 case "color":
577                     Color rgb = new Color(val.getAsInt());
578                     HSBType hsb = HSBType.fromRGB(rgb.getRed(), rgb.getGreen(), rgb.getBlue());
579                     updateState(basicChannel.getChannel(), hsb);
580                     break;
581                 default:
582                     logger.debug("No update logic for channeltype '{}' ", basicChannel.getType());
583             }
584         } catch (Exception e) {
585             logger.debug("Error updating {} property {} with '{}' : {}: {}", getThing().getUID(),
586                     basicChannel.getChannel(), val, e.getClass().getCanonicalName(), e.getMessage());
587             logger.trace("Property update error detail:", e);
588         }
589     }
590
591     private void quantityTypeUpdate(MiIoBasicChannel basicChannel, JsonElement val, String type) {
592         if (!basicChannel.getUnit().isBlank()) {
593             Unit<?> unit = MiIoQuantiyTypes.get(basicChannel.getUnit());
594             if (unit != null) {
595                 logger.debug("'{}' channel '{}' has unit '{}' with symbol '{}'.", getThing().getUID(),
596                         basicChannel.getChannel(), basicChannel.getUnit(), unit);
597                 updateState(basicChannel.getChannel(), new QuantityType<>(val.getAsBigDecimal(), unit));
598             } else {
599                 logger.debug(
600                         "Unit '{}' used by '{}' channel '{}' is not found in conversion table... Trying anyway to submit as the update.",
601                         basicChannel.getUnit(), getThing().getUID(), basicChannel.getChannel());
602                 updateState(basicChannel.getChannel(),
603                         new QuantityType<>(val.getAsBigDecimal().toPlainString() + " " + basicChannel.getUnit()));
604             }
605             return;
606         }
607         // if no unit is provided or unit not found use default units, these units have so far been seen for miio
608         // devices
609         switch (type.toLowerCase()) {
610             case "temperature":
611                 updateState(basicChannel.getChannel(), new QuantityType<>(val.getAsBigDecimal(), SIUnits.CELSIUS));
612                 break;
613             case "electriccurrent":
614                 updateState(basicChannel.getChannel(), new QuantityType<>(val.getAsBigDecimal(), Units.AMPERE));
615                 break;
616             case "energy":
617                 updateState(basicChannel.getChannel(), new QuantityType<>(val.getAsBigDecimal(), Units.WATT));
618                 break;
619             case "time":
620                 updateState(basicChannel.getChannel(), new QuantityType<>(val.getAsBigDecimal(), Units.HOUR));
621                 break;
622             default:
623                 updateState(basicChannel.getChannel(), new DecimalType(val.getAsBigDecimal()));
624         }
625     }
626
627     @Override
628     public void onMessageReceived(MiIoSendCommand response) {
629         super.onMessageReceived(response);
630         if (response.isError()) {
631             return;
632         }
633         try {
634             switch (response.getCommand()) {
635                 case MIIO_INFO:
636                     break;
637                 case GET_VALUE:
638                 case GET_PROPERTIES:
639                 case GET_PROPERTY:
640                     if (response.getResult().isJsonArray()) {
641                         updatePropsFromJsonArray(response);
642                     } else if (response.getResult().isJsonObject()) {
643                         updatePropsFromJsonObject(response);
644                     }
645                     break;
646                 default:
647                     if (refreshListCustomCommands.containsKey(response.getMethod())) {
648                         logger.debug("Processing custom refresh command response for !{}", response.getMethod());
649                         final MiIoBasicChannel ch = refreshListCustomCommands.get(response.getMethod());
650                         if (ch != null) {
651                             if (response.getResult().isJsonArray()) {
652                                 JsonArray cmdResponse = response.getResult().getAsJsonArray();
653                                 final String transformation = ch.getTransfortmation();
654                                 if (transformation == null || transformation.isBlank()) {
655                                     JsonElement response0 = cmdResponse.get(0);
656                                     updateChannel(ch, ch.getChannel(), response0.isJsonPrimitive() ? response0
657                                             : new JsonPrimitive(response0.toString()));
658                                 } else {
659                                     updateChannel(ch, ch.getChannel(), cmdResponse);
660                                 }
661                             } else {
662                                 updateChannel(ch, ch.getChannel(), new JsonPrimitive(response.getResult().toString()));
663                             }
664                         }
665                     }
666                     break;
667             }
668         } catch (Exception e) {
669             logger.debug("Error while handing message {}", response.getResponse(), e);
670         }
671     }
672 }