From bd8de55af3c4a046c2a3dc5270064bb06d7b6864 Mon Sep 17 00:00:00 2001 From: "Dmitry P. (d51x)" Date: Wed, 5 Jul 2023 18:27:20 +0300 Subject: [PATCH 01/15] [tuya] IR Controller: changed the name of the parameters Signed-off-by: Dmitry P. (d51x) --- bundles/org.smarthomej.binding.tuya/README.md | 36 +++ .../tuya/internal/TuyaBindingConstants.java | 3 + .../internal/config/ChannelConfiguration.java | 3 + .../internal/handler/TuyaDeviceHandler.java | 37 ++- .../binding/tuya/internal/util/IrUtils.java | 294 ++++++++++++++++++ .../resources/OH-INF/thing/thing-types.xml | 35 ++- 6 files changed, 401 insertions(+), 7 deletions(-) create mode 100644 bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/util/IrUtils.java diff --git a/bundles/org.smarthomej.binding.tuya/README.md b/bundles/org.smarthomej.binding.tuya/README.md index dc6fe38dc5..a2583806bd 100644 --- a/bundles/org.smarthomej.binding.tuya/README.md +++ b/bundles/org.smarthomej.binding.tuya/README.md @@ -117,6 +117,42 @@ The `min` and `max` parameters define the range allowed (e.g. 0-86400 for turn-o The `string` channel has one additional (optional) parameter `range`. It contains a comma-separated list of command options for this channel (e.g. `white,colour,scene,music` for the "workMode" channel). +### Type `ircode1` + +IR Code (Template) - use codes from templates library. + +Make a virtual remote control from pre-defined type of devices. + +The `ircode1` channel has three additional (mandatory) parameters: + +* `irCode` - Decoding parameter +* `irSendDelay` - used as `Send delay` parameter +* `irCodeType` - used as `type library` parameter + +If linked item received a command with `Key Code` (Code Library Parameter) then device sends appropriate key code. + +### Type `ircode2` + +IR Code (DIY Mode) - use study codes from real remotes. Make a virtual remote control in DIY, learn virtual buttons. + +The `ircode2` channel has no additional parameters. + +If linked item received a command with `Key Code` (Learning Code Parameter) then device sends appropriate key code. + +### Type `nec_code` + +IR Code in NEC format. + +Example, from Tasmota you need to use **_Data_** parameter, it can be with or without **_0x_** +```json +{"Time": "2023-07-05T18:17:42", "IrReceived": {"Protocol": "NEC", "Bits": 32, "Data": "0x10EFD02F"}} +``` + +Another example, use **_hex_** parameter +```json +{ "type": "nec", "uint32": 284151855, "address": 8, "data": 11, "hex": "10EFD02F" } +``` + ## Troubleshooting - If the `project` thing is not coming `ONLINE` check if you see your devices in the cloud-account on `iot.tuya.com`. diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/TuyaBindingConstants.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/TuyaBindingConstants.java index 3170653e52..01d0e99144 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/TuyaBindingConstants.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/TuyaBindingConstants.java @@ -56,6 +56,9 @@ public class TuyaBindingConstants { public static final ChannelTypeUID CHANNEL_TYPE_UID_NUMBER = new ChannelTypeUID(BINDING_ID, "number"); public static final ChannelTypeUID CHANNEL_TYPE_UID_STRING = new ChannelTypeUID(BINDING_ID, "string"); public static final ChannelTypeUID CHANNEL_TYPE_UID_SWITCH = new ChannelTypeUID(BINDING_ID, "switch"); + public static final ChannelTypeUID CHANNEL_TYPE_UID_IR_CODE_TEMPLATE = new ChannelTypeUID(BINDING_ID, "ircode1"); + public static final ChannelTypeUID CHANNEL_TYPE_UID_IR_CODE_DIY = new ChannelTypeUID(BINDING_ID, "ircode2"); + public static final ChannelTypeUID CHANNEL_TYPE_UID_IR_CODE_NEC = new ChannelTypeUID(BINDING_ID, "nec_code"); public static final int TCP_CONNECTION_HEARTBEAT_INTERVAL = 10; // in s public static final int TCP_CONNECTION_TIMEOUT = 60; // in s; diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/config/ChannelConfiguration.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/config/ChannelConfiguration.java index 428a7a9f24..f59d16f1d2 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/config/ChannelConfiguration.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/config/ChannelConfiguration.java @@ -26,4 +26,7 @@ public class ChannelConfiguration { public int min = Integer.MIN_VALUE; public int max = Integer.MAX_VALUE; public String range = ""; + public String irCode = ""; + public int irSendDelay = 300; + public int irCodeType = 0; } diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java index 2bb464b858..982097af45 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java @@ -16,12 +16,7 @@ import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -60,6 +55,7 @@ import org.smarthomej.binding.tuya.internal.local.UdpDiscoveryListener; import org.smarthomej.binding.tuya.internal.local.dto.DeviceInfo; import org.smarthomej.binding.tuya.internal.util.ConversionUtil; +import org.smarthomej.binding.tuya.internal.util.IrUtils; import org.smarthomej.binding.tuya.internal.util.SchemaDp; import org.smarthomej.commons.SimpleDynamicCommandDescriptionProvider; @@ -194,6 +190,17 @@ private void processChannelStatus(Integer dp, Object value) { } } + private String bytesToHex(byte[] bytes) { + final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = HEX_ARRAY[v >>> 4]; + hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; + } + return new String(hexChars); + } + @Override public void connectionStatus(boolean status) { if (status) { @@ -300,6 +307,24 @@ public void handleCommand(ChannelUID channelUID, Command command) { if (command instanceof OnOffType) { commandRequest.put(configuration.dp, OnOffType.ON.equals(command)); } + } else if (CHANNEL_TYPE_UID_IR_CODE_TEMPLATE.equals(channelTypeUID)) { + commandRequest.put(1, "send_ir"); + commandRequest.put(3, configuration.irCode); + commandRequest.put(4, command.toString()); + commandRequest.put(10, configuration.irSendDelay); + commandRequest.put(13, configuration.irCodeType); + } else if (CHANNEL_TYPE_UID_IR_CODE_DIY.equals(channelTypeUID)) { + if (command instanceof StringType) { + commandRequest.put(1, "study_key"); + commandRequest.put(7, command.toString()); + } + } else if (CHANNEL_TYPE_UID_IR_CODE_NEC.equals(channelTypeUID)) { + if (command instanceof StringType) { + long code = Long.parseLong(command.toString(), 16); + String base64Code = IrUtils.necToBase64(code, null); + commandRequest.put(1, "study_key"); + commandRequest.put(7, base64Code); + } } TuyaDevice tuyaDevice = this.tuyaDevice; diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/util/IrUtils.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/util/IrUtils.java new file mode 100644 index 0000000000..8df7579422 --- /dev/null +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/util/IrUtils.java @@ -0,0 +1,294 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.smarthomej.binding.tuya.internal.util; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link IrUtils} is a support class for decode/encode infra-red codes + *

+ * Based on https://github.com/jasonacox/tinytuya/blob/master/tinytuya/Contrib/IRRemoteControlDevice.py + * + * @author Dmitry P. (d51x) - Initial contribution + */ +public class IrUtils { + private static final Logger logger = LoggerFactory.getLogger(IrUtils.class); + + private IrUtils() { + } + + /** + * Convert Base64 code format from Tuya to nec-format. + * + * @param base64Code the base64 code format from Tuya + * @return the bec-format code + */ + public static String base64ToNec(String base64Code) { + ArrayList pulses = base64ToPulse(base64Code); + return pulsesToNec(pulses).get(0); + } + + private static ArrayList base64ToPulse(String base64Code) { + ArrayList pulses = new ArrayList<>(); + String key = (base64Code.length() % 4 == 1 && base64Code.startsWith("1")) ? base64Code.substring(1) + : base64Code; + byte[] raw_bytes = Base64.getDecoder().decode(key.getBytes(StandardCharsets.UTF_8)); + + int i = 0; + while (i < raw_bytes.length) { + int word = ((raw_bytes[i] & 0xFF) + (raw_bytes[i + 1] & 0xFF) * 256) & 0xFFFF; + pulses.add(word); + i += 2; + } + return pulses; + } + + private static ArrayList pulsesToWidthEncoded(ArrayList pulses, Integer start_mark, + Integer start_space, Integer pulse_threshold, Integer space_threshold) { + ArrayList ret = new ArrayList<>(); + if (pulses.size() < 68) { + // logger.warn("Length of pulses must be a multiple of 68! (2 start + 64 data + 2 trailing)"); + return null; + } + + if (pulse_threshold == null && space_threshold == null) { + // logger.error("pulse_threshold and/or space_threshold must be supplied!"); + return null; + } + + if (start_mark != null) { + while (pulses.size() >= 68 + && (pulses.get(0) < (start_mark * 0.75) || pulses.get(0) > (start_mark * 1.25))) { + pulses.remove(0); + } + + while (pulses.size() >= 68) { + if (pulses.get(0) < start_mark * 0.75 || pulses.get(0) > start_mark * 1.25) { + // logger.error("The start mark is not the correct length"); + return null; + } + + if (start_space != null + && (pulses.get(1) < (start_space * 0.75) || pulses.get(1) > (start_space * 1.25))) { + // logger.error("The start space is not the correct length"); + return null; + } + + // remove two first elements + pulses.remove(0); + pulses.remove(0); + + Integer res = 0; + long x = 0L; + + for (int i = 31; i >= 0; i--) { + Integer pulse_match = null; + Integer space_match = null; + + if (pulse_threshold != null) { + pulse_match = pulses.get(0) >= pulse_threshold ? 1 : 0; + } + if (space_threshold != null) { + space_match = pulses.get(1) >= space_threshold ? 1 : 0; + } + + if (pulse_match != null && space_match != null) { + if (!pulse_match.equals(space_match)) { + // logger.error("Both 'pulse_threshold' and 'space_threshold' are supplied and bit {} + // conflicts with both!", i); + return null; + } + res = space_match; + } else if (pulse_match == null) { + res = space_match; + } else { + res = pulse_match; + } + + if (res != null) { + x |= (long) (res) << i; + } + + // remove two first elements + pulses.remove(0); + pulses.remove(0); + } + + // remove two first elements + // pulses.remove(0); + // pulses.remove(0); + + if (!ret.contains(x)) { + ret.add(x); + } + } + } + + return ret; + } + + private static ArrayList widthEncodedToPulses(long data, PulseParams param) { + ArrayList pulses = new ArrayList<>(); + pulses.add(param.startMark); + pulses.add(param.startSpace); + + for (int i = 31; i >= 0; i--) { + if ((data & (1 << i)) > 0L) { + pulses.add(param.pulseOne); + pulses.add(param.spaceOne); + } else { + pulses.add(param.pulseZero); + pulses.add(param.spaceZero); + } + } + pulses.add(param.trailingPulse); + pulses.add(param.trailingSpace); + return pulses; + } + + private static long mirrorBits(long data, int bits) { + int shift = bits - 1; + long out = 0; + + for (int i = 0; i < bits; i++) { + if ((data & (1L << i)) > 0L) { + out |= 1L << shift; + } + shift -= 1; + } + return out & 0xFF; + } + + private static List pulsesToNec(ArrayList pulses) { + List ret = new ArrayList<>(); + ArrayList res = pulsesToWidthEncoded(pulses, 9000, null, null, 1125); + + for (Long code : res) { + long addr = mirrorBits((code >> 24) & 0xFF, 8); + long addr_not = mirrorBits((code >> 16) & 0xFF, 8); + long data = mirrorBits((code >> 8) & 0xFF, 8); + long data_not = mirrorBits(code & 0xFF, 8); + + if (addr != (addr_not ^ 0xFF)) { + addr = (addr << 8) | addr_not; + } + String d = String.format( + "{ \"type\": \"nec\", \"uint32\": %d, \"address\": None, \"data\": None, \"hex\": \"%08X\" }", code, + code); + if (data == (data_not ^ 0xFF)) { + d = String.format( + "{ \"type\": \"nec\", \"uint32\": %d, \"address\": %d, \"data\": %d, \"hex\": \"%08X\" }", code, + addr, data, code); + } + ret.add(d); + } + return ret; + } + + private static String bytesToHex(byte[] bytes) { + final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = HEX_ARRAY[v >>> 4]; + hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; + } + return new String(hexChars); + } + + private static ArrayList necToPulses(long address, Long data) { + Long newAddress, newData; + if (data == null) { + newAddress = address; + } else { + if (address < 256) { + newAddress = mirrorBits(address, 8); + newAddress = (newAddress << 8) | (newAddress ^ 0xFF); + } else { + newAddress = (mirrorBits((address >> 8) & 0xFF, 8) << 8) | mirrorBits(address & 0xFF, 8); + } + newData = mirrorBits(data, 8); + newData = (newData << 8) | (newData & 0xFF); + newAddress = (newAddress << 16) | newData; + } + + return widthEncodedToPulses(newAddress, new PulseParams()); + } + + private static String pulsesToBase64(ArrayList pulses) { + byte[] bytes = new byte[pulses.size() * 2]; + + final Integer[] i = { 0 }; + + pulses.forEach(p -> { + int val = p.shortValue(); + bytes[i[0]] = (byte) (val & 0xFF); + bytes[i[0] + 1] = (byte) ((val >> 8) & 0xFF); + i[0] = i[0] + 2; + }); + + return new String(Base64.getEncoder().encode(bytes)); + } + + /** + * Convert Nec-format code to base64-format code from Tuya + * + * @param code nec-format code + * @return the string + */ + public static String necToBase64(long code) { + ArrayList pulses = necToPulses(code, null); + return pulsesToBase64(pulses); + } + + private static class PulseParams { + /** + * The Start mark. + */ + public long startMark = 9000; + /** + * The Start space. + */ + public long startSpace = 4500; + /** + * The Pulse one. + */ + public long pulseOne = 563; + /** + * The Pulse zero. + */ + public long pulseZero = 563; + /** + * The Space one. + */ + public long spaceOne = 1688; + /** + * The Space zero. + */ + public long spaceZero = 563; + /** + * The Trailing pulse. + */ + public long trailingPulse = 563; + /** + * The Trailing space. + */ + public long trailingSpace = 30000; + } +} diff --git a/bundles/org.smarthomej.binding.tuya/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.smarthomej.binding.tuya/src/main/resources/OH-INF/thing/thing-types.xml index ad292a8872..fefe45cfed 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.smarthomej.binding.tuya/src/main/resources/OH-INF/thing/thing-types.xml @@ -58,7 +58,7 @@ - + A generic Tuya device. Can be extended with channels. @@ -185,4 +185,37 @@ + + String + + Code library parameter + + + + + Decoding parameter + + + + Send delay + + + + Code library label + + + + + + String + + Study key: Learning code parameter + + + + String + + Nec code in HEX + + From 84a9ac433c806c48f0695e1f7c31b6974a75c7a5 Mon Sep 17 00:00:00 2001 From: "Dmitry P. (d51x)" Date: Thu, 6 Jul 2023 08:03:11 +0300 Subject: [PATCH 02/15] [tuya] IR Controller: added support code in NEC-format for receiving command Signed-off-by: Dmitry P. (d51x) --- .../tuya/internal/TuyaBindingConstants.java | 4 +- .../internal/config/ChannelConfiguration.java | 1 + .../internal/handler/TuyaDeviceHandler.java | 73 ++++++++++++------- .../binding/tuya/internal/util/IrUtils.java | 61 +++++++++++++++- .../resources/OH-INF/thing/thing-types.xml | 48 ++++++------ 5 files changed, 134 insertions(+), 53 deletions(-) diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/TuyaBindingConstants.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/TuyaBindingConstants.java index 01d0e99144..1a6e54a21a 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/TuyaBindingConstants.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/TuyaBindingConstants.java @@ -56,9 +56,7 @@ public class TuyaBindingConstants { public static final ChannelTypeUID CHANNEL_TYPE_UID_NUMBER = new ChannelTypeUID(BINDING_ID, "number"); public static final ChannelTypeUID CHANNEL_TYPE_UID_STRING = new ChannelTypeUID(BINDING_ID, "string"); public static final ChannelTypeUID CHANNEL_TYPE_UID_SWITCH = new ChannelTypeUID(BINDING_ID, "switch"); - public static final ChannelTypeUID CHANNEL_TYPE_UID_IR_CODE_TEMPLATE = new ChannelTypeUID(BINDING_ID, "ircode1"); - public static final ChannelTypeUID CHANNEL_TYPE_UID_IR_CODE_DIY = new ChannelTypeUID(BINDING_ID, "ircode2"); - public static final ChannelTypeUID CHANNEL_TYPE_UID_IR_CODE_NEC = new ChannelTypeUID(BINDING_ID, "nec_code"); + public static final ChannelTypeUID CHANNEL_TYPE_UID_IR_CODE = new ChannelTypeUID(BINDING_ID, "ir-code"); public static final int TCP_CONNECTION_HEARTBEAT_INTERVAL = 10; // in s public static final int TCP_CONNECTION_TIMEOUT = 60; // in s; diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/config/ChannelConfiguration.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/config/ChannelConfiguration.java index f59d16f1d2..76fefcb78e 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/config/ChannelConfiguration.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/config/ChannelConfiguration.java @@ -29,4 +29,5 @@ public class ChannelConfiguration { public String irCode = ""; public int irSendDelay = 300; public int irCodeType = 0; + public String irType = ""; } diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java index 982097af45..31968227fe 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java @@ -170,6 +170,22 @@ private void processChannelStatus(Integer dp, Object value) { && CHANNEL_TYPE_UID_SWITCH.equals(channelTypeUID)) { updateState(channelId, OnOffType.from((boolean) value)); return; + } else if (value instanceof String && CHANNEL_TYPE_UID_IR_CODE.equals(channelTypeUID)) { + String decoded = null; + if (configuration.irType.equals("nec")) { + decoded = IrUtils.base64ToNec((String) value); + } else if (configuration.irType.equals("samsung")) { + decoded = IrUtils.base64ToSamsung((String) value); + } else { + if (((String) value).length() > 68) { + decoded = IrUtils.base64ToNec((String) value); + } else { + decoded = (String) value; + } + } + logger.error("ir code: {}", decoded); + updateState(channelId, new StringType(decoded)); + return; } logger.warn("Could not update channel '{}' of thing '{}' with value '{}'. Datatype incompatible.", channelId, getThing().getUID(), value); @@ -190,17 +206,6 @@ private void processChannelStatus(Integer dp, Object value) { } } - private String bytesToHex(byte[] bytes) { - final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); - char[] hexChars = new char[bytes.length * 2]; - for (int j = 0; j < bytes.length; j++) { - int v = bytes[j] & 0xFF; - hexChars[j * 2] = HEX_ARRAY[v >>> 4]; - hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; - } - return new String(hexChars); - } - @Override public void connectionStatus(boolean status) { if (status) { @@ -307,23 +312,32 @@ public void handleCommand(ChannelUID channelUID, Command command) { if (command instanceof OnOffType) { commandRequest.put(configuration.dp, OnOffType.ON.equals(command)); } - } else if (CHANNEL_TYPE_UID_IR_CODE_TEMPLATE.equals(channelTypeUID)) { - commandRequest.put(1, "send_ir"); - commandRequest.put(3, configuration.irCode); - commandRequest.put(4, command.toString()); - commandRequest.put(10, configuration.irSendDelay); - commandRequest.put(13, configuration.irCodeType); - } else if (CHANNEL_TYPE_UID_IR_CODE_DIY.equals(channelTypeUID)) { + } else if (CHANNEL_TYPE_UID_IR_CODE.equals(channelTypeUID)) { if (command instanceof StringType) { - commandRequest.put(1, "study_key"); - commandRequest.put(7, command.toString()); - } - } else if (CHANNEL_TYPE_UID_IR_CODE_NEC.equals(channelTypeUID)) { - if (command instanceof StringType) { - long code = Long.parseLong(command.toString(), 16); - String base64Code = IrUtils.necToBase64(code, null); - commandRequest.put(1, "study_key"); - commandRequest.put(7, base64Code); + if (configuration.irType.equals("base64")) { + commandRequest.put(1, "study_key"); + commandRequest.put(7, command.toString()); + } else if (configuration.irType.equals("tuya-head")) { + if (configuration.irCode != null && !configuration.irCode.isEmpty()) { + commandRequest.put(1, "send_ir"); + commandRequest.put(3, configuration.irCode); + commandRequest.put(4, command.toString()); + commandRequest.put(10, configuration.irSendDelay); + commandRequest.put(13, configuration.irCodeType); + } else { + logger.error("irCode is not set"); + } + } else if (configuration.irType.equals("nec")) { + long code = convertHexCode(command.toString()); + String base64Code = IrUtils.necToBase64(code); + commandRequest.put(1, "study_key"); + commandRequest.put(7, base64Code); + } else if (configuration.irType.equals("samsung")) { + long code = convertHexCode(command.toString()); + String base64Code = IrUtils.samsungToBase64(code); + commandRequest.put(1, "study_key"); + commandRequest.put(7, base64Code); + } } } @@ -530,4 +544,9 @@ protected void updateState(String channelId, State state) { channelStateCache.put(channelId, state); super.updateState(channelId, state); } + + private long convertHexCode(String code) { + String sCode = code.startsWith("0x") ? code.substring(2) : code; + return Long.parseLong(sCode, 16); + } } diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/util/IrUtils.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/util/IrUtils.java index 8df7579422..b25ab6a927 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/util/IrUtils.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/util/IrUtils.java @@ -37,13 +37,24 @@ private IrUtils() { * Convert Base64 code format from Tuya to nec-format. * * @param base64Code the base64 code format from Tuya - * @return the bec-format code + * @return the nec-format code */ public static String base64ToNec(String base64Code) { ArrayList pulses = base64ToPulse(base64Code); return pulsesToNec(pulses).get(0); } + /** + * Convert Base64 code format from Tuya to samsung-format. + * + * @param base64Code the base64 code format from Tuya + * @return the samsung-format code + */ + public static String base64ToSamsung(String base64Code) { + ArrayList pulses = base64ToPulse(base64Code); + return pulsesToSamsung(pulses).get(0); + } + private static ArrayList base64ToPulse(String base64Code) { ArrayList pulses = new ArrayList<>(); String key = (base64Code.length() % 4 == 1 && base64Code.startsWith("1")) ? base64Code.substring(1) @@ -257,6 +268,54 @@ public static String necToBase64(long code) { return pulsesToBase64(pulses); } + /** + * Convert Samsung-format code to base64-format code from Tuya + * + * @param code samsung-format code + * @return the string + */ + public static String samsungToBase64(long code) { + ArrayList pulses = samsungToPulses(code, null); + return pulsesToBase64(pulses); + } + + private static ArrayList samsungToPulses(long address, Long data) { + Long newAddress, newData; + if (data == null) { + newAddress = address; + } else { + newAddress = mirrorBits(address, 8); + newData = mirrorBits(data, 8); + newData = (newData << 8) | (newData & 0xFF); + newAddress = (newAddress << 24) + (newAddress << 16) + (newData << 8) + (newData ^ 0xFF); + } + return widthEncodedToPulses(newAddress, new PulseParams()); + } + + private static List pulsesToSamsung(ArrayList pulses) { + List ret = new ArrayList<>(); + ArrayList res = pulsesToWidthEncoded(pulses, 4500, null, null, 1125); + for (Long code : res) { + long addr = (code >> 24) & 0xFF; + long addr_not = (code >> 16) & 0xFF; + long data = (code >> 8) & 0xFF; + long data_not = code & 0xFF; + + String d = String.format( + "{ \"type\": \"samsung\", \"uint32\": %d, \"address\": None, \"data\": None, \"hex\": \"%08X\" }", + code, code); + if (addr == addr_not && data == (data_not ^ 0xFF)) { + addr = mirrorBits(addr, 8); + data = mirrorBits(data, 8); + d = String.format( + "{ \"type\": \"samsung\", \"uint32\": %d, \"address\": %d, \"data\": %d, \"hex\": \"%08X\" }", + code, addr, data, code); + } + ret.add(d); + } + return ret; + } + private static class PulseParams { /** * The Start mark. diff --git a/bundles/org.smarthomej.binding.tuya/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.smarthomej.binding.tuya/src/main/resources/OH-INF/thing/thing-types.xml index fefe45cfed..7d09624800 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.smarthomej.binding.tuya/src/main/resources/OH-INF/thing/thing-types.xml @@ -58,7 +58,7 @@ - + A generic Tuya device. Can be extended with channels. @@ -185,37 +185,41 @@ - + String - - Code library parameter + + Supperted codes: tuya basae64 codes diy mode, nec-format codes, samsung-format codes - + + + + + + + + + true + + + - Decoding parameter + Only for Tuya Codes Library: Decoding parameter + true - + - Send delay + Only for Tuya Codes Library: Send delay + 300 + true - + - Code library label + Only for Tuya Codes Library: Code library label + 0 + true - - String - - Study key: Learning code parameter - - - - String - - Nec code in HEX - - From 0c3d2d147b834261a79c5592ab5b2eb6db79c35e Mon Sep 17 00:00:00 2001 From: "Dmitry P. (d51x)" Date: Thu, 6 Jul 2023 08:03:11 +0300 Subject: [PATCH 03/15] [tuya] IR Controller: refactor code, add one ir-code channel with options, remove other ir-channels Signed-off-by: Dmitry P. (d51x) --- bundles/org.smarthomej.binding.tuya/README.md | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/bundles/org.smarthomej.binding.tuya/README.md b/bundles/org.smarthomej.binding.tuya/README.md index a2583806bd..6184acfffb 100644 --- a/bundles/org.smarthomej.binding.tuya/README.md +++ b/bundles/org.smarthomej.binding.tuya/README.md @@ -117,31 +117,28 @@ The `min` and `max` parameters define the range allowed (e.g. 0-86400 for turn-o The `string` channel has one additional (optional) parameter `range`. It contains a comma-separated list of command options for this channel (e.g. `white,colour,scene,music` for the "workMode" channel). -### Type `ircode1` +### Type `ir-code` -IR Code (Template) - use codes from templates library. +IR code types: ++ `Tuya DIY-mode` - use study codes from real remotes. + + Make a virtual remote control in DIY, learn virtual buttons. -Make a virtual remote control from pre-defined type of devices. ++ `Tuya Codes Library (check Advanced options)` - use codes from templates library. + + Make a virtual remote control from pre-defined type of devices. -The `ircode1` channel has three additional (mandatory) parameters: + Select Advanced checkbox to configure other parameters: + + `irCode` - Decoding parameter + + `irSendDelay` - used as `Send delay` parameter + + `irCodeType` - used as `type library` parameter -* `irCode` - Decoding parameter -* `irSendDelay` - used as `Send delay` parameter -* `irCodeType` - used as `type library` parameter ++ `NEC` - IR Code in NEC format ++ `Samsung` - IR Code in Samsung format. If linked item received a command with `Key Code` (Code Library Parameter) then device sends appropriate key code. -### Type `ircode2` - -IR Code (DIY Mode) - use study codes from real remotes. Make a virtual remote control in DIY, learn virtual buttons. - -The `ircode2` channel has no additional parameters. - -If linked item received a command with `Key Code` (Learning Code Parameter) then device sends appropriate key code. - -### Type `nec_code` - -IR Code in NEC format. +#### How to use IR Code in NEC format. Example, from Tasmota you need to use **_Data_** parameter, it can be with or without **_0x_** ```json From eeb2cb09ff0f60eda65eec654e08fca08bfc7b3f Mon Sep 17 00:00:00 2001 From: "Dmitry P. (d51x)" Date: Thu, 6 Jul 2023 09:58:57 +0300 Subject: [PATCH 04/15] [tuya] IR Controller: refactor code, add one ir-code channel with options, remove other ir-channels Signed-off-by: Dmitry P. (d51x) --- .../internal/handler/TuyaDeviceHandler.java | 68 +++++++++++++++---- .../tuya/internal/local/dto/IRCode.java | 28 ++++++++ .../resources/OH-INF/thing/thing-types.xml | 5 ++ 3 files changed, 88 insertions(+), 13 deletions(-) create mode 100644 bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/local/dto/IRCode.java diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java index 31968227fe..e50ffe7707 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java @@ -54,12 +54,14 @@ import org.smarthomej.binding.tuya.internal.local.TuyaDevice; import org.smarthomej.binding.tuya.internal.local.UdpDiscoveryListener; import org.smarthomej.binding.tuya.internal.local.dto.DeviceInfo; +import org.smarthomej.binding.tuya.internal.local.dto.IRCode; import org.smarthomej.binding.tuya.internal.util.ConversionUtil; import org.smarthomej.binding.tuya.internal.util.IrUtils; import org.smarthomej.binding.tuya.internal.util.SchemaDp; import org.smarthomej.commons.SimpleDynamicCommandDescriptionProvider; import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; import io.netty.channel.EventLoopGroup; @@ -171,20 +173,12 @@ private void processChannelStatus(Integer dp, Object value) { updateState(channelId, OnOffType.from((boolean) value)); return; } else if (value instanceof String && CHANNEL_TYPE_UID_IR_CODE.equals(channelTypeUID)) { - String decoded = null; - if (configuration.irType.equals("nec")) { - decoded = IrUtils.base64ToNec((String) value); - } else if (configuration.irType.equals("samsung")) { - decoded = IrUtils.base64ToSamsung((String) value); - } else { - if (((String) value).length() > 68) { - decoded = IrUtils.base64ToNec((String) value); - } else { - decoded = (String) value; - } + if (configuration.dp == 2) { + String decoded = convertBase64Code(configuration, (String) value); + logger.warn("ir code: {}", decoded); + updateState(channelId, new StringType(decoded)); + repeatStudyCode(); } - logger.error("ir code: {}", decoded); - updateState(channelId, new StringType(decoded)); return; } logger.warn("Could not update channel '{}' of thing '{}' with value '{}'. Datatype incompatible.", @@ -549,4 +543,52 @@ private long convertHexCode(String code) { String sCode = code.startsWith("0x") ? code.substring(2) : code; return Long.parseLong(sCode, 16); } + + private String convertBase64Code(ChannelConfiguration channelConfig, String encoded) { + String decoded; + try { + if (channelConfig.irType.equals("nec")) { + decoded = IrUtils.base64ToNec(encoded); + IRCode code = Objects.requireNonNull(gson.fromJson(decoded, IRCode.class)); + decoded = "0x" + code.hex; + } else if (channelConfig.irType.equals("samsung")) { + decoded = IrUtils.base64ToSamsung(encoded); + IRCode code = Objects.requireNonNull(gson.fromJson(decoded, IRCode.class)); + decoded = "0x" + code.hex; + } else { + if (encoded.length() > 68) { + decoded = IrUtils.base64ToNec(encoded); + if (decoded == null || decoded.isEmpty()) { + decoded = IrUtils.base64ToSamsung(encoded); + } + IRCode code = Objects.requireNonNull(gson.fromJson(decoded, IRCode.class)); + decoded = code.type + ": 0x" + code.hex; + } else { + decoded = encoded; + } + } + } catch (JsonSyntaxException e) { + logger.error("Incorrect json response: {}", e.getMessage()); + decoded = encoded; + } + return decoded; + } + + private void finishStudyCode() { + Map commandRequest = new HashMap<>(); + commandRequest.put(1, "study_exit"); + TuyaDevice tuyaDevice = this.tuyaDevice; + if (!commandRequest.isEmpty() && tuyaDevice != null) { + tuyaDevice.set(commandRequest); + } + } + + private void repeatStudyCode() { + Map commandRequest = new HashMap<>(); + commandRequest.put(1, "study"); + TuyaDevice tuyaDevice = this.tuyaDevice; + if (!commandRequest.isEmpty() && tuyaDevice != null) { + tuyaDevice.set(commandRequest); + } + } } diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/local/dto/IRCode.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/local/dto/IRCode.java new file mode 100644 index 0000000000..f084c49330 --- /dev/null +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/local/dto/IRCode.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2021-2023 Contributors to the SmartHome/J project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.smarthomej.binding.tuya.internal.local.dto; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link IRCode} represents the IR code decoded messages sent by Tuya devices + * + * @author DmitryP. (d51x) - Initial contribution + */ +@NonNullByDefault +public class IRCode { + public String type = ""; + public String hex = ""; + public Integer address = 0; + public Integer data = 0; +} diff --git a/bundles/org.smarthomej.binding.tuya/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.smarthomej.binding.tuya/src/main/resources/OH-INF/thing/thing-types.xml index 7d09624800..1797b5641d 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.smarthomej.binding.tuya/src/main/resources/OH-INF/thing/thing-types.xml @@ -219,6 +219,11 @@ 0 true + + + DP number for study key. Uses for receive key code in learning mode + 2 + From 81419c0d6b484519316ba8dd269928887f57c6b6 Mon Sep 17 00:00:00 2001 From: "Dmitry P. (d51x)" Date: Thu, 6 Jul 2023 10:16:44 +0300 Subject: [PATCH 05/15] [tuya] IR Controller: refactor code, add one ir-code channel with options, remove other ir-channels Signed-off-by: Dmitry P. (d51x) --- bundles/org.smarthomej.binding.tuya/README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/bundles/org.smarthomej.binding.tuya/README.md b/bundles/org.smarthomej.binding.tuya/README.md index 6184acfffb..b726f9a4cb 100644 --- a/bundles/org.smarthomej.binding.tuya/README.md +++ b/bundles/org.smarthomej.binding.tuya/README.md @@ -150,6 +150,25 @@ Another example, use **_hex_** parameter { "type": "nec", "uint32": 284151855, "address": 8, "data": 11, "hex": "10EFD02F" } ``` +#### How to get key codes without Tasmota and other + +Channel can receive learning key (autodetect format and put autodetected code in channel). + +To start learning codes add new channel with Type String and DP = 1 and Range with `send_ir,study,study_exit,study_key`. + +Link Item to this added channel and send command `study`. + +Device will be in learning mode and be able to receive codes from remote control. + +Just press a button on the remote control and see key code in channel `ir-code`. + +If type of channel `ir-code` is **_NEC_** or **_Samsung_** you will see just a hex code. + +If type of channel `ir-code` is **_Tuya DIY-mode_** you will see a type of code format and a hex code. + +Pressing buttons and copying codes, then assign codes with Item which control device (adjust State Description and Command Options you want). + +After receiving the key code, the learning mode automatically continues until you send command `study_exit` or send key code by Item with code ## Troubleshooting - If the `project` thing is not coming `ONLINE` check if you see your devices in the cloud-account on `iot.tuya.com`. From 4711f63c8161fe2eccd4d636d19d6cdb4a11a29d Mon Sep 17 00:00:00 2001 From: "Dmitry P. (d51x)" Date: Thu, 6 Jul 2023 10:16:44 +0300 Subject: [PATCH 06/15] [tuya] IR Controller: refactor code, add one ir-code channel with options, remove other ir-channels, added continuous learning mode Signed-off-by: Dmitry P. (d51x) --- bundles/org.smarthomej.binding.tuya/README.md | 5 +++++ .../tuya/internal/config/ChannelConfiguration.java | 2 ++ .../binding/tuya/internal/handler/TuyaDeviceHandler.java | 9 +++++++++ .../src/main/resources/OH-INF/thing/thing-types.xml | 7 ++++++- 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/bundles/org.smarthomej.binding.tuya/README.md b/bundles/org.smarthomej.binding.tuya/README.md index b726f9a4cb..2c8262be83 100644 --- a/bundles/org.smarthomej.binding.tuya/README.md +++ b/bundles/org.smarthomej.binding.tuya/README.md @@ -136,6 +136,11 @@ IR code types: + `NEC` - IR Code in NEC format + `Samsung` - IR Code in Samsung format. +**Additional options:** +* `Active Listening` - Device will be always in learning mode. After send command with key code device stays in the learning mode +* `DP Study Key` - **Advanced**. DP number for study key. Uses for receive key code in learning mode. Change it own your risk. + + If linked item received a command with `Key Code` (Code Library Parameter) then device sends appropriate key code. #### How to use IR Code in NEC format. diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/config/ChannelConfiguration.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/config/ChannelConfiguration.java index 76fefcb78e..3e264f0367 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/config/ChannelConfiguration.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/config/ChannelConfiguration.java @@ -30,4 +30,6 @@ public class ChannelConfiguration { public int irSendDelay = 300; public int irCodeType = 0; public String irType = ""; + + public Boolean activeListen = Boolean.FALSE; } diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java index e50ffe7707..b53b696b96 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java @@ -339,6 +339,14 @@ public void handleCommand(ChannelUID channelUID, Command command) { if (!commandRequest.isEmpty() && tuyaDevice != null) { tuyaDevice.set(commandRequest); } + + if (CHANNEL_TYPE_UID_IR_CODE.equals(channelTypeUID)) { + if (command instanceof StringType) { + if (Boolean.TRUE.equals(configuration.activeListen)) { + repeatStudyCode(); + } + } + } } @Override @@ -585,6 +593,7 @@ private void finishStudyCode() { private void repeatStudyCode() { Map commandRequest = new HashMap<>(); + commandRequest.clear(); commandRequest.put(1, "study"); TuyaDevice tuyaDevice = this.tuyaDevice; if (!commandRequest.isEmpty() && tuyaDevice != null) { diff --git a/bundles/org.smarthomej.binding.tuya/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.smarthomej.binding.tuya/src/main/resources/OH-INF/thing/thing-types.xml index 1797b5641d..72acbf6796 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.smarthomej.binding.tuya/src/main/resources/OH-INF/thing/thing-types.xml @@ -201,7 +201,11 @@ true - + + + Device will be always in learning mode. After send command with key code device stays in the learning + mode + Only for Tuya Codes Library: Decoding parameter @@ -223,6 +227,7 @@ DP number for study key. Uses for receive key code in learning mode 2 + true From ff4b37018359062f47f9576d7358c41d3e5a5fc6 Mon Sep 17 00:00:00 2001 From: "Dmitry P. (d51x)" Date: Thu, 6 Jul 2023 10:16:44 +0300 Subject: [PATCH 07/15] [tuya] IR Controller: update docs Signed-off-by: Dmitry P. (d51x) --- .../binding/tuya/internal/config/ChannelConfiguration.java | 1 - 1 file changed, 1 deletion(-) diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/config/ChannelConfiguration.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/config/ChannelConfiguration.java index 3e264f0367..092d723211 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/config/ChannelConfiguration.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/config/ChannelConfiguration.java @@ -30,6 +30,5 @@ public class ChannelConfiguration { public int irSendDelay = 300; public int irCodeType = 0; public String irType = ""; - public Boolean activeListen = Boolean.FALSE; } From 781e1b567b530403f11c445e0988c5608fb4f6b0 Mon Sep 17 00:00:00 2001 From: "Dmitry P. (d51x)" Date: Mon, 10 Jul 2023 17:28:22 +0300 Subject: [PATCH 08/15] [tuya] IR Controller: fix code style Signed-off-by: Dmitry P. (d51x) --- bundles/org.smarthomej.binding.tuya/README.md | 6 +- .../internal/handler/TuyaDeviceHandler.java | 87 +++++++------------ .../local/dto/{IRCode.java => IrCode.java} | 8 +- .../binding/tuya/internal/util/IrUtils.java | 29 ++----- .../resources/OH-INF/thing/thing-types.xml | 2 +- 5 files changed, 47 insertions(+), 85 deletions(-) rename bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/local/dto/{IRCode.java => IrCode.java} (76%) diff --git a/bundles/org.smarthomej.binding.tuya/README.md b/bundles/org.smarthomej.binding.tuya/README.md index 2c8262be83..c2adb1a6c5 100644 --- a/bundles/org.smarthomej.binding.tuya/README.md +++ b/bundles/org.smarthomej.binding.tuya/README.md @@ -137,8 +137,10 @@ IR code types: + `Samsung` - IR Code in Samsung format. **Additional options:** -* `Active Listening` - Device will be always in learning mode. After send command with key code device stays in the learning mode -* `DP Study Key` - **Advanced**. DP number for study key. Uses for receive key code in learning mode. Change it own your risk. +* `Active Listening` - Device will be always in learning mode. + After send command with key code device stays in the learning mode +* `DP Study Key` - **Advanced**. DP number for study key. Uses for receive key code in learning mode. Change it own your + risk. If linked item received a command with `Key Code` (Code Library Parameter) then device sends appropriate key code. diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java index b53b696b96..ecabe6607f 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java @@ -12,58 +12,32 @@ */ package org.smarthomej.binding.tuya.internal.handler; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.*; - -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.*; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.cache.ExpiringCache; -import org.openhab.core.cache.ExpiringCacheMap; -import org.openhab.core.config.core.Configuration; -import org.openhab.core.library.types.DecimalType; -import org.openhab.core.library.types.HSBType; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.library.types.PercentType; -import org.openhab.core.library.types.StringType; +import com.google.gson.*; +import io.netty.channel.*; +import org.eclipse.jdt.annotation.*; +import org.openhab.core.cache.*; +import org.openhab.core.config.core.*; +import org.openhab.core.library.types.*; import org.openhab.core.thing.Channel; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingStatus; -import org.openhab.core.thing.ThingStatusDetail; -import org.openhab.core.thing.ThingUID; -import org.openhab.core.thing.binding.BaseThingHandler; -import org.openhab.core.thing.binding.ThingHandlerCallback; -import org.openhab.core.thing.binding.builder.ChannelBuilder; -import org.openhab.core.thing.binding.builder.ThingBuilder; -import org.openhab.core.thing.type.ChannelTypeUID; -import org.openhab.core.types.Command; -import org.openhab.core.types.CommandOption; -import org.openhab.core.types.State; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.smarthomej.binding.tuya.internal.config.ChannelConfiguration; -import org.smarthomej.binding.tuya.internal.config.DeviceConfiguration; -import org.smarthomej.binding.tuya.internal.local.DeviceInfoSubscriber; -import org.smarthomej.binding.tuya.internal.local.DeviceStatusListener; -import org.smarthomej.binding.tuya.internal.local.TuyaDevice; -import org.smarthomej.binding.tuya.internal.local.UdpDiscoveryListener; -import org.smarthomej.binding.tuya.internal.local.dto.DeviceInfo; -import org.smarthomej.binding.tuya.internal.local.dto.IRCode; -import org.smarthomej.binding.tuya.internal.util.ConversionUtil; -import org.smarthomej.binding.tuya.internal.util.IrUtils; -import org.smarthomej.binding.tuya.internal.util.SchemaDp; -import org.smarthomej.commons.SimpleDynamicCommandDescriptionProvider; - -import com.google.gson.Gson; -import com.google.gson.JsonSyntaxException; - -import io.netty.channel.EventLoopGroup; +import org.openhab.core.thing.*; +import org.openhab.core.thing.binding.*; +import org.openhab.core.thing.binding.builder.*; +import org.openhab.core.thing.type.*; +import org.openhab.core.types.*; +import org.slf4j.*; +import org.smarthomej.binding.tuya.internal.config.*; +import org.smarthomej.binding.tuya.internal.local.*; +import org.smarthomej.binding.tuya.internal.local.dto.*; +import org.smarthomej.binding.tuya.internal.util.*; +import org.smarthomej.commons.*; + +import java.nio.charset.*; +import java.time.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.stream.*; + +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.*; /** * The {@link TuyaDeviceHandler} handles commands and state updates @@ -175,7 +149,7 @@ private void processChannelStatus(Integer dp, Object value) { } else if (value instanceof String && CHANNEL_TYPE_UID_IR_CODE.equals(channelTypeUID)) { if (configuration.dp == 2) { String decoded = convertBase64Code(configuration, (String) value); - logger.warn("ir code: {}", decoded); + logger.info("thing {} received ir code: {}", thing.getUID(), decoded); updateState(channelId, new StringType(decoded)); repeatStudyCode(); } @@ -319,7 +293,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { commandRequest.put(10, configuration.irSendDelay); commandRequest.put(13, configuration.irCodeType); } else { - logger.error("irCode is not set"); + logger.warn("irCode is not set for channel {}", channelUID); } } else if (configuration.irType.equals("nec")) { long code = convertHexCode(command.toString()); @@ -557,11 +531,11 @@ private String convertBase64Code(ChannelConfiguration channelConfig, String enco try { if (channelConfig.irType.equals("nec")) { decoded = IrUtils.base64ToNec(encoded); - IRCode code = Objects.requireNonNull(gson.fromJson(decoded, IRCode.class)); + IrCode code = Objects.requireNonNull(gson.fromJson(decoded, IrCode.class)); decoded = "0x" + code.hex; } else if (channelConfig.irType.equals("samsung")) { decoded = IrUtils.base64ToSamsung(encoded); - IRCode code = Objects.requireNonNull(gson.fromJson(decoded, IRCode.class)); + IrCode code = Objects.requireNonNull(gson.fromJson(decoded, IrCode.class)); decoded = "0x" + code.hex; } else { if (encoded.length() > 68) { @@ -569,7 +543,7 @@ private String convertBase64Code(ChannelConfiguration channelConfig, String enco if (decoded == null || decoded.isEmpty()) { decoded = IrUtils.base64ToSamsung(encoded); } - IRCode code = Objects.requireNonNull(gson.fromJson(decoded, IRCode.class)); + IrCode code = Objects.requireNonNull(gson.fromJson(decoded, IrCode.class)); decoded = code.type + ": 0x" + code.hex; } else { decoded = encoded; @@ -593,7 +567,6 @@ private void finishStudyCode() { private void repeatStudyCode() { Map commandRequest = new HashMap<>(); - commandRequest.clear(); commandRequest.put(1, "study"); TuyaDevice tuyaDevice = this.tuyaDevice; if (!commandRequest.isEmpty() && tuyaDevice != null) { diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/local/dto/IRCode.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/local/dto/IrCode.java similarity index 76% rename from bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/local/dto/IRCode.java rename to bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/local/dto/IrCode.java index f084c49330..83ced8e9ac 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/local/dto/IRCode.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/local/dto/IrCode.java @@ -12,15 +12,15 @@ */ package org.smarthomej.binding.tuya.internal.local.dto; -import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.*; /** - * The {@link IRCode} represents the IR code decoded messages sent by Tuya devices + * The {@link IrCode} represents the IR code decoded messages sent by Tuya devices * - * @author DmitryP. (d51x) - Initial contribution + * @author Dmitry Pyatykh - Initial contribution */ @NonNullByDefault -public class IRCode { +public class IrCode { public String type = ""; public String hex = ""; public Integer address = 0; diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/util/IrUtils.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/util/IrUtils.java index b25ab6a927..fe49b31bd3 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/util/IrUtils.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/util/IrUtils.java @@ -12,20 +12,17 @@ */ package org.smarthomej.binding.tuya.internal.util; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Base64; -import java.util.List; +import org.slf4j.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.nio.charset.*; +import java.util.*; /** * The {@link IrUtils} is a support class for decode/encode infra-red codes *

* Based on https://github.com/jasonacox/tinytuya/blob/master/tinytuya/Contrib/IRRemoteControlDevice.py * - * @author Dmitry P. (d51x) - Initial contribution + * @author Dmitry Pyatykh - Initial contribution */ public class IrUtils { private static final Logger logger = LoggerFactory.getLogger(IrUtils.class); @@ -70,16 +67,14 @@ private static ArrayList base64ToPulse(String base64Code) { return pulses; } - private static ArrayList pulsesToWidthEncoded(ArrayList pulses, Integer start_mark, - Integer start_space, Integer pulse_threshold, Integer space_threshold) { - ArrayList ret = new ArrayList<>(); + private static List pulsesToWidthEncoded(List pulses, Integer startMark, + Integer start_space, Integer pulse_threshold, Integer space_threshold) { + List ret = new ArrayList<>(); if (pulses.size() < 68) { - // logger.warn("Length of pulses must be a multiple of 68! (2 start + 64 data + 2 trailing)"); return null; } if (pulse_threshold == null && space_threshold == null) { - // logger.error("pulse_threshold and/or space_threshold must be supplied!"); return null; } @@ -91,13 +86,11 @@ private static ArrayList pulsesToWidthEncoded(ArrayList pulses, I while (pulses.size() >= 68) { if (pulses.get(0) < start_mark * 0.75 || pulses.get(0) > start_mark * 1.25) { - // logger.error("The start mark is not the correct length"); return null; } if (start_space != null && (pulses.get(1) < (start_space * 0.75) || pulses.get(1) > (start_space * 1.25))) { - // logger.error("The start space is not the correct length"); return null; } @@ -121,8 +114,6 @@ private static ArrayList pulsesToWidthEncoded(ArrayList pulses, I if (pulse_match != null && space_match != null) { if (!pulse_match.equals(space_match)) { - // logger.error("Both 'pulse_threshold' and 'space_threshold' are supplied and bit {} - // conflicts with both!", i); return null; } res = space_match; @@ -141,10 +132,6 @@ private static ArrayList pulsesToWidthEncoded(ArrayList pulses, I pulses.remove(0); } - // remove two first elements - // pulses.remove(0); - // pulses.remove(0); - if (!ret.contains(x)) { ret.add(x); } @@ -188,7 +175,7 @@ private static long mirrorBits(long data, int bits) { private static List pulsesToNec(ArrayList pulses) { List ret = new ArrayList<>(); - ArrayList res = pulsesToWidthEncoded(pulses, 9000, null, null, 1125); + List res = pulsesToWidthEncoded(pulses, 9000, null, null, 1125); for (Long code : res) { long addr = mirrorBits((code >> 24) & 0xFF, 8); diff --git a/bundles/org.smarthomej.binding.tuya/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.smarthomej.binding.tuya/src/main/resources/OH-INF/thing/thing-types.xml index 72acbf6796..d295cb733b 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.smarthomej.binding.tuya/src/main/resources/OH-INF/thing/thing-types.xml @@ -188,7 +188,7 @@ String - Supperted codes: tuya basae64 codes diy mode, nec-format codes, samsung-format codes + Supported codes: tuya base64 codes diy mode, nec-format codes, samsung-format codes From 40aa177323c69887e45a8c74807abb2991ee3b66 Mon Sep 17 00:00:00 2001 From: "Dmitry P. (d51x)" Date: Mon, 10 Jul 2023 17:46:00 +0300 Subject: [PATCH 09/15] [tuya] IR Controller: fix code style Signed-off-by: Dmitry P. (d51x) --- .../internal/handler/TuyaDeviceHandler.java | 85 +++++++++++++------ .../binding/tuya/internal/util/IrUtils.java | 85 ++++++++++--------- 2 files changed, 104 insertions(+), 66 deletions(-) diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java index ecabe6607f..5bde4702ee 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java @@ -12,32 +12,67 @@ */ package org.smarthomej.binding.tuya.internal.handler; -import com.google.gson.*; -import io.netty.channel.*; -import org.eclipse.jdt.annotation.*; -import org.openhab.core.cache.*; -import org.openhab.core.config.core.*; -import org.openhab.core.library.types.*; +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import io.netty.channel.EventLoopGroup; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.cache.ExpiringCache; +import org.openhab.core.cache.ExpiringCacheMap; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.HSBType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.library.types.StringType; import org.openhab.core.thing.Channel; -import org.openhab.core.thing.*; -import org.openhab.core.thing.binding.*; -import org.openhab.core.thing.binding.builder.*; -import org.openhab.core.thing.type.*; -import org.openhab.core.types.*; -import org.slf4j.*; -import org.smarthomej.binding.tuya.internal.config.*; -import org.smarthomej.binding.tuya.internal.local.*; -import org.smarthomej.binding.tuya.internal.local.dto.*; -import org.smarthomej.binding.tuya.internal.util.*; -import org.smarthomej.commons.*; - -import java.nio.charset.*; -import java.time.*; -import java.util.*; -import java.util.concurrent.*; -import java.util.stream.*; - -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.*; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.thing.binding.builder.ChannelBuilder; +import org.openhab.core.thing.binding.builder.ThingBuilder; +import org.openhab.core.thing.type.ChannelTypeUID; +import org.openhab.core.types.Command; +import org.openhab.core.types.CommandOption; +import org.openhab.core.types.State; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.smarthomej.binding.tuya.internal.config.ChannelConfiguration; +import org.smarthomej.binding.tuya.internal.config.DeviceConfiguration; +import org.smarthomej.binding.tuya.internal.local.DeviceInfoSubscriber; +import org.smarthomej.binding.tuya.internal.local.DeviceStatusListener; +import org.smarthomej.binding.tuya.internal.local.TuyaDevice; +import org.smarthomej.binding.tuya.internal.local.UdpDiscoveryListener; +import org.smarthomej.binding.tuya.internal.local.dto.DeviceInfo; +import org.smarthomej.binding.tuya.internal.local.dto.IrCode; +import org.smarthomej.binding.tuya.internal.util.ConversionUtil; +import org.smarthomej.binding.tuya.internal.util.IrUtils; +import org.smarthomej.binding.tuya.internal.util.SchemaDp; +import org.smarthomej.commons.SimpleDynamicCommandDescriptionProvider; + +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_COLOR; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_DIMMER; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_IR_CODE; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_NUMBER; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_STRING; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_SWITCH; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.SCHEMAS; /** * The {@link TuyaDeviceHandler} handles commands and state updates diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/util/IrUtils.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/util/IrUtils.java index fe49b31bd3..7f69ea9acd 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/util/IrUtils.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/util/IrUtils.java @@ -12,10 +12,13 @@ */ package org.smarthomej.binding.tuya.internal.util; -import org.slf4j.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import java.nio.charset.*; -import java.util.*; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; /** * The {@link IrUtils} is a support class for decode/encode infra-red codes @@ -67,30 +70,30 @@ private static ArrayList base64ToPulse(String base64Code) { return pulses; } - private static List pulsesToWidthEncoded(List pulses, Integer startMark, - Integer start_space, Integer pulse_threshold, Integer space_threshold) { + private static List pulsesToWidthEncoded(List pulses, Integer startMark, Integer startSpace, + Integer pulseThreshold, Integer spaceThreshold) { List ret = new ArrayList<>(); if (pulses.size() < 68) { return null; } - if (pulse_threshold == null && space_threshold == null) { + if (pulseThreshold == null && spaceThreshold == null) { return null; } - if (start_mark != null) { + if (startMark != null) { while (pulses.size() >= 68 - && (pulses.get(0) < (start_mark * 0.75) || pulses.get(0) > (start_mark * 1.25))) { + && (pulses.get(0) < (startMark * 0.75) || pulses.get(0) > (startMark * 1.25))) { pulses.remove(0); } while (pulses.size() >= 68) { - if (pulses.get(0) < start_mark * 0.75 || pulses.get(0) > start_mark * 1.25) { + if (pulses.get(0) < startMark * 0.75 || pulses.get(0) > startMark * 1.25) { return null; } - if (start_space != null - && (pulses.get(1) < (start_space * 0.75) || pulses.get(1) > (start_space * 1.25))) { + if (startSpace != null + && (pulses.get(1) < (startSpace * 0.75) || pulses.get(1) > (startSpace * 1.25))) { return null; } @@ -102,25 +105,25 @@ private static List pulsesToWidthEncoded(List pulses, Integer sta long x = 0L; for (int i = 31; i >= 0; i--) { - Integer pulse_match = null; - Integer space_match = null; + Integer pulseMatch = null; + Integer spaceMatch = null; - if (pulse_threshold != null) { - pulse_match = pulses.get(0) >= pulse_threshold ? 1 : 0; + if (pulseThreshold != null) { + pulseMatch = pulses.get(0) >= pulseThreshold ? 1 : 0; } - if (space_threshold != null) { - space_match = pulses.get(1) >= space_threshold ? 1 : 0; + if (spaceThreshold != null) { + spaceMatch = pulses.get(1) >= spaceThreshold ? 1 : 0; } - if (pulse_match != null && space_match != null) { - if (!pulse_match.equals(space_match)) { + if (pulseMatch != null && spaceMatch != null) { + if (!pulseMatch.equals(spaceMatch)) { return null; } - res = space_match; - } else if (pulse_match == null) { - res = space_match; + res = spaceMatch; + } else if (pulseMatch == null) { + res = spaceMatch; } else { - res = pulse_match; + res = pulseMatch; } if (res != null) { @@ -141,8 +144,8 @@ private static List pulsesToWidthEncoded(List pulses, Integer sta return ret; } - private static ArrayList widthEncodedToPulses(long data, PulseParams param) { - ArrayList pulses = new ArrayList<>(); + private static List widthEncodedToPulses(long data, PulseParams param) { + List pulses = new ArrayList<>(); pulses.add(param.startMark); pulses.add(param.startSpace); @@ -173,23 +176,23 @@ private static long mirrorBits(long data, int bits) { return out & 0xFF; } - private static List pulsesToNec(ArrayList pulses) { + private static List pulsesToNec(List pulses) { List ret = new ArrayList<>(); List res = pulsesToWidthEncoded(pulses, 9000, null, null, 1125); for (Long code : res) { long addr = mirrorBits((code >> 24) & 0xFF, 8); - long addr_not = mirrorBits((code >> 16) & 0xFF, 8); + long addrNot = mirrorBits((code >> 16) & 0xFF, 8); long data = mirrorBits((code >> 8) & 0xFF, 8); - long data_not = mirrorBits(code & 0xFF, 8); + long dataNot = mirrorBits(code & 0xFF, 8); - if (addr != (addr_not ^ 0xFF)) { - addr = (addr << 8) | addr_not; + if (addr != (addrNot ^ 0xFF)) { + addr = (addr << 8) | addrNot; } String d = String.format( "{ \"type\": \"nec\", \"uint32\": %d, \"address\": None, \"data\": None, \"hex\": \"%08X\" }", code, code); - if (data == (data_not ^ 0xFF)) { + if (data == (dataNot ^ 0xFF)) { d = String.format( "{ \"type\": \"nec\", \"uint32\": %d, \"address\": %d, \"data\": %d, \"hex\": \"%08X\" }", code, addr, data, code); @@ -210,7 +213,7 @@ private static String bytesToHex(byte[] bytes) { return new String(hexChars); } - private static ArrayList necToPulses(long address, Long data) { + private static List necToPulses(long address, Long data) { Long newAddress, newData; if (data == null) { newAddress = address; @@ -229,10 +232,10 @@ private static ArrayList necToPulses(long address, Long data) { return widthEncodedToPulses(newAddress, new PulseParams()); } - private static String pulsesToBase64(ArrayList pulses) { + private static String pulsesToBase64(List pulses) { byte[] bytes = new byte[pulses.size() * 2]; - final Integer[] i = { 0 }; + final Integer[] i = {0}; pulses.forEach(p -> { int val = p.shortValue(); @@ -251,7 +254,7 @@ private static String pulsesToBase64(ArrayList pulses) { * @return the string */ public static String necToBase64(long code) { - ArrayList pulses = necToPulses(code, null); + List pulses = necToPulses(code, null); return pulsesToBase64(pulses); } @@ -262,11 +265,11 @@ public static String necToBase64(long code) { * @return the string */ public static String samsungToBase64(long code) { - ArrayList pulses = samsungToPulses(code, null); + List pulses = samsungToPulses(code, null); return pulsesToBase64(pulses); } - private static ArrayList samsungToPulses(long address, Long data) { + private static List samsungToPulses(long address, Long data) { Long newAddress, newData; if (data == null) { newAddress = address; @@ -281,17 +284,17 @@ private static ArrayList samsungToPulses(long address, Long data) { private static List pulsesToSamsung(ArrayList pulses) { List ret = new ArrayList<>(); - ArrayList res = pulsesToWidthEncoded(pulses, 4500, null, null, 1125); + List res = pulsesToWidthEncoded(pulses, 4500, null, null, 1125); for (Long code : res) { long addr = (code >> 24) & 0xFF; - long addr_not = (code >> 16) & 0xFF; + long addrNot = (code >> 16) & 0xFF; long data = (code >> 8) & 0xFF; - long data_not = code & 0xFF; + long dataNot = code & 0xFF; String d = String.format( "{ \"type\": \"samsung\", \"uint32\": %d, \"address\": None, \"data\": None, \"hex\": \"%08X\" }", code, code); - if (addr == addr_not && data == (data_not ^ 0xFF)) { + if (addr == addrNot && data == (dataNot ^ 0xFF)) { addr = mirrorBits(addr, 8); data = mirrorBits(data, 8); d = String.format( From 077f138e011f76aaa75abf6ccf2e3de29fa57805 Mon Sep 17 00:00:00 2001 From: "Dmitry P. (d51x)" Date: Mon, 10 Jul 2023 19:57:29 +0300 Subject: [PATCH 10/15] [tuya] IR Controller: fix code style & possible check for NPE Signed-off-by: Dmitry P. (d51x) --- .../tuya/internal/TuyaHandlerFactory.java | 3 +- .../internal/handler/TuyaDeviceHandler.java | 44 +++++++------- .../binding/tuya/internal/util/IrUtils.java | 58 +++++++++++++------ 3 files changed, 64 insertions(+), 41 deletions(-) diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/TuyaHandlerFactory.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/TuyaHandlerFactory.java index a43ef24e97..40b256379e 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/TuyaHandlerFactory.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/TuyaHandlerFactory.java @@ -12,7 +12,8 @@ */ package org.smarthomej.binding.tuya.internal; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.*; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.THING_TYPE_PROJECT; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.THING_TYPE_TUYA_DEVICE; import java.lang.reflect.Type; import java.util.List; diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java index 5bde4702ee..61417ea4df 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java @@ -12,9 +12,26 @@ */ package org.smarthomej.binding.tuya.internal.handler; -import com.google.gson.Gson; -import com.google.gson.JsonSyntaxException; -import io.netty.channel.EventLoopGroup; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_COLOR; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_DIMMER; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_IR_CODE; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_NUMBER; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_STRING; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_SWITCH; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.SCHEMAS; + +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.cache.ExpiringCache; @@ -54,25 +71,10 @@ import org.smarthomej.binding.tuya.internal.util.SchemaDp; import org.smarthomej.commons.SimpleDynamicCommandDescriptionProvider; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_COLOR; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_DIMMER; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_IR_CODE; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_NUMBER; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_STRING; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_SWITCH; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.SCHEMAS; +import io.netty.channel.EventLoopGroup; /** * The {@link TuyaDeviceHandler} handles commands and state updates diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/util/IrUtils.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/util/IrUtils.java index 7f69ea9acd..37c4963053 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/util/IrUtils.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/util/IrUtils.java @@ -12,14 +12,14 @@ */ package org.smarthomej.binding.tuya.internal.util; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Base64; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * The {@link IrUtils} is a support class for decode/encode infra-red codes *

@@ -40,8 +40,15 @@ private IrUtils() { * @return the nec-format code */ public static String base64ToNec(String base64Code) { - ArrayList pulses = base64ToPulse(base64Code); - return pulsesToNec(pulses).get(0); + String result = null; + List pulses = base64ToPulse(base64Code); + if (pulses != null && !pulses.isEmpty()) { + List res = pulsesToNec(pulses); + if (res != null && !res.isEmpty()) { + result = res.get(0); + } + } + return result; } /** @@ -51,27 +58,38 @@ public static String base64ToNec(String base64Code) { * @return the samsung-format code */ public static String base64ToSamsung(String base64Code) { - ArrayList pulses = base64ToPulse(base64Code); - return pulsesToSamsung(pulses).get(0); + String result = null; + List pulses = base64ToPulse(base64Code); + if (pulses != null && !pulses.isEmpty()) { + List res = pulsesToSamsung(pulses); + if (res != null && !res.isEmpty()) { + result = res.get(0); + } + } + return result; } - private static ArrayList base64ToPulse(String base64Code) { - ArrayList pulses = new ArrayList<>(); + private static List base64ToPulse(String base64Code) { + List pulses = new ArrayList<>(); String key = (base64Code.length() % 4 == 1 && base64Code.startsWith("1")) ? base64Code.substring(1) : base64Code; byte[] raw_bytes = Base64.getDecoder().decode(key.getBytes(StandardCharsets.UTF_8)); int i = 0; - while (i < raw_bytes.length) { - int word = ((raw_bytes[i] & 0xFF) + (raw_bytes[i + 1] & 0xFF) * 256) & 0xFFFF; - pulses.add(word); - i += 2; + try { + while (i < raw_bytes.length) { + int word = ((raw_bytes[i] & 0xFF) + (raw_bytes[i + 1] & 0xFF) * 256) & 0xFFFF; + pulses.add(word); + i += 2; + } + } catch (ArrayIndexOutOfBoundsException e) { + logger.error("Failed to convert base64 key code to pulses: {}", e.getMessage()); } return pulses; } private static List pulsesToWidthEncoded(List pulses, Integer startMark, Integer startSpace, - Integer pulseThreshold, Integer spaceThreshold) { + Integer pulseThreshold, Integer spaceThreshold) { List ret = new ArrayList<>(); if (pulses.size() < 68) { return null; @@ -82,8 +100,7 @@ private static List pulsesToWidthEncoded(List pulses, Integer sta } if (startMark != null) { - while (pulses.size() >= 68 - && (pulses.get(0) < (startMark * 0.75) || pulses.get(0) > (startMark * 1.25))) { + while (pulses.size() >= 68 && (pulses.get(0) < (startMark * 0.75) || pulses.get(0) > (startMark * 1.25))) { pulses.remove(0); } @@ -179,7 +196,10 @@ private static long mirrorBits(long data, int bits) { private static List pulsesToNec(List pulses) { List ret = new ArrayList<>(); List res = pulsesToWidthEncoded(pulses, 9000, null, null, 1125); - + if (res == null || res.isEmpty()) { + logger.error("Failed to convert pulses to NEC-format"); + return ret; + } for (Long code : res) { long addr = mirrorBits((code >> 24) & 0xFF, 8); long addrNot = mirrorBits((code >> 16) & 0xFF, 8); @@ -235,7 +255,7 @@ private static List necToPulses(long address, Long data) { private static String pulsesToBase64(List pulses) { byte[] bytes = new byte[pulses.size() * 2]; - final Integer[] i = {0}; + final Integer[] i = { 0 }; pulses.forEach(p -> { int val = p.shortValue(); @@ -282,7 +302,7 @@ private static List samsungToPulses(long address, Long data) { return widthEncodedToPulses(newAddress, new PulseParams()); } - private static List pulsesToSamsung(ArrayList pulses) { + private static List pulsesToSamsung(List pulses) { List ret = new ArrayList<>(); List res = pulsesToWidthEncoded(pulses, 4500, null, null, 1125); for (Long code : res) { From 06097e2ed5564deebcceedb95f945613d342bfa1 Mon Sep 17 00:00:00 2001 From: "Dmitry P. (d51x)" Date: Mon, 10 Jul 2023 19:57:29 +0300 Subject: [PATCH 11/15] [tuya] IR Controller: fix code style & possible check for NPE Signed-off-by: Dmitry P. (d51x) --- .../tuya/internal/TuyaHandlerFactory.java | 3 +- .../internal/handler/TuyaDeviceHandler.java | 44 ++++++------- .../binding/tuya/internal/util/IrUtils.java | 63 +++++++++++++------ 3 files changed, 69 insertions(+), 41 deletions(-) diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/TuyaHandlerFactory.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/TuyaHandlerFactory.java index a43ef24e97..40b256379e 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/TuyaHandlerFactory.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/TuyaHandlerFactory.java @@ -12,7 +12,8 @@ */ package org.smarthomej.binding.tuya.internal; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.*; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.THING_TYPE_PROJECT; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.THING_TYPE_TUYA_DEVICE; import java.lang.reflect.Type; import java.util.List; diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java index 5bde4702ee..61417ea4df 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java @@ -12,9 +12,26 @@ */ package org.smarthomej.binding.tuya.internal.handler; -import com.google.gson.Gson; -import com.google.gson.JsonSyntaxException; -import io.netty.channel.EventLoopGroup; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_COLOR; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_DIMMER; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_IR_CODE; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_NUMBER; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_STRING; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_SWITCH; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.SCHEMAS; + +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.cache.ExpiringCache; @@ -54,25 +71,10 @@ import org.smarthomej.binding.tuya.internal.util.SchemaDp; import org.smarthomej.commons.SimpleDynamicCommandDescriptionProvider; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_COLOR; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_DIMMER; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_IR_CODE; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_NUMBER; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_STRING; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_SWITCH; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.SCHEMAS; +import io.netty.channel.EventLoopGroup; /** * The {@link TuyaDeviceHandler} handles commands and state updates diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/util/IrUtils.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/util/IrUtils.java index 7f69ea9acd..b786a85ef6 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/util/IrUtils.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/util/IrUtils.java @@ -12,14 +12,14 @@ */ package org.smarthomej.binding.tuya.internal.util; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Base64; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * The {@link IrUtils} is a support class for decode/encode infra-red codes *

@@ -40,8 +40,15 @@ private IrUtils() { * @return the nec-format code */ public static String base64ToNec(String base64Code) { - ArrayList pulses = base64ToPulse(base64Code); - return pulsesToNec(pulses).get(0); + String result = null; + List pulses = base64ToPulse(base64Code); + if (pulses != null && !pulses.isEmpty()) { + List res = pulsesToNec(pulses); + if (res != null && !res.isEmpty()) { + result = res.get(0); + } + } + return result; } /** @@ -51,27 +58,43 @@ public static String base64ToNec(String base64Code) { * @return the samsung-format code */ public static String base64ToSamsung(String base64Code) { - ArrayList pulses = base64ToPulse(base64Code); - return pulsesToSamsung(pulses).get(0); + String result = null; + List pulses = base64ToPulse(base64Code); + if (pulses != null && !pulses.isEmpty()) { + List res = pulsesToSamsung(pulses); + if (res != null && !res.isEmpty()) { + result = res.get(0); + } + } + return result; } - private static ArrayList base64ToPulse(String base64Code) { - ArrayList pulses = new ArrayList<>(); + private static List base64ToPulse(String base64Code) { + List pulses = new ArrayList<>(); String key = (base64Code.length() % 4 == 1 && base64Code.startsWith("1")) ? base64Code.substring(1) : base64Code; byte[] raw_bytes = Base64.getDecoder().decode(key.getBytes(StandardCharsets.UTF_8)); int i = 0; - while (i < raw_bytes.length) { - int word = ((raw_bytes[i] & 0xFF) + (raw_bytes[i + 1] & 0xFF) * 256) & 0xFFFF; - pulses.add(word); - i += 2; + try { + while (i < raw_bytes.length) { + int word = ((raw_bytes[i] & 0xFF) + (raw_bytes[i + 1] & 0xFF) * 256) & 0xFFFF; + pulses.add(word); + i += 2; + + // dirty hack because key not aligned by 4 byte ? + if (i >= raw_bytes.length) { + break; + } + } + } catch (ArrayIndexOutOfBoundsException e) { + logger.error("Failed to convert base64 key code to pulses: {}", e.getMessage()); } return pulses; } private static List pulsesToWidthEncoded(List pulses, Integer startMark, Integer startSpace, - Integer pulseThreshold, Integer spaceThreshold) { + Integer pulseThreshold, Integer spaceThreshold) { List ret = new ArrayList<>(); if (pulses.size() < 68) { return null; @@ -82,8 +105,7 @@ private static List pulsesToWidthEncoded(List pulses, Integer sta } if (startMark != null) { - while (pulses.size() >= 68 - && (pulses.get(0) < (startMark * 0.75) || pulses.get(0) > (startMark * 1.25))) { + while (pulses.size() >= 68 && (pulses.get(0) < (startMark * 0.75) || pulses.get(0) > (startMark * 1.25))) { pulses.remove(0); } @@ -179,7 +201,10 @@ private static long mirrorBits(long data, int bits) { private static List pulsesToNec(List pulses) { List ret = new ArrayList<>(); List res = pulsesToWidthEncoded(pulses, 9000, null, null, 1125); - + if (res == null || res.isEmpty()) { + logger.warn("[tuya:ir-controller] No ir key-code detected"); + return null; + } for (Long code : res) { long addr = mirrorBits((code >> 24) & 0xFF, 8); long addrNot = mirrorBits((code >> 16) & 0xFF, 8); @@ -235,7 +260,7 @@ private static List necToPulses(long address, Long data) { private static String pulsesToBase64(List pulses) { byte[] bytes = new byte[pulses.size() * 2]; - final Integer[] i = {0}; + final Integer[] i = { 0 }; pulses.forEach(p -> { int val = p.shortValue(); @@ -282,7 +307,7 @@ private static List samsungToPulses(long address, Long data) { return widthEncodedToPulses(newAddress, new PulseParams()); } - private static List pulsesToSamsung(ArrayList pulses) { + private static List pulsesToSamsung(List pulses) { List ret = new ArrayList<>(); List res = pulsesToWidthEncoded(pulses, 4500, null, null, 1125); for (Long code : res) { From 21ff55c62dd196a5e54c4bb22df4bdf712597cd3 Mon Sep 17 00:00:00 2001 From: "Dmitry P. (d51x)" Date: Mon, 10 Jul 2023 20:13:27 +0300 Subject: [PATCH 12/15] [tuya] IR Controller: fix continuous learning key code Signed-off-by: Dmitry P. (d51x) --- .../internal/handler/TuyaDeviceHandler.java | 41 ++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java index 61417ea4df..e7b0495b8a 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java @@ -100,7 +100,7 @@ public class TuyaDeviceHandler extends BaseThingHandler implements DeviceInfoSub private @Nullable ScheduledFuture reconnectFuture; private @Nullable ScheduledFuture pollingJob; - + private @Nullable ScheduledFuture irLearnJob; private boolean disposing = false; private final Map dpToChannelId = new HashMap<>(); @@ -188,7 +188,7 @@ private void processChannelStatus(Integer dp, Object value) { String decoded = convertBase64Code(configuration, (String) value); logger.info("thing {} received ir code: {}", thing.getUID(), decoded); updateState(channelId, new StringType(decoded)); - repeatStudyCode(); + irStartLearning(configuration.activeListen); } return; } @@ -221,6 +221,16 @@ public void connectionStatus(boolean status) { pollingJob = scheduler.scheduleWithFixedDelay(tuyaDevice::refreshStatus, pollingInterval, pollingInterval, TimeUnit.SECONDS); } + + // start learning code if thing is online and presents 'ir-code' channel + this.getThing().getChannels().stream() + .filter(channel -> CHANNEL_TYPE_UID_IR_CODE.equals(channel.getChannelTypeUID())).findFirst() + .ifPresent(channel -> { + ChannelConfiguration config = channelIdToConfiguration.get(channel.getChannelTypeUID()); + if (config != null) { + irStartLearning(config.activeListen); + } + }); } else { updateStatus(ThingStatus.OFFLINE); ScheduledFuture pollingJob = this.pollingJob; @@ -235,6 +245,7 @@ public void connectionStatus(boolean status) { if (tuyaDevice != null && !disposing && (reconnectFuture == null || reconnectFuture.isDone())) { this.reconnectFuture = scheduler.schedule(tuyaDevice::connect, 5000, TimeUnit.MILLISECONDS); } + irStopLearning(); } } @@ -343,6 +354,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { commandRequest.put(1, "study_key"); commandRequest.put(7, base64Code); } + irStopLearning(); } } @@ -353,9 +365,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { if (CHANNEL_TYPE_UID_IR_CODE.equals(channelTypeUID)) { if (command instanceof StringType) { - if (Boolean.TRUE.equals(configuration.activeListen)) { - repeatStudyCode(); - } + irStartLearning(configuration.activeListen); } } } @@ -380,6 +390,7 @@ public void dispose() { tuyaDevice.dispose(); this.tuyaDevice = null; } + irStopLearning(); } @Override @@ -540,6 +551,9 @@ private void configureChannel(Channel channel) { .requireNonNull(dp2ToChannelId.computeIfAbsent(configuration.dp2, ArrayList::new)); list.add(channelId); } + if (CHANNEL_TYPE_UID_IR_CODE.equals(channelTypeUID)) { + irStartLearning(configuration.activeListen); + } } private List toCommandOptionList(List options) { @@ -610,4 +624,21 @@ private void repeatStudyCode() { tuyaDevice.set(commandRequest); } } + + private void irStopLearning() { + logger.debug("[tuya:ir-controller] stop ir learning"); + ScheduledFuture feature = irLearnJob; + if (feature != null) { + feature.cancel(true); + this.irLearnJob = null; + } + } + + private void irStartLearning(Boolean available) { + irStopLearning(); + if (available) { + logger.debug("[tuya:ir-controller] start ir learning"); + irLearnJob = scheduler.scheduleWithFixedDelay(this::repeatStudyCode, 200, 29000, TimeUnit.MILLISECONDS); + } + } } From 6c1fe69f1da5aa5c0985889fadb3c7635f4f8fff Mon Sep 17 00:00:00 2001 From: "Dmitry P. (d51x)" Date: Mon, 10 Jul 2023 22:32:50 +0300 Subject: [PATCH 13/15] [tuya] IR Controller: fix possible NPE Signed-off-by: Dmitry P. (d51x) --- .../internal/handler/TuyaDeviceHandler.java | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java index e7b0495b8a..c39fed21af 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java @@ -12,26 +12,6 @@ */ package org.smarthomej.binding.tuya.internal.handler; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_COLOR; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_DIMMER; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_IR_CODE; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_NUMBER; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_STRING; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_SWITCH; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.SCHEMAS; - -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.cache.ExpiringCache; @@ -71,11 +51,31 @@ import org.smarthomej.binding.tuya.internal.util.SchemaDp; import org.smarthomej.commons.SimpleDynamicCommandDescriptionProvider; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; import io.netty.channel.EventLoopGroup; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_COLOR; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_DIMMER; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_IR_CODE; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_NUMBER; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_STRING; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_SWITCH; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.SCHEMAS; + /** * The {@link TuyaDeviceHandler} handles commands and state updates * @@ -578,7 +578,7 @@ private long convertHexCode(String code) { } private String convertBase64Code(ChannelConfiguration channelConfig, String encoded) { - String decoded; + String decoded = ""; try { if (channelConfig.irType.equals("nec")) { decoded = IrUtils.base64ToNec(encoded); @@ -603,6 +603,8 @@ private String convertBase64Code(ChannelConfiguration channelConfig, String enco } catch (JsonSyntaxException e) { logger.error("Incorrect json response: {}", e.getMessage()); decoded = encoded; + } catch (NullPointerException e) { + logger.error("unable decode key code'{}', reason: {}", decoded, e.getMessage()); } return decoded; } From 3a146575427ceede9eb36b7ab87596b1c5f1c996 Mon Sep 17 00:00:00 2001 From: d51x Date: Sun, 16 Jul 2023 16:37:51 +0300 Subject: [PATCH 14/15] fix code format after spotless check --- .../internal/handler/TuyaDeviceHandler.java | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java index c39fed21af..abdef9566d 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java @@ -12,6 +12,26 @@ */ package org.smarthomej.binding.tuya.internal.handler; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_COLOR; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_DIMMER; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_IR_CODE; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_NUMBER; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_STRING; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_SWITCH; +import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.SCHEMAS; + +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.cache.ExpiringCache; @@ -51,31 +71,11 @@ import org.smarthomej.binding.tuya.internal.util.SchemaDp; import org.smarthomej.commons.SimpleDynamicCommandDescriptionProvider; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; import io.netty.channel.EventLoopGroup; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_COLOR; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_DIMMER; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_IR_CODE; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_NUMBER; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_STRING; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.CHANNEL_TYPE_UID_SWITCH; -import static org.smarthomej.binding.tuya.internal.TuyaBindingConstants.SCHEMAS; - /** * The {@link TuyaDeviceHandler} handles commands and state updates * From 0e887ba4519ff1cb603eff90d6c595a249cec2ce Mon Sep 17 00:00:00 2001 From: d51x Date: Sun, 16 Jul 2023 16:41:12 +0300 Subject: [PATCH 15/15] fix code format after spotless check Signed-off-by: Dmitry Pyatykh --- .../binding/tuya/internal/handler/TuyaDeviceHandler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java index abdef9566d..5d4f39bfc4 100644 --- a/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java +++ b/bundles/org.smarthomej.binding.tuya/src/main/java/org/smarthomej/binding/tuya/internal/handler/TuyaDeviceHandler.java @@ -137,7 +137,6 @@ public void processDeviceStatus(Map deviceStatus) { if (tuyaDevice != null) { tuyaDevice.set(commandRequest); } - return; }