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