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