fix: 修改部分外设BASE偏移量;增加WS2812后端监控器;DSO寄
This commit is contained in:
		
							
								
								
									
										138
									
								
								server/src/Hubs/WS2812Hub.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								server/src/Hubs/WS2812Hub.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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"));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										168
									
								
								server/src/Peripherals/WS2812Client.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								server/src/Peripherals/WS2812Client.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user