From 506a6cbfba878bd71b9293dc6def1595a4f77482 Mon Sep 17 00:00:00 2001 From: Alexey 'Cluster' Avdyukhin Date: Tue, 5 Jul 2022 22:07:59 +0400 Subject: Tuya virtual IR remote control device support. --- TuyaIRControl.cs | 152 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 TuyaIRControl.cs diff --git a/TuyaIRControl.cs b/TuyaIRControl.cs new file mode 100644 index 0000000..2ca960c --- /dev/null +++ b/TuyaIRControl.cs @@ -0,0 +1,152 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace com.clusterrr.TuyaNet +{ + /// + /// Tuya virtual IR remote control + /// + public class TuyaIRControl : TuyaDevice + { + /// + /// Creates a new instance of the TuyaDevice class. + /// + /// IP address of device. + /// Local key of device (obtained via API). + /// Device ID. + /// Protocol version. + /// TCP port of device. + /// Receive timeout (msec). + public TuyaIRControl(string ip, string localKey, string deviceId, TuyaProtocolVersion protocolVersion = TuyaProtocolVersion.V33, int port = 6668, int receiveTimeout = 250) + : base(ip, localKey, deviceId, protocolVersion, port, receiveTimeout) + { + } + + /// + /// Learns button code of remote control. + /// + /// Learing timeout, you should press RC button during this interval. + /// Cancellation token. + /// Button code as Base64 string. + public async Task GetButtonCodeAsync(int timeout, CancellationToken cancellationToken = default) + { + try + { + var subCmd = new Dictionary() + { + { "control", "study_exit" } + }; + var subCmdJson = JsonConvert.SerializeObject(subCmd); + await SetDpsAsync(new Dictionary() { { 201, subCmdJson } }, nullRetries: 0, allowEmptyResponse: true, cancellationToken: cancellationToken); + + await Task.Delay(1000); + + subCmd = new Dictionary() + { + { "control", "study" } + }; + subCmdJson = JsonConvert.SerializeObject(subCmd); + await SetDpsAsync(new Dictionary() { { 201, subCmdJson } }, cancellationToken: cancellationToken); + + while (true) + { + var response = await SetDpsAsync(new Dictionary() { { 201, subCmdJson } }, overrideRecvTimeout: timeout, allowEmptyResponse: true, cancellationToken: cancellationToken); + if (response != null) + { + return response[202].ToString(); + } + } + } finally + { + try + { + var subCmd = new Dictionary() + { + { "control", "study_exit" } + }; + var subCmdJson = JsonConvert.SerializeObject(subCmd); + await SetDpsAsync(new Dictionary() { { 201, subCmdJson } }, nullRetries: 0, allowEmptyResponse: true, cancellationToken: cancellationToken); + } + catch { } + } + } + + /// + /// Sends button code. + /// + /// Button code in Base64 encoding. + /// Cancellation token. + public async Task SendButtonCodeAsync(string buttonCode, CancellationToken cancellationToken = default) + { + var subCmd = new Dictionary() + { + { "control", "send_ir" }, + { "key1", buttonCode } + }; + var subCmdJson = JsonConvert.SerializeObject(subCmd); + await SetDpsAsync(new Dictionary() { { 201, subCmdJson } }, nullRetries: 0, allowEmptyResponse: true, cancellationToken: cancellationToken); + } + + /// + /// Converts Base64 encoded button code into pulses duration. + /// + /// Base64 encoded button code. + /// Pulses/gaps length in microsecods. + public static ushort[] Base64ToPulses(string codeBase64) + { + var bytes = Convert.FromBase64String(codeBase64); + var pulses = Enumerable.Range(2, bytes.Length - 3) + .Where(x => x % 2 == 0) + .Select(x => (ushort)((bytes[x] << 8 | bytes[x + 1]) / 4)) + .ToArray(); + return pulses; + } + + /// + /// Converts pulses duration into Base64 encoded button code. + /// + /// Pulses/gaps length in microsecods. + /// Base64 encoded button code. + public static string PulsesToBase64(ushort[] pulses) + { + var pulsesMult = pulses.Select(x => (ushort)(x * 4)); // Convert 1 us to 250ns + var code64 = Enumerable.Concat(new ushort[] { 0xd500 /* Uknown value*/ }, pulsesMult); + var code64b = code64.SelectMany(x => new byte[] { (byte)((x >> 8) & 0xFF), (byte)(x & 0xFF) }); + code64b = Enumerable.Concat(code64b, new byte[] { 8 }); // Unknown padding byte + var codeBase64 = Convert.ToBase64String(code64b.ToArray()); + return codeBase64; + } + + /// + /// Converts hex encoded button code into pulses duration. + /// + /// Hex encoded button code. + /// Pulses/gaps length in microsecods. + public static ushort[] HexToPulses(string codeHex) + { + var pulses = Enumerable.Range(0, codeHex.Length) + .Where(x => (x % 4) == 0) + .Select(x => (ushort)(Convert.ToUInt16(codeHex.Substring(x + 2, 2) + codeHex.Substring(x, 2), 16))); + return pulses.ToArray(); + } + + /// + /// Converts pulses duration into hex encoded button code. + /// + /// Pulses/gaps length in microsecods. + /// Hex encoded button code. + public static string PulsesToHex(ushort[] pulses) + { + var words = Enumerable.Range(0, pulses.Length) + .Select(x => $"{(pulses[x] & 0xFF):x02}{((pulses[x] >> 8) & 0xFF):x02}").ToArray(); + var hex = string.Concat(words); + return hex; + } + } +} -- cgit v1.2.3