fix: 修改部分外设BASE偏移量;增加WS2812后端监控器;DSO寄
This commit is contained in:
parent
ca0322137b
commit
c8444d1d4e
|
@ -0,0 +1,138 @@
|
|||
using Microsoft.AspNetCore.Authorization;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using TypedSignalR.Client;
|
||||
using Tapper;
|
||||
using DotNext;
|
||||
using Peripherals.WS2812Client;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
#pragma warning disable 1998
|
||||
|
||||
namespace server.Hubs;
|
||||
|
||||
[Hub]
|
||||
public interface IWS2812Hub
|
||||
{
|
||||
Task<RGBColor[]?> GetAllLedColors();
|
||||
Task<RGBColor?> GetLedColor(int ledIndex);
|
||||
}
|
||||
|
||||
[Receiver]
|
||||
public interface IWS2812Receiver
|
||||
{
|
||||
Task OnReceive(RGBColor[] data);
|
||||
}
|
||||
|
||||
[TranspilationSource]
|
||||
public class WS2812TaskStatus
|
||||
{
|
||||
public bool IsRunning { get; set; } = false;
|
||||
}
|
||||
|
||||
class WS2812ScanTaskInfo
|
||||
{
|
||||
public string BoardID { get; set; }
|
||||
public string ClientID { get; set; }
|
||||
public Task? ScanTask { get; set; }
|
||||
public WS2812Client LedClient { get; set; }
|
||||
public CancellationTokenSource CTS { get; set; } = new();
|
||||
public bool IsRunning { get; set; } = false;
|
||||
|
||||
public WS2812ScanTaskInfo(string boardID, string clientID, WS2812Client client)
|
||||
{
|
||||
BoardID = boardID;
|
||||
ClientID = clientID;
|
||||
LedClient = client;
|
||||
}
|
||||
|
||||
public WS2812TaskStatus ToWS2812TaskStatus()
|
||||
{
|
||||
return new WS2812TaskStatus
|
||||
{
|
||||
IsRunning = IsRunning
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[EnableCors("SignalR")]
|
||||
public class WS2812Hub : Hub<IWS2812Receiver>, IWS2812Hub
|
||||
{
|
||||
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
private readonly IHubContext<WS2812Hub, IWS2812Receiver> _hubContext;
|
||||
private readonly Database.UserManager _userManager = new();
|
||||
private ConcurrentDictionary<(string, string), WS2812ScanTaskInfo> _scanTasks = new();
|
||||
|
||||
public WS2812Hub(IHubContext<WS2812Hub, IWS2812Receiver> hubContext)
|
||||
{
|
||||
_hubContext = hubContext;
|
||||
}
|
||||
|
||||
private Optional<Database.Board> TryGetBoard()
|
||||
{
|
||||
var userName = Context.User?.FindFirstValue(ClaimTypes.Name);
|
||||
if (string.IsNullOrEmpty(userName))
|
||||
{
|
||||
logger.Error("User name is null or empty");
|
||||
return null;
|
||||
}
|
||||
var userRet = _userManager.GetUserByName(userName);
|
||||
if (!userRet.IsSuccessful || !userRet.Value.HasValue)
|
||||
{
|
||||
logger.Error($"User '{userName}' not found");
|
||||
return null;
|
||||
}
|
||||
var user = userRet.Value.Value;
|
||||
var boardRet = _userManager.GetBoardByID(user.BoardID);
|
||||
if (!boardRet.IsSuccessful || !boardRet.Value.HasValue)
|
||||
{
|
||||
logger.Error($"Board not found");
|
||||
return null;
|
||||
}
|
||||
return boardRet.Value.Value;
|
||||
}
|
||||
|
||||
public async Task<RGBColor[]?> GetAllLedColors()
|
||||
{
|
||||
try
|
||||
{
|
||||
var board = TryGetBoard().OrThrow(() => new Exception("Board not found"));
|
||||
var client = new WS2812Client(board.IpAddr, board.Port, 0);
|
||||
var result = await client.GetAllLedColors();
|
||||
if (!result.IsSuccessful)
|
||||
{
|
||||
logger.Error($"GetAllLedColors failed: {result.Error}");
|
||||
return null;
|
||||
}
|
||||
return result.Value;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error(ex, "Failed to get all LED colors");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RGBColor?> GetLedColor(int ledIndex)
|
||||
{
|
||||
try
|
||||
{
|
||||
var board = TryGetBoard().OrThrow(() => new Exception("Board not found"));
|
||||
var client = new WS2812Client(board.IpAddr, board.Port, 0);
|
||||
var result = await client.GetLedColor(ledIndex);
|
||||
if (!result.IsSuccessful)
|
||||
{
|
||||
logger.Error($"GetLedColor failed: {result.Error}");
|
||||
return null;
|
||||
}
|
||||
return result.Value;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error(ex, "Failed to get LED color");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,11 +10,12 @@ static class HdmiInAddr
|
|||
|
||||
public const UInt32 CAPTURE_RD_CTRL = BASE + 0x0;
|
||||
|
||||
public const UInt32 START_WR_ADDR0 = BASE + 0x2;
|
||||
public const UInt32 END_WR_ADDR0 = BASE + 0x3;
|
||||
public const UInt32 START_WR_ADDR0 = BASE + 0x20;
|
||||
public const UInt32 END_WR_ADDR0 = BASE + 0x21;
|
||||
|
||||
public const UInt32 HDMI_NOT_READY = BASE + 0x8;
|
||||
public const UInt32 HDMI_HEIGHT_WIDTH = BASE + 0x9;
|
||||
public const UInt32 HDMI_NOT_READY = BASE + 0x26;
|
||||
public const UInt32 HDMI_HEIGHT_WIDTH = BASE + 0x27;
|
||||
public const UInt32 CAPTURE_HEIGHT_WIDTH = BASE + 0x28;
|
||||
|
||||
public const UInt32 ADDR_HDMI_WD_START = 0x0400_0000;
|
||||
}
|
||||
|
@ -31,7 +32,7 @@ public class HdmiIn
|
|||
|
||||
public int Width { get; private set; }
|
||||
public int Height { get; private set; }
|
||||
public int FrameLength => Width * Height * 3 / 4;
|
||||
public int FrameLength => Width * Height / 2;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化HDMI输入客户端
|
||||
|
@ -129,7 +130,7 @@ public class HdmiIn
|
|||
this.taskID, // taskID
|
||||
HdmiInAddr.ADDR_HDMI_WD_START,
|
||||
FrameLength, // 使用当前分辨率的动态大小
|
||||
BurstType.FixedBurst,
|
||||
BurstType.ExtendBurst,
|
||||
this.timeout);
|
||||
|
||||
if (!result.IsSuccessful)
|
||||
|
@ -165,25 +166,22 @@ public class HdmiIn
|
|||
return Optional<(byte[] header, byte[] data, byte[] footer)>.None;
|
||||
}
|
||||
|
||||
var rgb24Data = frameResult.Value;
|
||||
var rgb565Data = frameResult.Value;
|
||||
|
||||
// 验证数据长度是否正确 (RGB24为每像素2字节)
|
||||
var expectedLength = Width * Height * 2;
|
||||
if (rgb24Data.Length != expectedLength)
|
||||
if (rgb565Data.Length != expectedLength)
|
||||
{
|
||||
logger.Warn("HDMI数据长度不匹配,期望: {Expected}, 实际: {Actual}",
|
||||
expectedLength, rgb24Data.Length);
|
||||
expectedLength, rgb565Data.Length);
|
||||
}
|
||||
|
||||
// 将RGB24转换为JPEG(参考Camera版本的处理)
|
||||
var jpegStartTime = DateTime.UtcNow;
|
||||
var jpegResult = Common.Image.ConvertRGB24ToJpeg(rgb24Data, Width, Height, 80);
|
||||
var jpegEndTime = DateTime.UtcNow;
|
||||
var jpegTime = (jpegEndTime - jpegStartTime).TotalMilliseconds;
|
||||
var jpegResult = Common.Image.ConvertRGB565ToJpeg(rgb565Data, Width, Height, 80, false);
|
||||
|
||||
if (!jpegResult.IsSuccessful)
|
||||
{
|
||||
logger.Error("HDMI RGB24转JPEG失败: {Error}", jpegResult.Error);
|
||||
logger.Error("HDMI RGB565转JPEG失败: {Error}", jpegResult.Error);
|
||||
return Optional<(byte[] header, byte[] data, byte[] footer)>.None;
|
||||
}
|
||||
|
||||
|
@ -224,8 +222,8 @@ public class HdmiIn
|
|||
return new(new Exception("Invalid HDMI resolution data length"));
|
||||
}
|
||||
|
||||
var width = data[3] | (data[2] << 8);
|
||||
var height = data[1] | (data[0] << 8);
|
||||
var width = (data[3] | (data[2] << 8)) - 1 - (((data[3] | (data[2] << 8)) - 1)%2);
|
||||
var height = (data[1] | (data[0] << 8)) - 1 - (((data[1] | (data[0] << 8)) - 1)%2);
|
||||
this.Width = width;
|
||||
this.Height = height;
|
||||
|
||||
|
@ -234,6 +232,7 @@ public class HdmiIn
|
|||
return new((width, height));
|
||||
}
|
||||
|
||||
|
||||
public async ValueTask<Result<bool>> ConnectJpeg2Hdmi(int width, int height)
|
||||
{
|
||||
if (width <= 0 || height <= 0)
|
||||
|
@ -242,7 +241,22 @@ public class HdmiIn
|
|||
return new(new ArgumentException("Invalid HDMI resolution"));
|
||||
}
|
||||
|
||||
var frameSize = (UInt32)(width * height);
|
||||
var frameSize = (UInt32)(width * height) / 2;
|
||||
|
||||
{
|
||||
var ret = await UDPClientPool.WriteAddr(
|
||||
this.ep, this.taskID, HdmiInAddr.CAPTURE_HEIGHT_WIDTH, (uint)((height << 16) + width), this.timeout);
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Failed to set CAPTURE_HEIGHT_WIDTH: {ret.Error}");
|
||||
return new(ret.Error);
|
||||
}
|
||||
if (!ret.Value)
|
||||
{
|
||||
logger.Error($"Failed to set HDMI output start address");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var ret = await UDPClientPool.WriteAddr(
|
||||
|
|
|
@ -9,7 +9,7 @@ namespace Peripherals.LogicAnalyzerClient;
|
|||
static class AnalyzerAddr
|
||||
{
|
||||
const UInt32 BASE = 0x9000_0000;
|
||||
const UInt32 DMA1_BASE = 0x7000_0000;
|
||||
const UInt32 DMA_BASE = 0xA000_0000;
|
||||
const UInt32 DDR_BASE = 0x0000_0000;
|
||||
|
||||
/// <summary>
|
||||
|
@ -68,9 +68,9 @@ static class AnalyzerAddr
|
|||
public const UInt32 PRE_LOAD_NUM_ADDR = BASE + 0x0000_0003;
|
||||
public const UInt32 CAHNNEL_DIV_ADDR = BASE + 0x0000_0004;
|
||||
public const UInt32 CLOCK_DIV_ADDR = BASE + 0x0000_0005;
|
||||
public const UInt32 DMA1_START_WRITE_ADDR = DMA1_BASE + 0x0000_0012;
|
||||
public const UInt32 DMA1_END_WRITE_ADDR = DMA1_BASE + 0x0000_0013;
|
||||
public const UInt32 DMA1_CAPTURE_CTRL_ADDR = DMA1_BASE + 0x0000_0014;
|
||||
public const UInt32 DMA_CAPTURE_RD_CTRL1 = DMA_BASE + 0x1;
|
||||
public const UInt32 DMA_START_WRITE_ADDR1 = DMA_BASE + 0x22;
|
||||
public const UInt32 DMA_END_WRITE_ADDR1 = DMA_BASE + 0x23;
|
||||
public const UInt32 STORE_OFFSET_ADDR = DDR_BASE + 0x0100_0000;
|
||||
|
||||
/// <summary>
|
||||
|
@ -327,20 +327,34 @@ public class Analyzer
|
|||
/// <returns>操作结果,成功返回true,否则返回异常信息</returns>
|
||||
public async ValueTask<Result<bool>> SetCaptureMode(bool captureOn, bool force)
|
||||
{
|
||||
// 构造寄存器值
|
||||
UInt32 value = 0;
|
||||
if (captureOn) value |= 1 << 0;
|
||||
{
|
||||
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.DMA1_CAPTURE_CTRL_ADDR, value, this.timeout);
|
||||
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.DMA_CAPTURE_RD_CTRL1, 0x00000000u, this.timeout);
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Failed to set DMA1_CAPTURE_CTRL_ADDR: {ret.Error}");
|
||||
logger.Error($"Failed to set DMA_CAPTURE_RD_CTRL to 0: {ret.Error}");
|
||||
return new(ret.Error);
|
||||
}
|
||||
if (!ret.Value)
|
||||
{
|
||||
logger.Error("WriteAddr to DMA1_CAPTURE_CTRL_ADDR returned false");
|
||||
return new(new Exception("Failed to set DMA1_CAPTURE_CTRL_ADDR"));
|
||||
logger.Error("WriteAddr to DMA_CAPTURE_RD_CTRL returned false");
|
||||
return new(new Exception("Failed to set DMA_CAPTURE_RD_CTRL"));
|
||||
}
|
||||
}
|
||||
await Task.Delay(5);
|
||||
// 构造寄存器值
|
||||
UInt32 value = 0;
|
||||
if (captureOn) value |= 1 << 0;
|
||||
{
|
||||
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.DMA_CAPTURE_RD_CTRL1, value, this.timeout);
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Failed to set DMA_CAPTURE_RD_CTRL: {ret.Error}");
|
||||
return new(ret.Error);
|
||||
}
|
||||
if (!ret.Value)
|
||||
{
|
||||
logger.Error("WriteAddr to DMA_CAPTURE_RD_CTRL returned false");
|
||||
return new(new Exception("Failed to set DMA_CAPTURE_RD_CTRL"));
|
||||
}
|
||||
}
|
||||
if (force) value |= 1 << 8;
|
||||
|
@ -472,29 +486,29 @@ public class Analyzer
|
|||
}
|
||||
}
|
||||
{
|
||||
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.DMA1_START_WRITE_ADDR, AnalyzerAddr.STORE_OFFSET_ADDR, this.timeout);
|
||||
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.DMA_START_WRITE_ADDR1, AnalyzerAddr.STORE_OFFSET_ADDR, this.timeout);
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Failed to set DMA1_START_WRITE_ADDR: {ret.Error}");
|
||||
logger.Error($"Failed to set DMA_START_WRITE_ADDR: {ret.Error}");
|
||||
return new(ret.Error);
|
||||
}
|
||||
if (!ret.Value)
|
||||
{
|
||||
logger.Error("WriteAddr to DMA1_START_WRITE_ADDR returned false");
|
||||
return new(new Exception("Failed to set DMA1_START_WRITE_ADDR"));
|
||||
logger.Error("WriteAddr to DMA_START_WRITE_ADDR returned false");
|
||||
return new(new Exception("Failed to set DMA_START_WRITE_ADDR"));
|
||||
}
|
||||
}
|
||||
{
|
||||
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.DMA1_END_WRITE_ADDR, AnalyzerAddr.STORE_OFFSET_ADDR + (UInt32)(capture_length - 1), this.timeout);
|
||||
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.DMA_END_WRITE_ADDR1, AnalyzerAddr.STORE_OFFSET_ADDR + (UInt32)(capture_length - 1), this.timeout);
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Failed to set DMA1_END_WRITE_ADDR: {ret.Error}");
|
||||
logger.Error($"Failed to set DMA_END_WRITE_ADDR: {ret.Error}");
|
||||
return new(ret.Error);
|
||||
}
|
||||
if (!ret.Value)
|
||||
{
|
||||
logger.Error("WriteAddr to DMA1_END_WRITE_ADDR returned false");
|
||||
return new(new Exception("Failed to set DMA1_END_WRITE_ADDR"));
|
||||
logger.Error("WriteAddr to DMA_END_WRITE_ADDR returned false");
|
||||
return new(new Exception("Failed to set DMA_END_WRITE_ADDR"));
|
||||
}
|
||||
}
|
||||
{
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
using System.Net;
|
||||
using DotNext;
|
||||
|
||||
namespace Peripherals.WS2812Client;
|
||||
|
||||
class WS2812Addr
|
||||
{
|
||||
public const UInt32 BASE = 0xB0_00_01_00;
|
||||
public const int LED_COUNT = 128;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RGB颜色结构体,包含红、绿、蓝三个颜色分量
|
||||
/// </summary>
|
||||
public struct RGBColor
|
||||
{
|
||||
public byte Red { get; set; }
|
||||
public byte Green { get; set; }
|
||||
public byte Blue { get; set; }
|
||||
|
||||
public RGBColor(byte red, byte green, byte blue)
|
||||
{
|
||||
Red = red;
|
||||
Green = green;
|
||||
Blue = blue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从32位数据的低24位提取RGB颜色
|
||||
/// </summary>
|
||||
/// <param name="data">32位数据</param>
|
||||
/// <returns>RGB颜色</returns>
|
||||
public static RGBColor FromUInt32(UInt32 data)
|
||||
{
|
||||
return new RGBColor(
|
||||
(byte)((data >> 16) & 0xFF), // Red
|
||||
(byte)((data >> 8) & 0xFF), // Green
|
||||
(byte)(data & 0xFF) // Blue
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换为32位数据格式
|
||||
/// </summary>
|
||||
/// <returns>32位数据</returns>
|
||||
public UInt32 ToUInt32()
|
||||
{
|
||||
return ((UInt32)Red << 16) | ((UInt32)Green << 8) | (UInt32)Blue;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"RGB({Red}, {Green}, {Blue})";
|
||||
}
|
||||
}
|
||||
|
||||
public class WS2812Client
|
||||
{
|
||||
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
|
||||
readonly int timeout = 500;
|
||||
readonly int taskID;
|
||||
readonly int port;
|
||||
readonly string address;
|
||||
private IPEndPoint ep;
|
||||
|
||||
public WS2812Client(string address, int port, int taskID, int timeout = 500)
|
||||
{
|
||||
if (timeout < 0)
|
||||
throw new ArgumentException("Timeout couldn't be negative", nameof(timeout));
|
||||
this.address = address;
|
||||
this.port = port;
|
||||
this.ep = new IPEndPoint(IPAddress.Parse(address), port);
|
||||
this.taskID = taskID;
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定灯珠的RGB颜色
|
||||
/// </summary>
|
||||
/// <param name="ledIndex">灯珠索引,范围0-127</param>
|
||||
/// <returns>RGB颜色结果</returns>
|
||||
public async ValueTask<Result<RGBColor>> GetLedColor(int ledIndex)
|
||||
{
|
||||
if (ledIndex < 0 || ledIndex >= WS2812Addr.LED_COUNT)
|
||||
{
|
||||
return new(new ArgumentOutOfRangeException(nameof(ledIndex),
|
||||
$"LED index must be between 0 and {WS2812Addr.LED_COUNT - 1}"));
|
||||
}
|
||||
|
||||
if (MsgBus.IsRunning)
|
||||
MsgBus.UDPServer.ClearUDPData(this.address, this.taskID);
|
||||
else
|
||||
return new(new Exception("Message Bus not work!"));
|
||||
|
||||
var addr = WS2812Addr.BASE + (UInt32)(ledIndex * 4); // 每个地址32位,步长为4字节
|
||||
var ret = await UDPClientPool.ReadAddrByte(this.ep, this.taskID, addr, this.timeout);
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Get LED {ledIndex} color failed: {ret.Error}");
|
||||
return new(ret.Error);
|
||||
}
|
||||
var retData = ret.Value.Options.Data;
|
||||
if (retData is null)
|
||||
return new(new Exception($"Device {address} receive none"));
|
||||
if (retData.Length < 4)
|
||||
{
|
||||
var error = new Exception($"Invalid data length: expected 4 bytes, got {retData.Length}");
|
||||
logger.Error($"Get LED {ledIndex} color failed: {error}");
|
||||
return new(error);
|
||||
}
|
||||
|
||||
var colorData = Convert.ToUInt32(Common.Number.BytesToUInt64(retData).Value);
|
||||
var color = RGBColor.FromUInt32(colorData);
|
||||
return new(color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有灯珠的RGB颜色
|
||||
/// </summary>
|
||||
/// <returns>包含所有灯珠颜色的数组</returns>
|
||||
public async ValueTask<Result<RGBColor[]>> GetAllLedColors()
|
||||
{
|
||||
if (MsgBus.IsRunning)
|
||||
MsgBus.UDPServer.ClearUDPData(this.address, this.taskID);
|
||||
else
|
||||
return new(new Exception("Message Bus not work!"));
|
||||
|
||||
try
|
||||
{
|
||||
// 一次性读取所有LED数据,每个LED占用4字节,总共128*4=512字节
|
||||
var ret = await UDPClientPool.ReadAddr4Bytes(this.ep, this.taskID, WS2812Addr.BASE, WS2812Addr.LED_COUNT, this.timeout);
|
||||
|
||||
if (!ret.IsSuccessful)
|
||||
{
|
||||
logger.Error($"Get all LED colors failed: {ret.Error}");
|
||||
return new(ret.Error);
|
||||
}
|
||||
|
||||
var data = ret.Value;
|
||||
var expectedLength = WS2812Addr.LED_COUNT * 4; // 128 * 4 = 512 bytes
|
||||
|
||||
if (data.Length < expectedLength)
|
||||
{
|
||||
var error = new Exception($"Invalid data length: expected {expectedLength} bytes, got {data.Length}");
|
||||
logger.Error(error.Message);
|
||||
return new(error);
|
||||
}
|
||||
|
||||
var colors = new RGBColor[WS2812Addr.LED_COUNT];
|
||||
|
||||
for (int i = 0; i < WS2812Addr.LED_COUNT; i++)
|
||||
{
|
||||
var offset = i * 4;
|
||||
// 将4字节数据转换为UInt32
|
||||
var colorData = BitConverter.ToUInt32(data, offset);
|
||||
colors[i] = RGBColor.FromUInt32(colorData);
|
||||
}
|
||||
|
||||
return new(colors);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error($"Get all LED colors failed: {ex}");
|
||||
return new(ex);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue