2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.miio.internal.handler;
15 import static org.openhab.binding.miio.internal.MiIoBindingConstants.*;
17 import java.awt.Color;
18 import java.io.IOException;
20 import java.time.Instant;
21 import java.util.ArrayList;
22 import java.util.HashMap;
23 import java.util.LinkedHashSet;
24 import java.util.List;
26 import java.util.Map.Entry;
28 import java.util.concurrent.TimeUnit;
30 import javax.measure.Unit;
31 import javax.measure.format.MeasurementParseException;
33 import org.eclipse.jdt.annotation.NonNullByDefault;
34 import org.eclipse.jdt.annotation.Nullable;
35 import org.openhab.binding.miio.internal.MiIoBindingConfiguration;
36 import org.openhab.binding.miio.internal.MiIoCommand;
37 import org.openhab.binding.miio.internal.MiIoQuantiyTypes;
38 import org.openhab.binding.miio.internal.MiIoSendCommand;
39 import org.openhab.binding.miio.internal.Utils;
40 import org.openhab.binding.miio.internal.basic.ActionConditions;
41 import org.openhab.binding.miio.internal.basic.BasicChannelTypeProvider;
42 import org.openhab.binding.miio.internal.basic.CommandParameterType;
43 import org.openhab.binding.miio.internal.basic.Conversions;
44 import org.openhab.binding.miio.internal.basic.MiIoBasicChannel;
45 import org.openhab.binding.miio.internal.basic.MiIoBasicDevice;
46 import org.openhab.binding.miio.internal.basic.MiIoDatabaseWatchService;
47 import org.openhab.binding.miio.internal.basic.MiIoDeviceAction;
48 import org.openhab.binding.miio.internal.basic.MiIoDeviceActionCondition;
49 import org.openhab.binding.miio.internal.cloud.CloudConnector;
50 import org.openhab.binding.miio.internal.transport.MiIoAsyncCommunication;
51 import org.openhab.core.cache.ExpiringCache;
52 import org.openhab.core.library.types.DecimalType;
53 import org.openhab.core.library.types.HSBType;
54 import org.openhab.core.library.types.OnOffType;
55 import org.openhab.core.library.types.OpenClosedType;
56 import org.openhab.core.library.types.PercentType;
57 import org.openhab.core.library.types.QuantityType;
58 import org.openhab.core.library.types.StringType;
59 import org.openhab.core.library.unit.SIUnits;
60 import org.openhab.core.library.unit.Units;
61 import org.openhab.core.thing.Channel;
62 import org.openhab.core.thing.ChannelUID;
63 import org.openhab.core.thing.Thing;
64 import org.openhab.core.thing.binding.builder.ChannelBuilder;
65 import org.openhab.core.thing.binding.builder.ThingBuilder;
66 import org.openhab.core.thing.type.ChannelTypeRegistry;
67 import org.openhab.core.thing.type.ChannelTypeUID;
68 import org.openhab.core.types.Command;
69 import org.openhab.core.types.RefreshType;
70 import org.slf4j.Logger;
71 import org.slf4j.LoggerFactory;
73 import com.google.gson.Gson;
74 import com.google.gson.GsonBuilder;
75 import com.google.gson.JsonArray;
76 import com.google.gson.JsonElement;
77 import com.google.gson.JsonIOException;
78 import com.google.gson.JsonObject;
79 import com.google.gson.JsonParser;
80 import com.google.gson.JsonPrimitive;
81 import com.google.gson.JsonSyntaxException;
84 * The {@link MiIoBasicHandler} is responsible for handling commands, which are
85 * sent to one of the channels.
87 * @author Marcel Verpaalen - Initial contribution
90 public class MiIoBasicHandler extends MiIoAbstractHandler {
91 private final Logger logger = LoggerFactory.getLogger(MiIoBasicHandler.class);
92 private boolean hasChannelStructure;
94 private final ExpiringCache<Boolean> updateDataCache = new ExpiringCache<>(CACHE_EXPIRY, () -> {
95 miIoScheduler.schedule(this::updateData, 0, TimeUnit.SECONDS);
99 List<MiIoBasicChannel> refreshList = new ArrayList<>();
100 private Map<String, MiIoBasicChannel> refreshListCustomCommands = new HashMap<>();
102 private @Nullable MiIoBasicDevice miioDevice;
103 private Map<ChannelUID, MiIoBasicChannel> actions = new HashMap<>();
104 private ChannelTypeRegistry channelTypeRegistry;
105 private BasicChannelTypeProvider basicChannelTypeProvider;
107 public MiIoBasicHandler(Thing thing, MiIoDatabaseWatchService miIoDatabaseWatchService,
108 CloudConnector cloudConnector, ChannelTypeRegistry channelTypeRegistry,
109 BasicChannelTypeProvider basicChannelTypeProvider) {
110 super(thing, miIoDatabaseWatchService, cloudConnector);
111 this.channelTypeRegistry = channelTypeRegistry;
112 this.basicChannelTypeProvider = basicChannelTypeProvider;
116 public void initialize() {
118 hasChannelStructure = false;
119 isIdentified = false;
120 refreshList = new ArrayList<>();
121 refreshListCustomCommands = new HashMap<>();
125 public void handleCommand(ChannelUID channelUID, Command receivedCommand) {
126 Command command = receivedCommand;
127 deviceVariables.put(TIMESTAMP, Instant.now().getEpochSecond());
128 if (command == RefreshType.REFRESH) {
129 if (updateDataCache.isExpired()) {
130 logger.debug("Refreshing {}", channelUID);
131 updateDataCache.getValue();
133 logger.debug("Refresh {} skipped. Already refreshing", channelUID);
137 if (handleCommandsChannels(channelUID, command)) {
141 logger.debug("Locating action for {} channel '{}': '{}'", getThing().getUID(), channelUID.getId(), command);
142 if (!actions.isEmpty()) {
143 final MiIoBasicChannel miIoBasicChannel = actions.get(channelUID);
144 if (miIoBasicChannel != null) {
146 for (MiIoDeviceAction action : miIoBasicChannel.getActions()) {
148 JsonElement value = null;
149 JsonArray parameters = action.getParameters().deepCopy();
150 for (int i = 0; i < action.getParameters().size(); i++) {
151 JsonElement p = action.getParameters().get(i);
152 if (p.isJsonPrimitive() && p.getAsString().toLowerCase().contains("$value$")) {
157 String cmd = action.getCommand();
158 CommandParameterType paramType = action.getparameterType();
159 if (command instanceof QuantityType) {
160 QuantityType<?> qtc = null;
162 if (!miIoBasicChannel.getUnit().isBlank()) {
163 Unit<?> unit = MiIoQuantiyTypes.get(miIoBasicChannel.getUnit());
165 qtc = ((QuantityType<?>) command).toUnit(unit);
168 } catch (MeasurementParseException e) {
172 command = new DecimalType(qtc.toBigDecimal());
174 logger.debug("Could not convert QuantityType to '{}'", miIoBasicChannel.getUnit());
175 command = new DecimalType(((QuantityType<?>) command).toBigDecimal());
178 if (paramType == CommandParameterType.OPENCLOSE) {
179 if (command instanceof OpenClosedType) {
180 value = new JsonPrimitive(command == OpenClosedType.OPEN ? "open" : "close");
182 value = new JsonPrimitive(("ON".contentEquals(command.toString().toUpperCase())
183 || "1".contentEquals(command.toString())) ? "open" : "close");
186 if (paramType == CommandParameterType.OPENCLOSENUMBER) {
187 if (command instanceof OpenClosedType) {
188 value = new JsonPrimitive(command == OpenClosedType.OPEN ? 1 : 0);
190 value = new JsonPrimitive(("ON".contentEquals(command.toString().toUpperCase())
191 || "1".contentEquals(command.toString())) ? 1 : 0);
194 if (paramType == CommandParameterType.OPENCLOSESWITCH) {
195 if (command instanceof OpenClosedType) {
196 value = new JsonPrimitive(command == OpenClosedType.OPEN ? "on" : "off");
198 value = new JsonPrimitive(("ON".contentEquals(command.toString().toUpperCase())
199 || "1".contentEquals(command.toString())) ? "on" : "off");
202 if (paramType == CommandParameterType.COLOR) {
203 if (command instanceof HSBType) {
204 HSBType hsb = (HSBType) command;
205 Color color = Color.getHSBColor(hsb.getHue().floatValue() / 360,
206 hsb.getSaturation().floatValue() / 100, hsb.getBrightness().floatValue() / 100);
207 value = new JsonPrimitive(
208 (color.getRed() << 16) + (color.getGreen() << 8) + color.getBlue());
209 } else if (command instanceof DecimalType) {
210 // actually brightness is being set instead of a color
211 value = new JsonPrimitive(((DecimalType) command).toBigDecimal());
212 } else if (command instanceof OnOffType) {
213 value = new JsonPrimitive(command == OnOffType.ON ? 100 : 0);
215 logger.debug("Unsupported command for COLOR: {}", command);
217 } else if (command instanceof OnOffType) {
218 if (paramType == CommandParameterType.ONOFF) {
219 value = new JsonPrimitive(command == OnOffType.ON ? "on" : "off");
220 } else if (paramType == CommandParameterType.ONOFFPARA) {
221 cmd = cmd.replace("*", command == OnOffType.ON ? "on" : "off");
222 value = new JsonArray();
223 } else if (paramType == CommandParameterType.ONOFFBOOL) {
224 boolean boolCommand = command == OnOffType.ON;
225 value = new JsonPrimitive(boolCommand);
226 } else if (paramType == CommandParameterType.ONOFFBOOLSTRING) {
227 value = new JsonPrimitive(command == OnOffType.ON ? "true" : "false");
228 } else if (paramType == CommandParameterType.ONOFFNUMBER) {
229 value = new JsonPrimitive(command == OnOffType.ON ? 1 : 0);
231 } else if (command instanceof DecimalType) {
232 value = new JsonPrimitive(((DecimalType) command).toBigDecimal());
233 } else if (command instanceof StringType) {
234 if (paramType == CommandParameterType.STRING) {
235 value = new JsonPrimitive(command.toString().toLowerCase());
236 } else if (paramType == CommandParameterType.CUSTOMSTRING) {
237 value = new JsonPrimitive(parameters.get(valuePos).getAsString().replace("$value",
238 command.toString().toLowerCase()));
241 value = new JsonPrimitive(command.toString().toLowerCase());
243 if (paramType == CommandParameterType.EMPTY) {
244 value = new JsonArray();
246 final MiIoDeviceActionCondition miIoDeviceActionCondition = action.getCondition();
247 if (miIoDeviceActionCondition != null) {
248 value = ActionConditions.executeAction(miIoDeviceActionCondition, deviceVariables, value,
251 // Check for miot channel
253 if (action.isMiOtAction()) {
254 value = miotActionTransform(action, miIoBasicChannel, value);
255 } else if (miIoBasicChannel.isMiOt()) {
256 value = miotTransform(miIoBasicChannel, value);
259 if (paramType != CommandParameterType.NONE && paramType != CommandParameterType.ONOFFPARA
261 if (parameters.size() > 0) {
262 parameters.set(valuePos, value);
264 parameters.add(value);
267 if (action.isMiOtAction() && parameters.size() > 0 && parameters.get(0).isJsonObject()) {
268 // hack as unlike any other commands miot actions parameters appear to be send as a json object
269 // instead of a json array
270 cmd = cmd + parameters.get(0).getAsJsonObject().toString();
272 cmd = cmd + parameters.toString();
275 logger.debug("Sending command {}", cmd);
278 if (miIoDeviceActionCondition != null) {
279 logger.debug("Conditional command {} not send, condition '{}' not met", cmd,
280 miIoDeviceActionCondition.getName());
282 logger.debug("Command not send. Value null");
287 logger.debug("Channel Id {} not in mapping.", channelUID.getId());
288 if (logger.isTraceEnabled()) {
289 for (Entry<ChannelUID, MiIoBasicChannel> a : actions.entrySet()) {
290 logger.trace("Available entries: {} : {}", a.getKey(), a.getValue().getFriendlyName());
296 logger.debug("Actions not loaded yet, or none available");
300 private void forceStatusUpdate() {
301 updateDataCache.invalidateValue();
302 miIoScheduler.schedule(() -> {
304 }, 3000, TimeUnit.MILLISECONDS);
307 private @Nullable JsonElement miotTransform(MiIoBasicChannel miIoBasicChannel, @Nullable JsonElement value) {
308 JsonObject json = new JsonObject();
309 json.addProperty("did", miIoBasicChannel.getChannel());
310 json.addProperty("siid", miIoBasicChannel.getSiid());
311 json.addProperty("piid", miIoBasicChannel.getPiid());
312 json.add("value", value);
316 private @Nullable JsonElement miotActionTransform(MiIoDeviceAction action, MiIoBasicChannel miIoBasicChannel,
317 @Nullable JsonElement value) {
318 JsonObject json = new JsonObject();
319 json.addProperty("did", miIoBasicChannel.getChannel());
320 json.addProperty("siid", action.getSiid());
321 json.addProperty("aiid", action.getAiid());
323 json.add("in", value);
329 protected synchronized void updateData() {
330 logger.debug("Periodic update for '{}' ({})", getThing().getUID().toString(), getThing().getThingTypeUID());
331 final MiIoAsyncCommunication miioCom = getConnection();
333 if (!hasConnection() || skipUpdate() || miioCom == null) {
336 checkChannelStructure();
338 sendCommand(MiIoCommand.MIIO_INFO);
340 final MiIoBasicDevice midevice = miioDevice;
341 if (midevice != null) {
342 deviceVariables.put(TIMESTAMP, Instant.now().getEpochSecond());
343 refreshProperties(midevice);
344 refreshCustomProperties(midevice);
347 } catch (Exception e) {
348 logger.debug("Error while updating '{}': ", getThing().getUID().toString(), e);
352 private void refreshCustomProperties(MiIoBasicDevice midevice) {
353 for (MiIoBasicChannel miChannel : refreshListCustomCommands.values()) {
354 if (!isLinked(miChannel.getChannel())) {
355 logger.debug("Skip refresh of channel {} for {} as it is not linked", miChannel.getChannel(),
356 getThing().getUID());
359 String cmd = miChannel.getChannelCustomRefreshCommand();
360 if (!cmd.startsWith("/")) {
361 cmds.put(sendCommand(miChannel.getChannelCustomRefreshCommand()), miChannel.getChannel());
363 if (cloudServer.isBlank()) {
364 logger.debug("Cloudserver empty. Skipping refresh for {} channel '{}'", getThing().getUID(),
365 miChannel.getChannel());
367 cmds.put(sendCommand(cmd, cloudServer), miChannel.getChannel());
373 private boolean refreshProperties(MiIoBasicDevice device) {
374 MiIoCommand command = MiIoCommand.getCommand(device.getDevice().getPropertyMethod());
375 int maxProperties = device.getDevice().getMaxProperties();
376 JsonArray getPropString = new JsonArray();
377 for (MiIoBasicChannel miChannel : refreshList) {
378 if (!isLinked(miChannel.getChannel())) {
379 logger.debug("Skip refresh of channel {} for {} as it is not linked", miChannel.getChannel(),
380 getThing().getUID());
383 JsonElement property;
384 if (miChannel.isMiOt()) {
385 JsonObject json = new JsonObject();
386 json.addProperty("did", miChannel.getProperty());
387 json.addProperty("siid", miChannel.getSiid());
388 json.addProperty("piid", miChannel.getPiid());
391 property = new JsonPrimitive(miChannel.getProperty());
393 getPropString.add(property);
394 if (getPropString.size() >= maxProperties) {
395 sendRefreshProperties(command, getPropString);
396 getPropString = new JsonArray();
399 if (getPropString.size() > 0) {
400 sendRefreshProperties(command, getPropString);
405 private void sendRefreshProperties(MiIoCommand command, JsonArray getPropString) {
406 sendCommand(command, getPropString.toString());
410 * Checks if the channel structure has been build already based on the model data. If not build it.
412 private void checkChannelStructure() {
413 final MiIoBindingConfiguration configuration = this.configuration;
414 if (configuration == null) {
417 if (!hasChannelStructure) {
418 if (configuration.model.isEmpty()) {
419 logger.debug("Model needs to be determined");
420 isIdentified = false;
422 hasChannelStructure = buildChannelStructure(configuration.model);
425 if (hasChannelStructure) {
426 refreshList = new ArrayList<>();
427 refreshListCustomCommands = new HashMap<>();
428 final MiIoBasicDevice miioDevice = this.miioDevice;
429 if (miioDevice != null) {
430 for (MiIoBasicChannel miChannel : miioDevice.getDevice().getChannels()) {
431 if (miChannel.getRefresh()) {
432 if (miChannel.getChannelCustomRefreshCommand().isBlank()) {
433 refreshList.add(miChannel);
435 String i = miChannel.getChannelCustomRefreshCommand().split("\\[")[0];
436 refreshListCustomCommands.put(i.trim(), miChannel);
445 private boolean buildChannelStructure(String deviceName) {
446 logger.debug("Building Channel Structure for {} - Model: {}", getThing().getUID().toString(), deviceName);
447 URL fn = miIoDatabaseWatchService.getDatabaseUrl(deviceName);
449 logger.warn("Database entry for model '{}' cannot be found.", deviceName);
453 JsonObject deviceMapping = Utils.convertFileToJSON(fn);
454 logger.debug("Using device database: {} for device {}", fn.getFile(), deviceName);
455 Gson gson = new GsonBuilder().serializeNulls().create();
456 miioDevice = gson.fromJson(deviceMapping, MiIoBasicDevice.class);
457 for (Channel ch : getThing().getChannels()) {
458 logger.debug("Current thing channels {}, type: {}", ch.getUID(), ch.getChannelTypeUID());
460 ThingBuilder thingBuilder = editThing();
461 int channelsAdded = 0;
463 // make a map of the actions
464 actions = new HashMap<>();
465 final MiIoBasicDevice device = this.miioDevice;
466 if (device != null) {
467 for (Channel cn : getThing().getChannels()) {
468 logger.trace("Channel '{}' for thing {} already exist... removing", cn.getUID(),
469 getThing().getUID());
470 if (!PERSISTENT_CHANNELS.contains(cn.getUID().getId().toString())) {
471 thingBuilder.withoutChannels(cn);
474 for (MiIoBasicChannel miChannel : device.getDevice().getChannels()) {
475 logger.debug("properties {}", miChannel);
476 if (!miChannel.getType().isEmpty()) {
477 basicChannelTypeProvider.addChannelType(miChannel, deviceName);
478 ChannelUID channelUID = addChannel(thingBuilder, miChannel, deviceName);
479 if (channelUID != null) {
480 actions.put(channelUID, miChannel);
483 logger.debug("Channel for {} ({}) not loaded", miChannel.getChannel(),
484 miChannel.getFriendlyName());
487 logger.debug("Channel {} ({}), not loaded, missing type", miChannel.getChannel(),
488 miChannel.getFriendlyName());
492 // only update if channels were added/removed
493 if (channelsAdded > 0) {
494 logger.debug("Current thing channels added: {}", channelsAdded);
495 updateThing(thingBuilder.build());
498 } catch (JsonIOException | JsonSyntaxException e) {
499 logger.warn("Error parsing database Json", e);
500 } catch (IOException e) {
501 logger.warn("Error reading database file", e);
502 } catch (Exception e) {
503 logger.warn("Error creating channel structure", e);
508 private @Nullable ChannelUID addChannel(ThingBuilder thingBuilder, MiIoBasicChannel miChannel, String model) {
509 String channel = miChannel.getChannel();
510 String dataType = miChannel.getType();
511 if (channel.isEmpty() || dataType.isEmpty()) {
512 logger.info("Channel '{}', UID '{}' cannot be added incorrectly configured database. ", channel,
513 getThing().getUID());
516 ChannelUID channelUID = new ChannelUID(getThing().getUID(), channel);
517 ChannelBuilder newChannel = ChannelBuilder.create(channelUID, dataType).withLabel(miChannel.getFriendlyName());
518 boolean useGeneratedChannelType = false;
519 if (!miChannel.getChannelType().isBlank()) {
520 ChannelTypeUID channelTypeUID = new ChannelTypeUID(miChannel.getChannelType());
521 if (channelTypeRegistry.getChannelType(channelTypeUID) != null) {
522 newChannel = newChannel.withType(channelTypeUID);
523 final LinkedHashSet<String> tags = miChannel.getTags();
524 if (tags != null && !tags.isEmpty()) {
525 newChannel.withDefaultTags(tags);
528 logger.debug("ChannelType '{}' is not available. Check the Json file for {}", channelTypeUID, model);
529 useGeneratedChannelType = true;
532 useGeneratedChannelType = true;
534 if (useGeneratedChannelType) {
535 newChannel = newChannel
536 .withType(new ChannelTypeUID(BINDING_ID, model.toUpperCase().replace(".", "_") + "_" + channel));
537 final Set<String> tags = miChannel.getTags();
538 if (tags != null && !tags.isEmpty()) {
539 newChannel.withDefaultTags(tags);
542 thingBuilder.withChannel(newChannel.build());
546 private @Nullable MiIoBasicChannel getChannel(String parameter) {
547 for (MiIoBasicChannel refreshEntry : refreshList) {
548 if (refreshEntry.getProperty().equals(parameter)) {
552 logger.trace("Did not find channel for {} in {}", parameter, refreshList);
556 private @Nullable MiIoBasicChannel getCustomRefreshChannel(String channelName) {
557 for (MiIoBasicChannel refreshEntry : refreshListCustomCommands.values()) {
558 if (refreshEntry.getChannel().equals(channelName)) {
562 logger.trace("Did not find channel for {} in {}", channelName, refreshList);
566 private void updatePropsFromJsonArray(MiIoSendCommand response) {
567 JsonArray res = response.getResult().getAsJsonArray();
568 JsonArray para = JsonParser.parseString(response.getCommandString()).getAsJsonObject().get("params")
570 if (res.size() != para.size()) {
571 logger.debug("Unexpected size different. Request size {}, response size {}. (Req: {}, Resp:{})",
572 para.size(), res.size(), para, res);
575 for (int i = 0; i < para.size(); i++) {
576 // This is a miot parameter
578 final JsonElement paraElement = para.get(i);
579 if (paraElement.isJsonObject()) { // miot channel
580 param = paraElement.getAsJsonObject().get("did").getAsString();
582 param = paraElement.getAsString();
584 JsonElement val = res.get(i);
585 if (val.isJsonNull()) {
586 logger.debug("Property '{}' returned null (is it supported?).", param);
588 } else if (val.isJsonObject()) { // miot channel
589 val = val.getAsJsonObject().get("value");
591 MiIoBasicChannel basicChannel = getChannel(param);
592 updateChannel(basicChannel, param, val);
596 private void updatePropsFromJsonObject(MiIoSendCommand response) {
597 JsonObject res = response.getResult().getAsJsonObject();
598 for (Object k : res.keySet()) {
599 String param = (String) k;
600 JsonElement val = res.get(param);
601 if (val.isJsonNull()) {
602 logger.debug("Property '{}' returned null (is it supported?).", param);
605 MiIoBasicChannel basicChannel = getChannel(param);
606 updateChannel(basicChannel, param, val);
610 private void updateChannel(@Nullable MiIoBasicChannel basicChannel, String param, JsonElement value) {
611 JsonElement val = value;
612 deviceVariables.put(param, val);
613 if (basicChannel == null) {
614 logger.debug("Channel not found for {}", param);
617 final String transformation = basicChannel.getTransformation();
618 if (transformation != null) {
619 JsonElement transformed = Conversions.execute(transformation, val, deviceVariables);
620 logger.debug("Transformed with '{}': {} {} -> {} ", transformation, basicChannel.getFriendlyName(), val,
625 String[] chType = basicChannel.getType().toLowerCase().split(":");
628 quantityTypeUpdate(basicChannel, val, chType.length > 1 ? chType[1] : "");
631 updateState(basicChannel.getChannel(), new PercentType(val.getAsBigDecimal()));
634 if (val.isJsonPrimitive()) {
635 updateState(basicChannel.getChannel(), new StringType(val.getAsString()));
637 updateState(basicChannel.getChannel(), new StringType(val.toString()));
641 if (val.getAsJsonPrimitive().isNumber()) {
642 updateState(basicChannel.getChannel(), val.getAsInt() > 0 ? OnOffType.ON : OnOffType.OFF);
644 String strVal = val.getAsString().toLowerCase();
645 updateState(basicChannel.getChannel(),
646 "on".equals(strVal) || "true".equals(strVal) || "1".equals(strVal) ? OnOffType.ON
651 if (val.getAsJsonPrimitive().isNumber()) {
652 updateState(basicChannel.getChannel(),
653 val.getAsInt() > 0 ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
655 String strVal = val.getAsString().toLowerCase();
656 updateState(basicChannel.getChannel(),
657 "open".equals(strVal) || "on".equals(strVal) || "true".equals(strVal)
658 || "1".equals(strVal) ? OpenClosedType.OPEN : OpenClosedType.CLOSED);
662 if (val.isJsonPrimitive()
663 && (val.getAsJsonPrimitive().isNumber() || val.getAsString().matches("^[0-9]+$"))) {
664 Color rgb = new Color(val.getAsInt());
665 HSBType hsb = HSBType.fromRGB(rgb.getRed(), rgb.getGreen(), rgb.getBlue());
666 updateState(basicChannel.getChannel(), hsb);
669 HSBType hsb = HSBType.valueOf(val.getAsString().replace("[", "").replace("]", ""));
670 updateState(basicChannel.getChannel(), hsb);
671 } catch (IllegalArgumentException e) {
672 logger.debug("Failed updating channel '{}'. Could not convert '{}' to color",
673 basicChannel.getChannel(), val.getAsString());
678 logger.debug("No update logic for channeltype '{}' ", basicChannel.getType());
680 } catch (Exception e) {
681 logger.debug("Error updating {} property {} with '{}' : {}: {}", getThing().getUID(),
682 basicChannel.getChannel(), val, e.getClass().getCanonicalName(), e.getMessage());
683 logger.trace("Property update error detail:", e);
687 private void quantityTypeUpdate(MiIoBasicChannel basicChannel, JsonElement val, String type) {
688 if (!basicChannel.getUnit().isBlank()) {
689 Unit<?> unit = MiIoQuantiyTypes.get(basicChannel.getUnit());
691 logger.debug("'{}' channel '{}' has unit '{}' with symbol '{}'.", getThing().getUID(),
692 basicChannel.getChannel(), basicChannel.getUnit(), unit);
693 updateState(basicChannel.getChannel(), new QuantityType<>(val.getAsBigDecimal(), unit));
696 "Unit '{}' used by '{}' channel '{}' is not found in conversion table... Trying anyway to submit as the update.",
697 basicChannel.getUnit(), getThing().getUID(), basicChannel.getChannel());
698 updateState(basicChannel.getChannel(),
699 new QuantityType<>(val.getAsBigDecimal().toPlainString() + " " + basicChannel.getUnit()));
703 // if no unit is provided or unit not found use default units, these units have so far been seen for miio
705 switch (type.toLowerCase()) {
707 updateState(basicChannel.getChannel(), new QuantityType<>(val.getAsBigDecimal(), SIUnits.CELSIUS));
709 case "electriccurrent":
710 updateState(basicChannel.getChannel(), new QuantityType<>(val.getAsBigDecimal(), Units.AMPERE));
713 updateState(basicChannel.getChannel(), new QuantityType<>(val.getAsBigDecimal(), Units.WATT));
716 updateState(basicChannel.getChannel(), new QuantityType<>(val.getAsBigDecimal(), Units.HOUR));
719 updateState(basicChannel.getChannel(), new DecimalType(val.getAsBigDecimal()));
724 public void onMessageReceived(MiIoSendCommand response) {
725 super.onMessageReceived(response);
726 if (response.isError()) {
730 switch (response.getCommand()) {
733 case GET_DEVICE_PROPERTY_EXP:
737 if (response.getResult().isJsonArray()) {
738 updatePropsFromJsonArray(response);
739 } else if (response.getResult().isJsonObject()) {
740 updatePropsFromJsonObject(response);
744 String channel = cmds.get(response.getId());
745 if (channel != null) {
746 logger.debug("Processing custom refresh command response for '{}' - {}", response.getMethod(),
747 response.getResult());
748 final MiIoBasicChannel ch = getCustomRefreshChannel(channel);
750 if (response.getResult().isJsonArray()) {
751 JsonArray cmdResponse = response.getResult().getAsJsonArray();
752 final String transformation = ch.getTransformation();
753 if (transformation == null || transformation.isBlank()) {
754 JsonElement response0 = cmdResponse.get(0);
755 updateChannel(ch, ch.getChannel(), response0.isJsonPrimitive() ? response0
756 : new JsonPrimitive(response0.toString()));
758 updateChannel(ch, ch.getChannel(), cmdResponse);
761 updateChannel(ch, ch.getChannel(), new JsonPrimitive(response.getResult().toString()));
764 cmds.remove(response.getId());
768 } catch (Exception e) {
769 logger.debug("Error while handing message {}", response.getResponse(), e);