]> git.basschouten.com Git - openhab-addons.git/blob
0f1d8ae748a7fa541af8eff27d41272390449fbe
[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                         } else if (paramType == CommandParameterType.ONOFFNUMBER) {
200                             value = new JsonPrimitive(command == OnOffType.ON ? 1 : 0);
201                         }
202                     } else if (command instanceof DecimalType) {
203                         value = new JsonPrimitive(((DecimalType) command).toBigDecimal());
204                     } else if (command instanceof StringType) {
205                         if (paramType == CommandParameterType.STRING) {
206                             value = new JsonPrimitive(command.toString().toLowerCase());
207                         } else if (paramType == CommandParameterType.CUSTOMSTRING) {
208                             value = new JsonPrimitive(parameters.get(valuePos).getAsString().replace("$value",
209                                     command.toString().toLowerCase()));
210                         }
211                     } else {
212                         value = new JsonPrimitive(command.toString().toLowerCase());
213                     }
214                     if (paramType == CommandParameterType.EMPTY) {
215                         value = new JsonArray();
216                     }
217                     final MiIoDeviceActionCondition miIoDeviceActionCondition = action.getCondition();
218                     if (miIoDeviceActionCondition != null) {
219                         value = ActionConditions.executeAction(miIoDeviceActionCondition, deviceVariables, value,
220                                 command);
221                     }
222                     // Check for miot channel
223                     if (value != null) {
224                         if (action.isMiOtAction()) {
225                             value = miotActionTransform(action, miIoBasicChannel, value);
226                         } else if (miIoBasicChannel.isMiOt()) {
227                             value = miotTransform(miIoBasicChannel, value);
228                         }
229                     }
230                     if (paramType != CommandParameterType.NONE && paramType != CommandParameterType.ONOFFPARA
231                             && value != null) {
232                         if (parameters.size() > 0) {
233                             parameters.set(valuePos, value);
234                         } else {
235                             parameters.add(value);
236                         }
237                     }
238                     if (action.isMiOtAction() && parameters.size() > 0 && parameters.get(0).isJsonObject()) {
239                         // hack as unlike any other commands miot actions parameters appear to be send as a json object
240                         // instead of a json array
241                         cmd = cmd + parameters.get(0).getAsJsonObject().toString();
242                     } else {
243                         cmd = cmd + parameters.toString();
244                     }
245                     if (value != null) {
246                         logger.debug("Sending command {}", cmd);
247                         sendCommand(cmd);
248                     } else {
249                         if (miIoDeviceActionCondition != null) {
250                             logger.debug("Conditional command {} not send, condition '{}' not met", cmd,
251                                     miIoDeviceActionCondition.getName());
252                         } else {
253                             logger.debug("Command not send. Value null");
254                         }
255                     }
256                 }
257             } else {
258                 logger.debug("Channel Id {} not in mapping.", channelUID.getId());
259                 if (logger.isTraceEnabled()) {
260                     for (Entry<ChannelUID, MiIoBasicChannel> a : actions.entrySet()) {
261                         logger.trace("Available entries: {} : {}", a.getKey(), a.getValue().getFriendlyName());
262                     }
263                 }
264             }
265             updateDataCache.invalidateValue();
266             miIoScheduler.schedule(() -> {
267                 updateData();
268             }, 3000, TimeUnit.MILLISECONDS);
269         } else {
270             logger.debug("Actions not loaded yet, or none available");
271         }
272     }
273
274     private @Nullable JsonElement miotTransform(MiIoBasicChannel miIoBasicChannel, @Nullable JsonElement value) {
275         JsonObject json = new JsonObject();
276         json.addProperty("did", miIoBasicChannel.getChannel());
277         json.addProperty("siid", miIoBasicChannel.getSiid());
278         json.addProperty("piid", miIoBasicChannel.getPiid());
279         json.add("value", value);
280         return json;
281     }
282
283     private @Nullable JsonElement miotActionTransform(MiIoDeviceAction action, MiIoBasicChannel miIoBasicChannel,
284             @Nullable JsonElement value) {
285         JsonObject json = new JsonObject();
286         json.addProperty("did", miIoBasicChannel.getChannel());
287         json.addProperty("siid", action.getSiid());
288         json.addProperty("aiid", action.getAiid());
289         if (value != null) {
290             json.add("in", value);
291         }
292         return json;
293     }
294
295     @Override
296     protected synchronized void updateData() {
297         logger.debug("Periodic update for '{}' ({})", getThing().getUID().toString(), getThing().getThingTypeUID());
298         final MiIoAsyncCommunication miioCom = getConnection();
299         try {
300             if (!hasConnection() || skipUpdate() || miioCom == null) {
301                 return;
302             }
303             checkChannelStructure();
304             if (!isIdentified) {
305                 sendCommand(MiIoCommand.MIIO_INFO);
306             }
307             final MiIoBasicDevice midevice = miioDevice;
308             if (midevice != null) {
309                 refreshProperties(midevice);
310                 refreshCustomProperties(midevice);
311                 refreshNetwork();
312             }
313         } catch (Exception e) {
314             logger.debug("Error while updating '{}': ", getThing().getUID().toString(), e);
315         }
316     }
317
318     private void refreshCustomProperties(MiIoBasicDevice midevice) {
319         for (MiIoBasicChannel miChannel : refreshListCustomCommands.values()) {
320             sendCommand(miChannel.getChannelCustomRefreshCommand());
321         }
322     }
323
324     private boolean refreshProperties(MiIoBasicDevice device) {
325         MiIoCommand command = MiIoCommand.getCommand(device.getDevice().getPropertyMethod());
326         int maxProperties = device.getDevice().getMaxProperties();
327         JsonArray getPropString = new JsonArray();
328         for (MiIoBasicChannel miChannel : refreshList) {
329             if (!isLinked(miChannel.getChannel())) {
330                 logger.debug("Skip refresh of channel {} for {} as it is not linked", miChannel.getChannel(),
331                         getThing().getUID());
332                 continue;
333             }
334             JsonElement property;
335             if (miChannel.isMiOt()) {
336                 JsonObject json = new JsonObject();
337                 json.addProperty("did", miChannel.getProperty());
338                 json.addProperty("siid", miChannel.getSiid());
339                 json.addProperty("piid", miChannel.getPiid());
340                 property = json;
341             } else {
342                 property = new JsonPrimitive(miChannel.getProperty());
343             }
344             getPropString.add(property);
345             if (getPropString.size() >= maxProperties) {
346                 sendRefreshProperties(command, getPropString);
347                 getPropString = new JsonArray();
348             }
349         }
350         if (getPropString.size() > 0) {
351             sendRefreshProperties(command, getPropString);
352         }
353         return true;
354     }
355
356     private void sendRefreshProperties(MiIoCommand command, JsonArray getPropString) {
357         sendCommand(command, getPropString.toString());
358     }
359
360     /**
361      * Checks if the channel structure has been build already based on the model data. If not build it.
362      */
363     private void checkChannelStructure() {
364         final MiIoBindingConfiguration configuration = this.configuration;
365         if (configuration == null) {
366             return;
367         }
368         if (!hasChannelStructure) {
369             if (configuration.model == null || configuration.model.isEmpty()) {
370                 logger.debug("Model needs to be determined");
371                 isIdentified = false;
372             } else {
373                 hasChannelStructure = buildChannelStructure(configuration.model);
374             }
375         }
376         if (hasChannelStructure) {
377             refreshList = new ArrayList<>();
378             final MiIoBasicDevice miioDevice = this.miioDevice;
379             if (miioDevice != null) {
380                 for (MiIoBasicChannel miChannel : miioDevice.getDevice().getChannels()) {
381                     if (miChannel.getRefresh()) {
382                         if (miChannel.getChannelCustomRefreshCommand().isBlank()) {
383                             refreshList.add(miChannel);
384                         } else {
385                             String i = miChannel.getChannelCustomRefreshCommand().split("\\[")[0];
386                             refreshListCustomCommands.put(i.trim(), miChannel);
387                         }
388                     }
389                 }
390             }
391
392         }
393     }
394
395     private boolean buildChannelStructure(String deviceName) {
396         logger.debug("Building Channel Structure for {} - Model: {}", getThing().getUID().toString(), deviceName);
397         URL fn = miIoDatabaseWatchService.getDatabaseUrl(deviceName);
398         if (fn == null) {
399             logger.warn("Database entry for model '{}' cannot be found.", deviceName);
400             return false;
401         }
402         try {
403             JsonObject deviceMapping = Utils.convertFileToJSON(fn);
404             logger.debug("Using device database: {} for device {}", fn.getFile(), deviceName);
405             Gson gson = new GsonBuilder().serializeNulls().create();
406             miioDevice = gson.fromJson(deviceMapping, MiIoBasicDevice.class);
407             for (Channel ch : getThing().getChannels()) {
408                 logger.debug("Current thing channels {}, type: {}", ch.getUID(), ch.getChannelTypeUID());
409             }
410             ThingBuilder thingBuilder = editThing();
411             int channelsAdded = 0;
412
413             // make a map of the actions
414             actions = new HashMap<>();
415             final MiIoBasicDevice device = this.miioDevice;
416             if (device != null) {
417                 for (Channel cn : getThing().getChannels()) {
418                     logger.trace("Channel '{}' for thing {} already exist... removing", cn.getUID(),
419                             getThing().getUID());
420                     if (!PERSISTENT_CHANNELS.contains(cn.getUID().getId().toString())) {
421                         thingBuilder.withoutChannels(cn);
422                     }
423                 }
424                 for (MiIoBasicChannel miChannel : device.getDevice().getChannels()) {
425                     logger.debug("properties {}", miChannel);
426                     if (!miChannel.getType().isEmpty()) {
427                         basicChannelTypeProvider.addChannelType(miChannel, deviceName);
428                         ChannelUID channelUID = addChannel(thingBuilder, miChannel, deviceName);
429                         if (channelUID != null) {
430                             actions.put(channelUID, miChannel);
431                             channelsAdded++;
432                         } else {
433                             logger.debug("Channel for {} ({}) not loaded", miChannel.getChannel(),
434                                     miChannel.getFriendlyName());
435                         }
436                     } else {
437                         logger.debug("Channel {} ({}), not loaded, missing type", miChannel.getChannel(),
438                                 miChannel.getFriendlyName());
439                     }
440                 }
441             }
442             // only update if channels were added/removed
443             if (channelsAdded > 0) {
444                 logger.debug("Current thing channels added: {}", channelsAdded);
445                 updateThing(thingBuilder.build());
446             }
447             return true;
448         } catch (JsonIOException | JsonSyntaxException e) {
449             logger.warn("Error parsing database Json", e);
450         } catch (IOException e) {
451             logger.warn("Error reading database file", e);
452         } catch (Exception e) {
453             logger.warn("Error creating channel structure", e);
454         }
455         return false;
456     }
457
458     private @Nullable ChannelUID addChannel(ThingBuilder thingBuilder, MiIoBasicChannel miChannel, String model) {
459         String channel = miChannel.getChannel();
460         String dataType = miChannel.getType();
461         if (channel.isEmpty() || dataType.isEmpty()) {
462             logger.info("Channel '{}', UID '{}' cannot be added incorrectly configured database. ", channel,
463                     getThing().getUID());
464             return null;
465         }
466         ChannelUID channelUID = new ChannelUID(getThing().getUID(), channel);
467         ChannelBuilder newChannel = ChannelBuilder.create(channelUID, dataType).withLabel(miChannel.getFriendlyName());
468         boolean useGeneratedChannelType = false;
469         if (!miChannel.getChannelType().isBlank()) {
470             ChannelTypeUID channelTypeUID = new ChannelTypeUID(miChannel.getChannelType());
471             if (channelTypeRegistry.getChannelType(channelTypeUID) != null) {
472                 newChannel = newChannel.withType(channelTypeUID);
473                 final LinkedHashSet<String> tags = miChannel.getTags();
474                 if (tags != null && tags.size() > 0) {
475                     newChannel.withDefaultTags(tags);
476                 }
477             } else {
478                 logger.debug("ChannelType '{}' is not available. Check the Json file for {}", channelTypeUID, model);
479                 useGeneratedChannelType = true;
480             }
481         } else {
482             useGeneratedChannelType = true;
483         }
484         if (useGeneratedChannelType) {
485             newChannel = newChannel
486                     .withType(new ChannelTypeUID(BINDING_ID, model.toUpperCase().replace(".", "_") + "_" + channel));
487             final LinkedHashSet<String> tags = miChannel.getTags();
488             if (tags != null && tags.size() > 0) {
489                 newChannel.withDefaultTags(tags);
490             }
491         }
492         thingBuilder.withChannel(newChannel.build());
493         return channelUID;
494     }
495
496     private @Nullable MiIoBasicChannel getChannel(String parameter) {
497         for (MiIoBasicChannel refreshEntry : refreshList) {
498             if (refreshEntry.getProperty().equals(parameter)) {
499                 return refreshEntry;
500             }
501         }
502         logger.trace("Did not find channel for {} in {}", parameter, refreshList);
503         return null;
504     }
505
506     private void updatePropsFromJsonArray(MiIoSendCommand response) {
507         JsonArray res = response.getResult().getAsJsonArray();
508         JsonArray para = parser.parse(response.getCommandString()).getAsJsonObject().get("params").getAsJsonArray();
509         if (res.size() != para.size()) {
510             logger.debug("Unexpected size different. Request size {},  response size {}. (Req: {}, Resp:{})",
511                     para.size(), res.size(), para, res);
512             return;
513         }
514         for (int i = 0; i < para.size(); i++) {
515             // This is a miot parameter
516             String param;
517             final JsonElement paraElement = para.get(i);
518             if (paraElement.isJsonObject()) { // miot channel
519                 param = paraElement.getAsJsonObject().get("did").getAsString();
520             } else {
521                 param = paraElement.getAsString();
522             }
523             JsonElement val = res.get(i);
524             if (val.isJsonNull()) {
525                 logger.debug("Property '{}' returned null (is it supported?).", param);
526                 continue;
527             } else if (val.isJsonObject()) { // miot channel
528                 val = val.getAsJsonObject().get("value");
529             }
530             MiIoBasicChannel basicChannel = getChannel(param);
531             updateChannel(basicChannel, param, val);
532         }
533     }
534
535     private void updatePropsFromJsonObject(MiIoSendCommand response) {
536         JsonObject res = response.getResult().getAsJsonObject();
537         for (Object k : res.keySet()) {
538             String param = (String) k;
539             JsonElement val = res.get(param);
540             if (val.isJsonNull()) {
541                 logger.debug("Property '{}' returned null (is it supported?).", param);
542                 continue;
543             }
544             MiIoBasicChannel basicChannel = getChannel(param);
545             updateChannel(basicChannel, param, val);
546         }
547     }
548
549     private void updateChannel(@Nullable MiIoBasicChannel basicChannel, String param, JsonElement value) {
550         JsonElement val = value;
551         if (basicChannel == null) {
552             logger.debug("Channel not found for {}", param);
553             return;
554         }
555         final String transformation = basicChannel.getTransfortmation();
556         if (transformation != null) {
557             JsonElement transformed = Conversions.execute(transformation, val);
558             logger.debug("Transformed with '{}': {} {} -> {} ", transformation, basicChannel.getFriendlyName(), val,
559                     transformed);
560             val = transformed;
561         }
562         try {
563             String[] chType = basicChannel.getType().toLowerCase().split(":");
564             switch (chType[0]) {
565                 case "number":
566                     quantityTypeUpdate(basicChannel, val, chType.length > 1 ? chType[1] : "");
567                     break;
568                 case "dimmer":
569                     updateState(basicChannel.getChannel(), new PercentType(val.getAsBigDecimal()));
570                     break;
571                 case "string":
572                     updateState(basicChannel.getChannel(), new StringType(val.getAsString()));
573                     break;
574                 case "switch":
575                     if (val.getAsJsonPrimitive().isNumber()) {
576                         updateState(basicChannel.getChannel(), val.getAsInt() > 0 ? OnOffType.ON : OnOffType.OFF);
577                     } else {
578                         String strVal = val.getAsString().toLowerCase();
579                         updateState(basicChannel.getChannel(),
580                                 strVal.equals("on") || strVal.equals("true") ? OnOffType.ON : OnOffType.OFF);
581                     }
582                     break;
583                 case "color":
584                     Color rgb = new Color(val.getAsInt());
585                     HSBType hsb = HSBType.fromRGB(rgb.getRed(), rgb.getGreen(), rgb.getBlue());
586                     updateState(basicChannel.getChannel(), hsb);
587                     break;
588                 default:
589                     logger.debug("No update logic for channeltype '{}' ", basicChannel.getType());
590             }
591         } catch (Exception e) {
592             logger.debug("Error updating {} property {} with '{}' : {}: {}", getThing().getUID(),
593                     basicChannel.getChannel(), val, e.getClass().getCanonicalName(), e.getMessage());
594             logger.trace("Property update error detail:", e);
595         }
596     }
597
598     private void quantityTypeUpdate(MiIoBasicChannel basicChannel, JsonElement val, String type) {
599         if (!basicChannel.getUnit().isBlank()) {
600             Unit<?> unit = MiIoQuantiyTypes.get(basicChannel.getUnit());
601             if (unit != null) {
602                 logger.debug("'{}' channel '{}' has unit '{}' with symbol '{}'.", getThing().getUID(),
603                         basicChannel.getChannel(), basicChannel.getUnit(), unit);
604                 updateState(basicChannel.getChannel(), new QuantityType<>(val.getAsBigDecimal(), unit));
605             } else {
606                 logger.debug(
607                         "Unit '{}' used by '{}' channel '{}' is not found in conversion table... Trying anyway to submit as the update.",
608                         basicChannel.getUnit(), getThing().getUID(), basicChannel.getChannel());
609                 updateState(basicChannel.getChannel(),
610                         new QuantityType<>(val.getAsBigDecimal().toPlainString() + " " + basicChannel.getUnit()));
611             }
612             return;
613         }
614         // if no unit is provided or unit not found use default units, these units have so far been seen for miio
615         // devices
616         switch (type.toLowerCase()) {
617             case "temperature":
618                 updateState(basicChannel.getChannel(), new QuantityType<>(val.getAsBigDecimal(), SIUnits.CELSIUS));
619                 break;
620             case "electriccurrent":
621                 updateState(basicChannel.getChannel(), new QuantityType<>(val.getAsBigDecimal(), Units.AMPERE));
622                 break;
623             case "energy":
624                 updateState(basicChannel.getChannel(), new QuantityType<>(val.getAsBigDecimal(), Units.WATT));
625                 break;
626             case "time":
627                 updateState(basicChannel.getChannel(), new QuantityType<>(val.getAsBigDecimal(), Units.HOUR));
628                 break;
629             default:
630                 updateState(basicChannel.getChannel(), new DecimalType(val.getAsBigDecimal()));
631         }
632     }
633
634     @Override
635     public void onMessageReceived(MiIoSendCommand response) {
636         super.onMessageReceived(response);
637         if (response.isError()) {
638             return;
639         }
640         try {
641             switch (response.getCommand()) {
642                 case MIIO_INFO:
643                     break;
644                 case GET_VALUE:
645                 case GET_PROPERTIES:
646                 case GET_PROPERTY:
647                     if (response.getResult().isJsonArray()) {
648                         updatePropsFromJsonArray(response);
649                     } else if (response.getResult().isJsonObject()) {
650                         updatePropsFromJsonObject(response);
651                     }
652                     break;
653                 default:
654                     if (refreshListCustomCommands.containsKey(response.getMethod())) {
655                         logger.debug("Processing custom refresh command response for !{}", response.getMethod());
656                         final MiIoBasicChannel ch = refreshListCustomCommands.get(response.getMethod());
657                         if (ch != null) {
658                             if (response.getResult().isJsonArray()) {
659                                 JsonArray cmdResponse = response.getResult().getAsJsonArray();
660                                 final String transformation = ch.getTransfortmation();
661                                 if (transformation == null || transformation.isBlank()) {
662                                     JsonElement response0 = cmdResponse.get(0);
663                                     updateChannel(ch, ch.getChannel(), response0.isJsonPrimitive() ? response0
664                                             : new JsonPrimitive(response0.toString()));
665                                 } else {
666                                     updateChannel(ch, ch.getChannel(), cmdResponse);
667                                 }
668                             } else {
669                                 updateChannel(ch, ch.getChannel(), new JsonPrimitive(response.getResult().toString()));
670                             }
671                         }
672                     }
673                     break;
674             }
675         } catch (Exception e) {
676             logger.debug("Error while handing message {}", response.getResponse(), e);
677         }
678     }
679 }