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
{
public const int DP_SEND_IR = 201; // ir_send, send and report (read-write)
public const int DP_LEARNED_ID = 202; // ir_study_code, report only (read-only)
public const string NSDP_CONTROL = "control"; // The control commands
public const string NSDP_STUDY_CODE = "study_code"; // Report learned IR codes
public const string NSDP_IR_CODE = "ir_code"; // IR signal decoding2
public const string NSDP_KEY_CODE = "key_code"; // Remote key code
public const string NSDP_KEY_CODE2 = "key_code2"; // Remote key code 2
public const string NSDP_KEY_CODE3 = "key_code3"; // Remote key code 3
public const string NSDP_KEY_CODE4 = "key_code4"; // Remote key code 4
public const string NSDP_KEY_STUDY = "key_study"; // Send the learning code 1
public const string NSDP_KEY_STUDY2 = "key_study2"; // Send the learning code 2
public const string NSDP_KEY_STUDY3 = "key_study3"; // Send the learning code 3
public const string NSDP_KEY_STUDY4 = "key_study4"; // Send the learning code 4
public const string NSDP_DELAY_TIME = "delay_time"; // IR code transmission delay
public const string NSDP_TYPE = "type"; // The identifier of an IR library
public const string NSDP_DELAY = "delay"; // Actually used but not documented
public const string NSDP_HEAD = "head"; // Actually used but not documented
public const string NSDP_KEY1 = "key1"; // Actually used but not documented
///
/// 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)
{
}
///
/// Creates a new instance of the TuyaDevice class.
///
/// IP address of device.
/// Region to access Cloud API.
/// Access ID to access Cloud API.
/// API secret to access Cloud API.
/// Device ID.
/// Protocol version.
/// TCP port of device.
/// Receive timeout (msec).
public TuyaIRControl(string ip, TuyaApi.Region region, string accessId, string apiSecret, string deviceId, TuyaProtocolVersion protocolVersion = TuyaProtocolVersion.V33, int port = 6668, int receiveTimeout = 250)
: base(ip, region, accessId, apiSecret, 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, int retries = 2, CancellationToken cancellationToken = default)
{
try
{
var subCmd = new Dictionary()
{
{ NSDP_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()
{
{ NSDP_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)
{
var result = response[202].ToString();
return result;
}
}
}
finally
{
try
{
var subCmd = new Dictionary()
{
{ NSDP_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, int retries = 2, int? overrideRecvTimeout = null, CancellationToken cancellationToken = default)
{
var subCmd = new Dictionary()
{
{ NSDP_CONTROL, "send_ir" },
{ NSDP_KEY1, buttonCode.Length % 4 == 0 ? "1" + buttonCode : buttonCode }, // code need to be padded with "1" (wtf?)
{ NSDP_TYPE, 0 },
{ NSDP_DELAY, 0 }
};
var subCmdJson = JsonConvert.SerializeObject(subCmd);
await SetDpsAsync(new Dictionary() { { 201, subCmdJson } }, retries: retries,
nullRetries: 0, overrideRecvTimeout: overrideRecvTimeout, 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.Length % 4 == 1 && codeBase64.StartsWith("1"))
? codeBase64.Substring(1) // code can be padded with "1" (wtf?)
: codeBase64
);
var pulses = Enumerable.Range(0, bytes.Length)
.Where(x => x % 2 == 0)
.Select(x => (ushort)((bytes[x] | bytes[x + 1] << 8)))
.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 bytes = pulses.SelectMany(x => new byte[] { (byte)(x & 0xFF), (byte)((x >> 8) & 0xFF) });
var codeBase64 = Convert.ToBase64String(bytes.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;
}
}
}