Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/ClusterM/tuyanet.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexey 'Cluster' Avdyukhin <clusterrr@clusterrr.com>2021-10-27 17:34:46 +0300
committerAlexey 'Cluster' Avdyukhin <clusterrr@clusterrr.com>2021-10-27 17:36:41 +0300
commit3960735f1b6a00c8d732b20db55e204e8abf0d04 (patch)
treeb0c44cdf41d3d9145732ea7844515d29f5b58c1a
parentcffc3a33ba3211f431861d5a78716cdcee5bdd09 (diff)
Comments and minor fixes
-rw-r--r--TuyaApi.cs55
-rw-r--r--TuyaCommand.cs3
-rw-r--r--TuyaDevice.cs91
-rw-r--r--TuyaDeviceApiInfo.cs3
-rw-r--r--TuyaDeviceScanInfo.cs3
-rw-r--r--TuyaDeviceStatus.cs9
-rw-r--r--TuyaLocalResponse.cs20
-rw-r--r--TuyaNet.csproj7
-rw-r--r--TuyaParser.cs4
-rw-r--r--TuyaProtocolVersion.cs9
-rw-r--r--TuyaScanner.cs22
11 files changed, 201 insertions, 25 deletions
diff --git a/TuyaApi.cs b/TuyaApi.cs
index 27d4bfe..70c3487 100644
--- a/TuyaApi.cs
+++ b/TuyaApi.cs
@@ -11,16 +11,19 @@ using System.Threading.Tasks;
namespace com.clusterrr.TuyaNet
{
+ /// <summary>
+ /// Provides access to Tuya Web API.
+ /// </summary>
public class TuyaApi
{
private readonly Region region;
- private readonly string apiKey;
+ private readonly string accessId;
private readonly string apiSecret;
private readonly HttpClient httpClient;
private TuyaToken token = null;
private DateTime tokenTime = new DateTime();
- public class TuyaToken
+ private class TuyaToken
{
[JsonPropertyName("access_token")]
public string AccessToken { get; set; }
@@ -35,14 +38,23 @@ namespace com.clusterrr.TuyaNet
public string Uid { get; set; }
}
- public TuyaApi(Region region, string apiKey, string apiSecret)
+ /// <summary>
+ /// Creates a new instance of the TuyaApi class.
+ /// </summary>
+ /// <param name="region">Region of server.</param>
+ /// <param name="accessId">Access ID/Client ID from https://iot.tuya.com/ .</param>
+ /// <param name="apiSecret">API secret from https://iot.tuya.com/ .</param>
+ public TuyaApi(Region region, string accessId, string apiSecret)
{
this.region = region;
- this.apiKey = apiKey;
+ this.accessId = accessId;
this.apiSecret = apiSecret;
httpClient = new HttpClient();
}
+ /// <summary>
+ /// Region of server.
+ /// </summary>
public enum Region
{
China,
@@ -80,6 +92,14 @@ namespace com.clusterrr.TuyaNet
return urlHost;
}
+ /// <summary>
+ /// Low-level request to API.
+ /// </summary>
+ /// <param name="uri">Method URI.</param>
+ /// <param name="body">Body of request if any.</param>
+ /// <param name="headers">Additional headers.</param>
+ /// <param name="noToken">Execute query without token.</param>
+ /// <returns>JSON string with response.</returns>
public async Task<string> RequestAsync(string uri, string body = null, Dictionary<string, string> headers = null, bool noToken = false)
{
var urlHost = RegionToHost(region);
@@ -99,13 +119,13 @@ namespace com.clusterrr.TuyaNet
string payload;
if (noToken)
{
- payload = apiKey + now;
+ payload = accessId + now;
headers["secret"] = apiSecret;
}
else
{
- await RefreshAccessToken();
- payload = apiKey + token.AccessToken + now;
+ await RefreshAccessTokenAsync();
+ payload = accessId + token.AccessToken + now;
}
using (var sha256 = SHA256.Create())
@@ -122,7 +142,7 @@ namespace com.clusterrr.TuyaNet
signature = string.Concat(hmac.ComputeHash(Encoding.UTF8.GetBytes(payload)).Select(b => $"{b:X2}"));
}
- headers["client_id"] = apiKey;
+ headers["client_id"] = accessId;
headers["sign"] = signature;
headers["t"] = now;
headers["sign_method"] = "HMAC-SHA256";
@@ -148,6 +168,10 @@ namespace com.clusterrr.TuyaNet
}
}
+ /// <summary>
+ /// Requests access token.
+ /// </summary>
+ /// <returns>Access token.</returns>
private async Task<TuyaToken> GetAccessTokenAsync()
{
var uri = "token?grant_type=1";
@@ -156,7 +180,10 @@ namespace com.clusterrr.TuyaNet
return token;
}
- private async Task RefreshAccessToken()
+ /// <summary>
+ /// Refreshes access token if it's expired or not requested yet.
+ /// </summary>
+ private async Task RefreshAccessTokenAsync()
{
if ((token == null) || tokenTime.AddSeconds(token.ExpireTime) >= DateTime.Now)
{
@@ -165,6 +192,11 @@ namespace com.clusterrr.TuyaNet
}
}
+ /// <summary>
+ /// Requests info about device by it's ID.
+ /// </summary>
+ /// <param name="deviceId">Device ID.</param>
+ /// <returns>Device info.</returns>
public async Task<TuyaDeviceApiInfo> GetDeviceInfoAsync(string deviceId)
{
var uri = $"devices/{deviceId}";
@@ -173,6 +205,11 @@ namespace com.clusterrr.TuyaNet
return device;
}
+ /// <summary>
+ /// Requests info about all registered devices, requires ID of any registered device.
+ /// </summary>
+ /// <param name="anyDeviceId">ID of any registered device.</param>
+ /// <returns>Array of devices info.</returns>
public async Task<TuyaDeviceApiInfo[]> GetAllDevicesInfoAsync(string anyDeviceId)
{
var userId = (await GetDeviceInfoAsync(anyDeviceId)).UserId;
diff --git a/TuyaCommand.cs b/TuyaCommand.cs
index 2bfe119..720edfb 100644
--- a/TuyaCommand.cs
+++ b/TuyaCommand.cs
@@ -1,5 +1,8 @@
namespace com.clusterrr.TuyaNet
{
+ /// <summary>
+ /// Tuya command type
+ /// </summary>
public enum TuyaCommand
{
UDP = 0,
diff --git a/TuyaDevice.cs b/TuyaDevice.cs
index 748219d..414eeb4 100644
--- a/TuyaDevice.cs
+++ b/TuyaDevice.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
-using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading;
@@ -11,8 +10,20 @@ using System.Threading.Tasks;
namespace com.clusterrr.TuyaNet
{
+ /// <summary>
+ /// Connection with Tuya device.
+ /// </summary>
public class TuyaDevice : IDisposable
{
+ /// <summary>
+ /// Creates a new instance of the TuyaDevice class.
+ /// </summary>
+ /// <param name="ip">IP address of device.</param>
+ /// <param name="localKey">Local key of device (obtained via API).</param>
+ /// <param name="deviceId">Device ID.</param>
+ /// <param name="protocolVersion">Protocol version.</param>
+ /// <param name="port">TCP port of device.</param>
+ /// <param name="receiveTimeout">Receive timeout.</param>
public TuyaDevice(string ip, string localKey, string deviceId = null, TuyaProtocolVersion protocolVersion = TuyaProtocolVersion.V33, int port = 6668, int receiveTimeout = 250)
{
IP = ip;
@@ -23,25 +34,72 @@ namespace com.clusterrr.TuyaNet
ReceiveTimeout = receiveTimeout;
}
+ /// <summary>
+ /// IP address of device.
+ /// </summary>
public string IP { get; private set; }
+ /// <summary>
+ /// Local key of device.
+ /// </summary>
public string LocalKey { get; private set; }
- public int Port { get; private set; } = 6668;
+ /// <summary>
+ /// Device ID.
+ /// </summary>
public string DeviceId { get; private set; } = null;
+ /// <summary>
+ /// TCP port of device.
+ /// </summary>
+ public int Port { get; private set; } = 6668;
+ /// <summary>
+ /// Protocol version.
+ /// </summary>
public TuyaProtocolVersion ProtocolVersion { get; set; }
+ /// <summary>
+ /// Receive timeout.
+ /// </summary>
public int ReceiveTimeout { get; set; }
+ /// <summary>
+ /// Permanent connection (connect and stay connected).
+ /// </summary>
public bool PermanentConnection { get; set; } = false;
private TcpClient client = null;
+ /// <summary>
+ /// Creates encoded and encrypted payload data from JSON string.
+ /// </summary>
+ /// <param name="command">Tuya command ID.</param>
+ /// <param name="json">String with JSON to send.</param>
+ /// <returns>Raw data.</returns>
public byte[] CreatePayload(TuyaCommand command, string json)
=> TuyaParser.CreatePayload(command, json, Encoding.UTF8.GetBytes(LocalKey), ProtocolVersion);
- public TuyaLocalResponse DecodeResponse(byte[] data)
-
+ /// <summary>
+ /// Parses and decrypts payload data from received bytes.
+ /// </summary>
+ /// <param name="data">.</param>
+ /// <returns>Instance of TuyaLocalResponse.</returns>
+ public TuyaLocalResponse DecodeResponse(byte[] data)
=> TuyaParser.DecodeResponse(data, Encoding.UTF8.GetBytes(LocalKey), ProtocolVersion);
+
+ /// <summary>
+ /// Sends JSON string to device and reads response.
+ /// </summary>
+ /// <param name="command">Tuya command ID.</param>
+ /// <param name="json">JSON string.</param>
+ /// <param name="command">Tuya command ID.</param>
+ /// <param name="json">String with JSON to send.</param>
+ /// <returns>Parsed and decrypred received data as instance of TuyaLocalResponse.</returns>
public async Task<TuyaLocalResponse> SendAsync(TuyaCommand command, string json, int tries = 2, int nullRetries = 1)
=> DecodeResponse(await SendAsync(CreatePayload(command, json), tries, nullRetries));
+ /// <summary>
+ /// Sends raw data over to device and read response.
+ /// </summary>
+ /// <param name="data">Raw data to send.</param>
+ /// <param name="tries">Number of retries.</param>
+ /// <param name="nullRetries">Number of retries in case of null answer.</param>
+ /// <returns>Received data (raw).</returns>
public async Task<byte[]> SendAsync(byte[] data, int tries = 2, int nullRetries = 1)
{
Exception lastException = null;
@@ -122,6 +180,11 @@ namespace com.clusterrr.TuyaNet
return result;
}
+ /// <summary>
+ /// Requests current DPS status.
+ /// </summary>
+ /// <param name="deviceId">Device ID, required only if constuctor was called without it.</param>
+ /// <returns>Dictionary of DPS numbers and values.</returns>
public async Task<Dictionary<int, object>> GetDps(string deviceId = null)
{
deviceId = deviceId ?? DeviceId;
@@ -135,7 +198,7 @@ namespace com.clusterrr.TuyaNet
{ "t", (DateTime.Now - new DateTime(1970, 1, 1)).TotalSeconds.ToString("0") }
};
string requestJson = JsonSerializer.Serialize(cmd);
- var response = await SendAsync(TuyaCommand.CONTROL, requestJson, tries: 2, nullRetries: 2);
+ var response = await SendAsync(TuyaCommand.CONTROL, requestJson, tries: 2, nullRetries: 1);
if (string.IsNullOrEmpty(response.JSON))
throw new InvalidDataException("Response is empty");
var responseJson = JsonDocument.Parse(response.JSON);
@@ -143,9 +206,22 @@ namespace com.clusterrr.TuyaNet
return new Dictionary<int, object>(dps.Select(kv => new KeyValuePair<int, object>(int.Parse(kv.Key), kv.Value)));
}
+ /// <summary>
+ /// Sets DPS to specified value.
+ /// </summary>
+ /// <param name="dpsNumber">DPS number.</param>
+ /// <param name="value">Value.</param>
+ /// <param name="deviceId">Device ID, required only if constuctor was called without it.</param>
+ /// <returns></returns>
public async Task<Dictionary<int, object>> SetDps(int dpsNumber, object value, string deviceId = null)
=> await SetDps(new Dictionary<int, object> { { dpsNumber, value } }, deviceId);
+ /// <summary>
+ /// Sets DPS to specified value.
+ /// </summary>
+ /// <param name="dps">Dictionary of DPS numbers and values to set.</param>
+ /// <param name="deviceId">Device ID, required only if constuctor was called without it.</param>
+ /// <returns></returns>
public async Task<Dictionary<int, object>> SetDps(Dictionary<int, object> dps, string deviceId = null)
{
deviceId = deviceId ?? DeviceId;
@@ -160,7 +236,7 @@ namespace com.clusterrr.TuyaNet
{ "dps", dps }
};
string requestJson = JsonSerializer.Serialize(cmd);
- var response = await SendAsync(TuyaCommand.CONTROL, requestJson, tries: 2, nullRetries: 2);
+ var response = await SendAsync(TuyaCommand.CONTROL, requestJson, tries: 2, nullRetries: 1);
if (string.IsNullOrEmpty(response.JSON))
throw new InvalidDataException("Response is empty");
var responseJson = JsonDocument.Parse(response.JSON);
@@ -168,6 +244,9 @@ namespace com.clusterrr.TuyaNet
return new Dictionary<int, object>(newDps.Select(kv => new KeyValuePair<int, object>(int.Parse(kv.Key), kv.Value)));
}
+ /// <summary>
+ /// Disposes object.
+ /// </summary>
public void Dispose()
{
client?.Close();
diff --git a/TuyaDeviceApiInfo.cs b/TuyaDeviceApiInfo.cs
index d5228c4..5fd1b29 100644
--- a/TuyaDeviceApiInfo.cs
+++ b/TuyaDeviceApiInfo.cs
@@ -3,6 +3,9 @@ using System.Text.Json.Serialization;
namespace com.clusterrr.TuyaNet
{
+ /// <summary>
+ /// Device info received from Tuya API.
+ /// </summary>
public class TuyaDeviceApiInfo
{
[JsonPropertyName("active_time")]
diff --git a/TuyaDeviceScanInfo.cs b/TuyaDeviceScanInfo.cs
index 777b8b9..84befad 100644
--- a/TuyaDeviceScanInfo.cs
+++ b/TuyaDeviceScanInfo.cs
@@ -3,6 +3,9 @@ using System.Text.Json.Serialization;
namespace com.clusterrr.TuyaNet
{
+ /// <summary>
+ /// Device info received from local network.
+ /// </summary>
public class TuyaDeviceScanInfo : IEquatable<TuyaDeviceScanInfo>
{
[JsonPropertyName("ip")]
diff --git a/TuyaDeviceStatus.cs b/TuyaDeviceStatus.cs
index c25d0bd..d772afa 100644
--- a/TuyaDeviceStatus.cs
+++ b/TuyaDeviceStatus.cs
@@ -2,11 +2,20 @@
namespace com.clusterrr.TuyaNet
{
+ /// <summary>
+ /// Currect device status.
+ /// </summary>
public class TuyaDeviceStatus
{
+ /// <summary>
+ /// DPS number
+ /// </summary>
[JsonPropertyName("code")]
public string Code { get; set; }
+ /// <summary>
+ /// DPS value.
+ /// </summary>
[JsonPropertyName("value")]
public object Value { get; set; }
}
diff --git a/TuyaLocalResponse.cs b/TuyaLocalResponse.cs
index 6d6f94e..56e0b17 100644
--- a/TuyaLocalResponse.cs
+++ b/TuyaLocalResponse.cs
@@ -1,15 +1,21 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace com.clusterrr.TuyaNet
+namespace com.clusterrr.TuyaNet
{
+ /// <summary>
+ /// Response from local Tuya device.
+ /// </summary>
public class TuyaLocalResponse
{
+ /// <summary>
+ /// Command code.
+ /// </summary>
public TuyaCommand Command { get; }
+ /// <summary>
+ /// Return code.
+ /// </summary>
public int ReturnCode { get; }
+ /// <summary>
+ /// Response as JSON string.
+ /// </summary>
public string JSON { get; }
internal TuyaLocalResponse(TuyaCommand command, int returnCode, string json)
diff --git a/TuyaNet.csproj b/TuyaNet.csproj
index 02e8f85..590da8b 100644
--- a/TuyaNet.csproj
+++ b/TuyaNet.csproj
@@ -3,6 +3,13 @@
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>com.clusterrr.TuyaNet</RootNamespace>
+ <Authors>Alexey "Cluster" Avdyukhin</Authors>
+ <Description>.NET library to interface with Tuya WiFi smart devices.</Description>
+ <Copyright>(c) Cluster, 2021</Copyright>
+ <PackageProjectUrl>https://github.com/ClusterM/tuyanet</PackageProjectUrl>
+ <RepositoryUrl>https://github.com/ClusterM/tuyanet.git</RepositoryUrl>
+ <RepositoryType>GIT</RepositoryType>
+ <PackageId>TuyaNet</PackageId>
</PropertyGroup>
</Project>
diff --git a/TuyaParser.cs b/TuyaParser.cs
index 708ec23..8930454 100644
--- a/TuyaParser.cs
+++ b/TuyaParser.cs
@@ -4,10 +4,12 @@ using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
-using System.Threading.Tasks;
namespace com.clusterrr.TuyaNet
{
+ /// <summary>
+ /// Class to encode and decode data sent over local network.
+ /// </summary>
internal static class TuyaParser
{
private static byte[] PROTOCOL_VERSION_BYTES_31 = Encoding.ASCII.GetBytes("3.1");
diff --git a/TuyaProtocolVersion.cs b/TuyaProtocolVersion.cs
index 156f629..d514965 100644
--- a/TuyaProtocolVersion.cs
+++ b/TuyaProtocolVersion.cs
@@ -1,8 +1,17 @@
namespace com.clusterrr.TuyaNet
{
+ /// <summary>
+ /// Tuya protocol version.
+ /// </summary>
public enum TuyaProtocolVersion
{
+ /// <summary>
+ /// Version 3.1.
+ /// </summary>
V31,
+ /// <summary>
+ /// Version 3.3.
+ /// </summary>
V33
}
}
diff --git a/TuyaScanner.cs b/TuyaScanner.cs
index 67cbdc2..7e2e1d2 100644
--- a/TuyaScanner.cs
+++ b/TuyaScanner.cs
@@ -1,16 +1,17 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading;
-using System.Threading.Tasks;
namespace com.clusterrr.TuyaNet
{
+ /// <summary>
+ /// Scanner to discover devices over local network.
+ /// </summary>
public class TuyaScanner
{
private const ushort UDP_PORT31 = 6666; // Tuya 3.1 UDP Port
@@ -24,9 +25,23 @@ namespace com.clusterrr.TuyaNet
private Thread udpListener33 = null;
private List<TuyaDeviceScanInfo> devices = new List<TuyaDeviceScanInfo>();
+ /// <summary>
+ /// Even that will be called on every broadcast message from devices.
+ /// </summary>
public event EventHandler<TuyaDeviceScanInfo> OnDeviceInfoReceived;
+ /// <summary>
+ /// Even that will be called only once for every device.
+ /// </summary>
public event EventHandler<TuyaDeviceScanInfo> OnNewDeviceInfoReceived;
+ /// <summary>
+ /// Creates a new instance of the TuyaScanner class.
+ /// </summary>
+ public TuyaScanner() { }
+
+ /// <summary>
+ /// Starts scanner.
+ /// </summary>
public void Start()
{
Stop();
@@ -40,6 +55,9 @@ namespace com.clusterrr.TuyaNet
udpListener33.Start(udpServer33);
}
+ /// <summary>
+ /// Stops scanner.
+ /// </summary>
public void Stop()
{
running = false;