HarmonyTuya 0.1.0-alpha.3

This is a prerelease version of HarmonyTuya.
dotnet add package HarmonyTuya --version 0.1.0-alpha.3
                    
NuGet\Install-Package HarmonyTuya -Version 0.1.0-alpha.3
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="HarmonyTuya" Version="0.1.0-alpha.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="HarmonyTuya" Version="0.1.0-alpha.3" />
                    
Directory.Packages.props
<PackageReference Include="HarmonyTuya" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add HarmonyTuya --version 0.1.0-alpha.3
                    
#r "nuget: HarmonyTuya, 0.1.0-alpha.3"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package HarmonyTuya@0.1.0-alpha.3
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=HarmonyTuya&version=0.1.0-alpha.3&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=HarmonyTuya&version=0.1.0-alpha.3&prerelease
                    
Install as a Cake Tool

HarmonyTuya

Lightweight, extensible .NET client for the Tuya Cloud API.

Install

HarmonyTuya is published as a NuGet package.

  • Package ID: HarmonyTuya
  • Targets: net8.0, net6.0

Example install command:

dotnet add package HarmonyTuya

Quick start

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp", // optional, per-tenant
    Region = "EU", // EU, US, CN, IN. Or set BaseUrl directly.
    UseRegionPathPrefix = true // set if your tenant requires region in path
};

using ITuyaClient client = new TuyaClient(options);
var state = await client.GetDeviceStateAsync("vdevo16*********15");
Console.WriteLine($"Frozen: {state.IsFrozen} (state={state.State})");

The client authenticates automatically (fetches and caches an access token) and signs each request.

Note: The client uses a default Tuya V2 HMAC-SHA256 request signer (TuyaV2RequestSigner). If you need custom signing rules, inject your own ITuyaRequestSigner. Keep your AccessSecret safe—use environment variables or a secret store.

Supported frameworks

  • .NET 8.0
  • .NET 6.0

Changelog

See CHANGELOG.md for release notes and version history.

Running tests

This repo includes a small xUnit test suite that validates authentication flow and the serialization/deserialization of selected endpoint models.

  • Prerequisites: .NET SDK 8.0 or newer installed locally.
  • Run tests from the repository root with your preferred tooling (for example, via your IDE's Test Explorer or dotnet test).

Notes:

  • The library targets .NET 6.0 and .NET 8.0. The test project currently targets .NET 8.0.
  • In CI, tests can run as part of your build pipeline before packaging/publishing.

Authentication

HarmonyTuya authenticates automatically using Tuya OAuth "simple mode" tokens.

  • On the first API call, the client fetches an access token from v1.0/token?grant_type=1 (request is signed with your AccessKey/AccessSecret).
  • The token is cached in-memory and refreshed just before expiry. You don't need to call anything manually.
  • Every request still uses HMAC signing and also includes the access_token header as required by Tuya Cloud.

Configuration notes:

  • Set AccessKey and AccessSecret in TuyaClientOptions.
  • Optional: ProjectCode if your tenant requires it; Region or BaseUrl for your Tuya environment.
  • Optional: UseRegionPathPrefix = true to include a region segment like EU/ in the path when required by your setup.

Security: never commit real secrets. Use environment variables, Secret Manager, or your CI/CD secret store.

Configuration (TuyaClientOptions)

These options control how the client connects and authenticates:

  • AccessKey: Tuya Cloud API Client ID
  • AccessSecret: Tuya Cloud API Client Secret
  • Region: Region code like EU, US, CN, IN (used for base URL mapping)
  • BaseUrl: Optional. Override full base URL (skips Region mapping)
  • HttpClient: Optional. Provide a custom HttpClient you manage
  • UseRegionPathPrefix: When true, includes the region (e.g., EU/) as the first path segment
  • ProjectCode: Optional. Required by some Tuya tenants; sent as project_code header

Client and interface split by category

To keep things maintainable, the monolithic client and interface were split into partials organized by domain. Public API remains the same; only the file layout changed.

  • Client implementation (HarmonyTuya.Tuya):

    • TuyaClient.cs — core shell (constructor, options, BaseUrl, Dispose)
    • TuyaClient.DeviceState.cs — device state
    • TuyaClient.DeviceLogs.cs — device report logs (v2.0/v2.1)
    • TuyaClient.DeviceCore.cs — device CRUD, action, rename/transfer/freeze/reset
    • TuyaClient.DeviceShadow.cs — shadow and desired properties
    • TuyaClient.DeviceSpecs.cs — model, specification, functions, categories, latest status
    • TuyaClient.FirmwareAndLogs.cs — firmware progress/info and event logs
    • TuyaClient.Groups.cs — groups management and properties
    • TuyaClient.Spaces.cs — spaces and project devices
    • TuyaClient.ScenesAndActivation.cs — scenes (linkage rules), QR activation, signal
  • Interface (HarmonyTuya.Tuya.Abstractions):

    • ITuyaClient.cs — partial interface shell
    • ITuyaClient.DeviceState.cs, ITuyaClient.DeviceLogs.cs
    • ITuyaClient.DeviceCore.cs, ITuyaClient.DeviceShadow.cs, ITuyaClient.DeviceSpecs.cs
    • ITuyaClient.Firmware.cs, ITuyaClient.Groups.cs, ITuyaClient.Spaces.cs
    • ITuyaClient.ScenesAndActivation.cs

Tip: Navigate by category to find related methods fast. Examples below continue to work unchanged.

Error handling

  • Non-success responses produce HttpRequestException, or InvalidOperationException when the API response is unexpectedly empty.
  • Methods are async and respect CancellationToken where available.
  • Authentication and retry: the client retries once automatically on 401 Unauthorized (likely expired access token). It invalidates the token, refreshes it, and re-sends the request. Other transient retry policies are not enabled by default.

Report logs example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);
// Note: TuyaClient uses a built-in TuyaV2RequestSigner by default which computes HMAC-SHA256 signatures.
// If your project uses different signing rules, inject a custom ITuyaRequestSigner into the TuyaClient constructor.

// SECURITY: Keep AccessSecret safe. Do NOT commit real secrets to source control. Use environment variables or a secret store in production.

var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
var hourAgo = now - 60_000 * 60;

var logs = await client.GetDeviceReportLogsAsync(
    deviceId: "vdevo16*********15",
    codes: "switch_led_1",
    startTime: hourAgo,
    endTime: now,
    size: 20,
    lastRowKey: null
);

Console.WriteLine($"DeviceId: {logs.DeviceId}, Total: {logs.Total}, HasMore: {logs.HasMore}, LastRowKey: {logs.LastRowKey}");
foreach (var item in logs.Logs)
{
    Console.WriteLine($"{item.EventTime}: {item.Code} -> {item.Value}");
}
using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
var hourAgo = now - 60_000 * 60;

// First page
var v21 = await client.GetDeviceReportLogsV21Async(
    deviceId: "vdevo16*********15",
    codes: "switch_led_1",
    startTime: hourAgo,
    endTime: now,
    size: 20
);

Console.WriteLine($"DeviceId: {v21.DeviceId}, Total: {v21.Total}, HasMore: {v21.HasMore}, LastRowKey: {v21.LastRowKey}");
foreach (var item in v21.Logs)
{
    Console.WriteLine($"{item.EventTime}: {item.Code} -> {item.Value}");
}

// Next page (when HasMore is true):
// var next = await client.GetDeviceReportLogsV21Async(
//     deviceId: "vdevo16*********15",
//     codes: "switch_led_1",
//     startTime: hourAgo,
//     endTime: now,
//     size: 20,
//     lastRowKey: v21.LastRowKey);

Get group details example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var group = await client.GetGroupAsync("1285***7");
Console.WriteLine($"Group {group.Id}: {group.Name} (type={group.Type}, pv={group.Pv})");

Add devices to group example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;
using HarmonyTuya.Tuya.Models;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var request = new AddDevicesToGroupRequest
{
    SpaceId = "2512****",
    DeviceIds = "vdevo163815*****,529739391******"
};

var ok = await client.AddDevicesToGroupAsync("12856***", request);
Console.WriteLine($"Add devices result: {ok}");

Query devices in a group

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var devices = await client.GetGroupDevicesAsync("12856***", pageSize: 1, pageNo: 1);
if (devices != null)
{
    Console.WriteLine($"Total: {devices.Count}, Page: {devices.PageNumber}/{devices.PageSize}");
    foreach (var d in devices.DataList)
    {
        Console.WriteLine($"Dev: {d.DevId} Status: {d.Status}");
    }
}

Delete devices from group example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;
using HarmonyTuya.Tuya.Models;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var delReq = new DeleteDevicesFromGroupRequest
{
    DeviceIds = "vdevo16381*****,5529739***"
};

var removed = await client.DeleteDevicesFromGroupAsync("12856***", delReq);
Console.WriteLine($"Delete devices result: {removed}");

Get groups by device example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var groups = await client.GetGroupsByDeviceAsync("vdevo16*********15");
foreach (var g in groups)
{
    Console.WriteLine($"Group {g.Id}: {g.Name} (product={g.ProductId})");
}

Get group properties example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var props = await client.GetGroupPropertiesAsync("12856***");
foreach (var p in props.Properties)
{
    Console.WriteLine($"{p.Code} (group={p.GId}): {p.Value}");
}

Delete group example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var deleted = await client.DeleteGroupAsync("1285*****");
Console.WriteLine($"Delete group result: {deleted}");

Rename group example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var renamed = await client.RenameGroupAsync("123***2", "gro*****ame");
Console.WriteLine($"Rename group result: {renamed}");

Create group example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;
using HarmonyTuya.Tuya.Models;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var createReq = new CreateGroupRequest
{
    SpaceId = "25129******",
    Name = "Test Group**",
    ProductId = "dsadhsjkh2***",
    DeviceIds = "dshajhdkhaj***,hsdkajhkk***"
};

var created = await client.CreateGroupAsync(createReq);
Console.WriteLine($"Created group {created.Id} (name={created.Name}, pv={created.Pv})");

List groups in a space example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var result = await client.GetGroupsInSpaceAsync(spaceId: "158170***", pageSize: 10, pageNo: 1);
if (result != null)
{
    Console.WriteLine($"Total: {result.Count}, Page: {result.PageNumber}, Size: {result.PageSize}");
    foreach (var g in result.DataList)
    {
        Console.WriteLine($"Group {g.Id}: {g.Name} (pv={g.Pv}, product={g.ProductId})");
    }
}

Send device action example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;
using HarmonyTuya.Tuya.Models;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var action = new DeviceActionRequest
{
    Code = "action",
    InputParams = "{\"color\":\"red\",\"name\":\"text\"}"
};

var ok = await client.SendDeviceActionAsync("2132131*****", action);
Console.WriteLine($"Send device action result: {ok}");

Get device shadow properties example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

// Without codes filter
var allProps = await client.GetDeviceShadowPropertiesAsync("372817193*****");
if (allProps != null)
{
    foreach (var p in allProps.Properties)
    {
        Console.WriteLine($"{p.Code} (dp:{p.DpId}) at {p.Time} = {p.Value}");
    }
}

// With codes filter
var filtered = await client.GetDeviceShadowPropertiesAsync("372817193*****", codes: "switch_led,brightness");
if (filtered != null)
{
    foreach (var p in filtered.Properties)
    {
        Console.WriteLine($"Filtered {p.Code}: {p.Value}");
    }
}

Set device shadow properties example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;
using HarmonyTuya.Tuya.Models;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var setReq = new DeviceShadowPropertiesSetRequest
{
    Properties =
    {
        new DeviceShadowSetPropertyItem { Code = "switch_led", CustomName = "switch_led1" }
    }
};

var updated = await client.SetDeviceShadowPropertiesAsync("372817193*****", setReq);
Console.WriteLine($"Set device shadow properties: {updated}");

Get desired device properties example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var desired = await client.GetDeviceDesiredPropertiesAsync("372817193*****");
if (desired != null)
{
    foreach (var p in desired.Properties)
    {
        Console.WriteLine($"Desired {p.Code} (dp:{p.DpId}) value={p.Value} status={p.Status} exp={p.ExpireTime}");
    }
}

Set desired device properties example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;
using HarmonyTuya.Tuya.Models;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var desiredSet = new DeviceDesiredPropertiesSetRequest
{
    Properties = "{\"switch_1\":true}",
    Duration = 1000
};

var okDesired = await client.SetDeviceDesiredPropertiesAsync("vdevo1638******", desiredSet);
Console.WriteLine($"Set desired device properties result: {okDesired}");

Get device model example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var model = await client.GetDeviceModelAsync("372817193*****");
Console.WriteLine(model?.Model);

Get devices in batch example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var list = await client.GetDevicesBatchAsync("vdevo165942257398***,vdevo165abcd98***");
foreach (var d in list)
{
    Console.WriteLine($"{d.Id} Online={d.IsOnline} Name={d.Name} Product={d.ProductName}");
}

Get firmware update progress example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var progress = await client.GetFirmwareUpdateProgressAsync("aab21***", channel: 0);
if (progress != null)
{
    Console.WriteLine($"Progress: {progress.Progress}%, Status: {progress.UpgradeStatus}");
}

Start firmware update example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var started = await client.StartFirmwareUpdateAsync("vdev1243432***", channel: 0);
Console.WriteLine($"Start firmware update: {started}");

Get firmware update info example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var info = await client.GetFirmwareUpdateInfoAsync("vdev1243432***");
Console.WriteLine($"Status={info?.UpgradeStatus}, Current={info?.CurrentVersion}, Target={info?.Version}, Desc={info?.Desc}");

Get single device details example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var device = await client.GetDeviceAsync("vdevo165abcd98***");
Console.WriteLine($"{device?.Id} Online={device?.IsOnline} Name={device?.Name} Product={device?.ProductName}");

Get device event logs example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
var dayAgo = now - 24L*60*60*1000;

var logs = await client.GetDeviceEventLogsAsync(
    deviceId: "vdevo167115422036***",
    type: "1,2,3,4,5,6,7,8,9,10",
    startTime: dayAgo,
    endTime: now,
    queryType: 1,
    size: 20,
    codes: "switch_led_1");

if (logs != null)
{
    Console.WriteLine($"HasNext: {logs.HasNext}, CurrentRowKey: {logs.CurrentRowKey}");
    foreach (var ev in logs.Logs)
    {
        Console.WriteLine($"{ev.EventTime} {ev.Code} from {ev.EventFrom} id={ev.EventId} status={ev.Status} value={ev.Value}");
    }
}

Modify device attribute example (rename)

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;
using HarmonyTuya.Tuya.Models;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var renameReq = new ModifyDeviceAttributeRequest { Type = 1, Data = "Main Bedroom Light" };
var renamed = await client.ModifyDeviceAttributeAsync("6c19224514c02f7490****", renameReq);
Console.WriteLine($"Rename device result: {renamed}");

Freeze or unfreeze device example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;
using HarmonyTuya.Tuya.Models;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

// Freeze
var freezeReq = new DeviceFreezeRequest { State = 1 };
var froze = await client.SetDeviceFreezeStateAsync("6c19224514c02f7490****", freezeReq);
Console.WriteLine($"Freeze device result: {froze}");

// Unfreeze
var unfreezeReq = new DeviceFreezeRequest { State = 0 };
var unfroze = await client.SetDeviceFreezeStateAsync("6c19224514c02f7490****", unfreezeReq);
Console.WriteLine($"Unfreeze device result: {unfroze}");

Transfer device to another space example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;
using HarmonyTuya.Tuya.Models;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var transferReq = new DeviceTransferRequest { SpaceId = "12345***" };
var transferred = await client.TransferDeviceAsync("vdevo16*********15", transferReq);
Console.WriteLine($"Transfer device result: {transferred}");

Reset (factory default) device example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var reset = await client.ResetDeviceAsync("6c19224514c02f7490****");
Console.WriteLine($"Reset device result: {reset}");

Send group properties example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;
using HarmonyTuya.Tuya.Models;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var propsReq = new SendGroupPropertiesRequest
{
    GroupId = "89218****",
    Properties = "{ \"switch1\": true }"
};

var sent = await client.SendGroupPropertiesAsync(propsReq);
Console.WriteLine($"Send group properties result: {sent}");

Delete device example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var deleted = await client.DeleteDeviceAsync("vdevo16*********15");
Console.WriteLine($"Delete device result: {deleted}");

Query devices in spaces (paged) example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

// First page
var devices = await client.GetSpaceDevicesAsync(spaceIds: "146323341***", pageSize: 20, isRecursion: true);
foreach (var d in devices)
{
    Console.WriteLine($"{d.Id} Online={d.IsOnline} Name={d.Name} Product={d.ProductName} Cat={d.Category}");
}

// For the next page, pass lastId as the last device id you received previously
// var nextPage = await client.GetSpaceDevicesAsync("146323341***", pageSize: 20, isRecursion: true, lastId: devices.LastOrDefault()?.Id);

Query project devices (paged) example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var page = await client.GetProjectDevicesAsync(pageSize: 20, productIds: "h9sabcugftb***");
foreach (var d in page)
{
    Console.WriteLine($"{d.Id} Online={d.IsOnline} Name={d.Name} Product={d.ProductName} Cat={d.Category}");
}

// Next page example:
// var next = await client.GetProjectDevicesAsync(20, productIds: "h9sabcugftb***", lastId: page.LastOrDefault()?.Id);

Activate device with QR code example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;
using HarmonyTuya.Tuya.Models;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var req = new QrCodeActivateRequest
{
    QrCode = "https://m.smart321.com/********",
    SpaceId = 1500_1234,
    TimeZoneId = "Asia/Shanghai"
};

var result = await client.ActivateDeviceWithQrCodeAsync(req);
Console.WriteLine($"Token={result?.Token} DeviceId={result?.DeviceId}");

Query QR activation status example

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var status = await client.GetQrCodeActivationStatusAsync(token: "94IB****");
if (status != null)
{
    foreach (var s in status.SuccessDevices)
    {
        Console.WriteLine($"PAIRED: {s.Id} {s.Name} Online={s.Online} Product={s.ProductId}");
    }
    foreach (var e in status.ErrorDevices)
    {
        Console.WriteLine($"FAILED: {e.Id} {e.Name} Code={e.ErrorCode} Msg={e.ErrorMsg}");
    }
}

Issue signal detection to a device

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;
using HarmonyTuya.Tuya.Models;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var issueReq = new SignalDetectionIssueRequest
{
    DeviceId = "vdevo163815529739***",
    DeviceType = "WiFi" // or "zigbee"
};

var issued = await client.IssueSignalDetectionAsync(issueReq);
Console.WriteLine($"Signal detection issued: {issued}");

Query device signal (Wi-Fi/Zigbee)

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

// Wi-Fi (last 10 minutes)
var wifi = await client.GetDeviceSignalAsync("vdevo163815529739***", deviceType: "WiFi");
Console.WriteLine($"Wi-Fi RSSI={wifi?.Signal} level={wifi?.SignalLevel} type={wifi?.IndicatorType} at {wifi?.EventTime}");

// Zigbee (last 2 minutes)
var zigbee = await client.GetDeviceSignalAsync("vdevo163815529739***", deviceType: "zigbee");
Console.WriteLine($"Zigbee {zigbee?.IndicatorType}={zigbee?.Signal} level={zigbee?.SignalLevel} at {zigbee?.EventTime}");

Delete a space (and subspaces)

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var removed = await client.DeleteSpaceAsync("70735***");
Console.WriteLine($"Delete space result: {removed}");

Query resources in a space (paged)

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

// First page
var res = await client.GetSpaceResourcesAsync(spaceId: "15000***", onlySub: true, lastRowKey: 0, pageSize: 100);
if (res != null)
{
    foreach (var r in res.Data)
    {
        Console.WriteLine($"ResId={r.ResId} Type={r.ResType}");
    }
    Console.WriteLine($"Next cursor: {res.LastRowKey} PageSize: {res.PageSize}");
}

// Next page example:
// var next = await client.GetSpaceResourcesAsync("15000***", onlySub: true, lastRowKey: res?.LastRowKey, pageSize: 100);

Create a space (optional parent)

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;
using HarmonyTuya.Tuya.Models;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var createReq = new CreateSpaceRequest
{
    Name = "Test Space",
    ParentId = 150000002,
    Description = "Description of the space"
};

var newSpaceId = await client.CreateSpaceAsync(createReq);
Console.WriteLine($"Created space id: {newSpaceId}");

Get space info by ID

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var info = await client.GetSpaceAsync("1500****");
Console.WriteLine($"Space {info?.Id}: {info?.Name} (parent={info?.ParentId}, root={info?.RootId})");

Query child spaces (root or by space)

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

// Root-level spaces
var root = await client.GetSpaceChildrenAsync(pageSize: 100);
Console.WriteLine($"Root next cursor: {root?.LastRowKey} size={root?.PageSize}");
foreach (var id in root?.Data ?? [])
{
    Console.WriteLine($"Space ID: {id}");
}

// Children under a specific space (direct children only)
var childs = await client.GetSpaceChildrenAsync(spaceId: "15000002", onlySub: true, pageSize: 100);
Console.WriteLine($"Next cursor: {childs?.LastRowKey}");
foreach (var id in childs?.Data ?? [])
{
    Console.WriteLine($"Child Space ID: {id}");
}

Update a space (name/description)

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;
using HarmonyTuya.Tuya.Models;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var upd = new UpdateSpaceRequest
{
    Name = "test space",
    Description = "This is a test space."
};

var ok = await client.UpdateSpaceAsync("7073****", upd);
Console.WriteLine($"Update space result: {ok}");

Check parent-child relation between spaces

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var isChild = await client.IsChildSpaceAsync(parentId: "15000001", childId: "15000002");
Console.WriteLine($"Is child: {isChild}");

Get linkage (scene) rule details

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var rule = await client.GetSceneRuleAsync("***");
Console.WriteLine($"Rule {rule?.Id}: {rule?.Name} type={rule?.Type} status={rule?.Status} mode={rule?.RunningMode}");
foreach (var c in rule?.Conditions ?? [])
{
    Console.WriteLine($"COND[{c.Code}] {c.EntityType} on {c.EntityId} {c.Expr?.Comparator} {c.Expr?.StatusCode} = {c.Expr?.StatusValue}");
}
foreach (var a in rule?.Actions ?? [])
{
    Console.WriteLine($"ACT {a.ActionExecutor} on {a.EntityId} code={a.ExecutorProperty?.FunctionCode} value={a.ExecutorProperty?.FunctionValue} delay={a.ExecutorProperty?.DelaySeconds}");
}

List linkage rules in a space (paged)

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var list = await client.GetSceneRulesAsync(spaceId: "150***", pageSize: 2, pageNo: 1, type: "automation");
Console.WriteLine($"Total: {list?.Total} HasMore: {list?.HasMore}");
foreach (var r in list?.List ?? [])
{
    Console.WriteLine($"Rule {r.Id}: {r.Name} type={r.Type} status={r.Status} mode={r.RunningMode}");
}

Delete multiple linkage rules

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var deleted = await client.DeleteSceneRulesAsync(spaceId: "53IYS**", ids: "abc123,def456");
Console.WriteLine($"Delete rules result: {deleted}");

Trigger a tap-to-run scene by rule ID

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "etjgr3pru3pt4m55m8vn",
    AccessSecret = "<REDACTED_CLIENT_SECRET>",
    ProjectCode = "p17579564043307sgqxp",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var triggered = await client.TriggerSceneRuleAsync(ruleId: "***");
Console.WriteLine($"Triggered: {triggered}");

Modify a linkage rule

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;
using HarmonyTuya.Tuya.Models;

var options = new TuyaClientOptions
{
    AccessKey = "<KEY>",
    AccessSecret = "<SECRET>",
    ProjectCode = "<PROJECT_CODE>",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var update = new UpdateSceneRuleRequest
{
    SpaceId = "150***",
    Name = "Test Scene",
    DecisionExpr = "or",
    EffectiveTime = new SceneRuleEffectiveTime
    {
        Start = "00:00",
        End = "23:00",
        Loops = "1111111",
        TimeZoneId = "Asia/Shanghai"
    },
    Conditions = new()
    {
        new SceneRuleCondition
        {
            Code = 1,
            EntityId = "****",
            EntityType = "device_report",
            Expr = new SceneRuleConditionExpr
            {
                StatusCode = "switch_1",
                Comparator = "==",
                StatusValue = true
            }
        }
    },
    Actions = new()
    {
        new SceneRuleAction
        {
            EntityId = "****",
            ActionExecutor = "device_issue",
            ExecutorProperty = new SceneRuleExecutorProperty
            {
                FunctionCode = "switch_1",
                FunctionValue = true
            }
        }
    }
};

var ok = await client.UpdateSceneRuleAsync(ruleId: "***", update);
Console.WriteLine($"Updated: {ok}");

Enable or disable automation rules

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "<KEY>",
    AccessSecret = "<SECRET>",
    ProjectCode = "<PROJECT_CODE>",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

// Enable two rules by IDs, optionally scoped to a space
var enabled = await client.SetSceneRuleStateAsync(ids: "abc1,abc2", isEnable: true, spaceId: "53IYS**");
Console.WriteLine($"Enabled: {enabled}");

// Disable the same rules
var disabled = await client.SetSceneRuleStateAsync(ids: "abc1,abc2", isEnable: false, spaceId: "53IYS**");
Console.WriteLine($"Disabled: {disabled}");

Create a linkage rule

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;
using HarmonyTuya.Tuya.Models;

var options = new TuyaClientOptions
{
    AccessKey = "<KEY>",
    AccessSecret = "<SECRET>",
    ProjectCode = "<PROJECT_CODE>",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var create = new CreateSceneRuleRequest
{
    SpaceId = "150***",
    Name = "Test Scene",
    Type = "automation", // or "scene"
    DecisionExpr = "or",
    EffectiveTime = new SceneRuleEffectiveTime
    {
        Start = "00:00",
        End = "23:00",
        Loops = "1111111",
        TimeZoneId = "Asia/Shanghai"
    },
    Conditions = new()
    {
        new SceneRuleCondition
        {
            Code = 1,
            EntityId = "****",
            EntityType = "device_report",
            Expr = new SceneRuleConditionExpr
            {
                StatusCode = "switch_1",
                Comparator = "==",
                StatusValue = true
            }
        }
    },
    Actions = new()
    {
        new SceneRuleAction
        {
            EntityId = "****",
            ActionExecutor = "device_issue",
            ExecutorProperty = new SceneRuleExecutorProperty
            {
                FunctionCode = "switch_1",
                FunctionValue = true
            }
        }
    }
};

var newRuleId = await client.CreateSceneRuleAsync(create);
Console.WriteLine($"Created rule id: {newRuleId}");

Get device specification (functions and status)

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "<KEY>",
    AccessSecret = "<SECRET>",
    ProjectCode = "<PROJECT_CODE>",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var spec = await client.GetDeviceSpecificationAsync(deviceId: "xxxid");
Console.WriteLine($"Category: {spec?.Category}");
foreach (var f in spec?.Functions ?? Array.Empty<DeviceFunctionItem>())
{
    Console.WriteLine($"Function: {f.Code} ({f.Type}) values={f.Values}");
}
foreach (var s in spec?.Status ?? Array.Empty<DeviceStatusItem>())
{
    Console.WriteLine($"Status: {s.Code} ({s.Type}) values={s.Values}");
}

Get device functions (instruction set)

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "<KEY>",
    AccessSecret = "<SECRET>",
    ProjectCode = "<PROJECT_CODE>",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var funcs = await client.GetDeviceFunctionsAsync(deviceId: "0123xxxxxx");
Console.WriteLine($"Category: {funcs?.Category}");
foreach (var f in funcs?.Functions ?? Array.Empty<DeviceFunctionItem>())
{
    Console.WriteLine($"Function: {f.Code} ({f.Type}) desc={f.Desc} values={f.Values}");
}

Get category functions (instruction set)

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "<KEY>",
    AccessSecret = "<SECRET>",
    ProjectCode = "<PROJECT_CODE>",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var catFuncs = await client.GetCategoryFunctionsAsync(category: "kg");
Console.WriteLine($"Category: {catFuncs?.Category}");
foreach (var f in catFuncs?.Functions ?? Array.Empty<DeviceFunctionItem>())
{
    Console.WriteLine($"Function: {f.Code} ({f.Type}) desc={f.Desc} values={f.Values}");
}

Send device commands

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;
using HarmonyTuya.Tuya.Models;

var options = new TuyaClientOptions
{
    AccessKey = "<KEY>",
    AccessSecret = "<SECRET>",
    ProjectCode = "<PROJECT_CODE>",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var cmd = new DeviceCommandsRequest
{
    Commands = new()
    {
        new DeviceCommandItem { Code = "switch_led", Value = true }
    }
};

var ok = await client.SendDeviceCommandsAsync(deviceId: "xxxid", cmd);
Console.WriteLine($"Command accepted: {ok}");

List device categories

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "<KEY>",
    AccessSecret = "<SECRET>",
    ProjectCode = "<PROJECT_CODE>",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var categories = await client.GetDeviceCategoriesAsync();
foreach (var c in categories)
{
    Console.WriteLine($"{c.Code}: {c.Name}");
}

Get latest device status

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "<KEY>",
    AccessSecret = "<SECRET>",
    ProjectCode = "<PROJECT_CODE>",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var latest = await client.GetDeviceLatestStatusAsync(deviceId: "xxxxId");
foreach (var s in latest)
{
    Console.WriteLine($"{s.Code}: {s.Value}");
}

Get latest status for multiple devices

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "<KEY>",
    AccessSecret = "<SECRET>",
    ProjectCode = "<PROJECT_CODE>",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var batch = await client.GetDevicesLatestStatusAsync(deviceIds: "xxxid1,xxxxid2");
foreach (var item in batch)
{
    Console.WriteLine($"Device {item.Id}:");
    foreach (var s in item.Status)
    {
        Console.WriteLine($"  {s.Code}: {s.Value}");
    }
}

Get category status set

using HarmonyTuya.Tuya;
using HarmonyTuya.Tuya.Abstractions;

var options = new TuyaClientOptions
{
    AccessKey = "<KEY>",
    AccessSecret = "<SECRET>",
    ProjectCode = "<PROJECT_CODE>",
    Region = "EU",
    UseRegionPathPrefix = true
};

using ITuyaClient client = new TuyaClient(options);

var catStatus = await client.GetCategoryStatusAsync("kg");
Console.WriteLine($"Category: {catStatus?.Category}");
foreach (var s in catStatus?.Statuses ?? new List<DeviceStatusItem>())
{
    Console.WriteLine($"Status: {s.Code} ({s.Type}) values={s.Values}");
}

Roadmap

  • Token auto-refresh (done) and add one-time retry on token-expired responses
  • Concurrency guard for token refresh under parallel load
  • More device/project/group endpoints as needed by users
  • Error handling improvements and domain-specific exceptions
  • Optional transient retry strategy (e.g., Polly)

Publishing to NuGet (CI/CD)

This repo includes a GitHub Actions workflow that builds, packs, and publishes the package to nuget.org when you push a tag.

  • File: .github/workflows/publish-nuget.yml
  • Trigger: pushing a tag like v1.2.3 or v1.2.3-beta.1
  • Version: derived from the tag name (the leading v is stripped)

Before first run:

  • Create a NuGet API key in your nuget.org account
  • Add it to the repository secrets as NUGET_API_KEY

Release:

  • Create and push a tag, e.g. v0.1.0-alpha.2
  • The workflow will pack the project for net8.0 and net6.0 and publish to nuget.org (skipping duplicates)
Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net6.0

    • No dependencies.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0-alpha.3 243 9/17/2025
0.1.0-alpha.2 252 9/16/2025