add: 为逻辑分析仪添加了深度、预存储深度、通道组设置
This commit is contained in:
		@@ -45,7 +45,18 @@ public class LogicAnalyzerController : ControllerBase
 | 
			
		||||
        /// 全局触发模式
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public GlobalCaptureMode GlobalMode { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 捕获深度
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public int CaptureLength { get; set; } = 2048 * 32;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 预采样深度
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public int PreCaptureLength { get; set; } = 2048;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 有效通道
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public AnalyzerChannelDiv ChannelDiv { get; set; } = AnalyzerChannelDiv.EIGHT;
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 信号触发配置列表
 | 
			
		||||
        /// </summary>
 | 
			
		||||
@@ -77,7 +88,7 @@ public class LogicAnalyzerController : ControllerBase
 | 
			
		||||
                return null;
 | 
			
		||||
 | 
			
		||||
            var board = boardRet.Value.Value;
 | 
			
		||||
            return new Analyzer(board.IpAddr, board.Port, 2);
 | 
			
		||||
            return new Analyzer(board.IpAddr, board.Port, 0);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
@@ -208,8 +219,8 @@ public class LogicAnalyzerController : ControllerBase
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            if (signalIndex < 0 || signalIndex > 7)
 | 
			
		||||
                return BadRequest("信号索引必须在0-7之间");
 | 
			
		||||
            if (signalIndex < 0 || signalIndex > 31)
 | 
			
		||||
                return BadRequest("信号索引必须在0-31之间");
 | 
			
		||||
 | 
			
		||||
            var analyzer = GetAnalyzer();
 | 
			
		||||
            if (analyzer == null)
 | 
			
		||||
@@ -231,6 +242,48 @@ public class LogicAnalyzerController : ControllerBase
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 设置深度、预采样深度、有效通道
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="capture_length">深度</param>
 | 
			
		||||
    /// <param name="pre_capture_length">预采样深度</param>
 | 
			
		||||
    /// <param name="channel_div">有效通道(0-[1],1-[2],2-[4],3-[8],4-[16],5-[32])</param>
 | 
			
		||||
    /// <returns>操作结果</returns>
 | 
			
		||||
    [HttpPost("SetCaptureParams")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
 | 
			
		||||
    public async Task<IActionResult> SetCaptureParams(int capture_length, int pre_capture_length, AnalyzerChannelDiv channel_div)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            if (capture_length < 0 || capture_length > 2048*32)
 | 
			
		||||
                return BadRequest("采样深度设置错误");
 | 
			
		||||
            if (pre_capture_length < 0 || pre_capture_length >= capture_length)
 | 
			
		||||
                return BadRequest("预采样深度必须小于捕获深度");
 | 
			
		||||
 | 
			
		||||
            var analyzer = GetAnalyzer();
 | 
			
		||||
            if (analyzer == null)
 | 
			
		||||
                return BadRequest("用户未绑定有效的实验板");
 | 
			
		||||
 | 
			
		||||
            var result = await analyzer.SetCaptureParams(capture_length, pre_capture_length, channel_div);
 | 
			
		||||
            if (!result.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"设置深度、预采样深度、有效通道失败: {result.Error}");
 | 
			
		||||
                return StatusCode(StatusCodes.Status500InternalServerError, "设置深度、预采样深度、有效通道失败");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Ok(result.Value);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error(ex, "设置深度、预采样深度、有效通道失败时发生异常");
 | 
			
		||||
            return StatusCode(StatusCodes.Status500InternalServerError, "操作失败,请稍后重试");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 批量配置捕获参数
 | 
			
		||||
    /// </summary>
 | 
			
		||||
@@ -264,8 +317,8 @@ public class LogicAnalyzerController : ControllerBase
 | 
			
		||||
            // 设置信号触发模式
 | 
			
		||||
            foreach (var signalConfig in config.SignalConfigs)
 | 
			
		||||
            {
 | 
			
		||||
                if (signalConfig.SignalIndex < 0 || signalConfig.SignalIndex > 7)
 | 
			
		||||
                    return BadRequest($"信号索引{signalConfig.SignalIndex}超出范围0-7");
 | 
			
		||||
                if (signalConfig.SignalIndex < 0 || signalConfig.SignalIndex > 31)
 | 
			
		||||
                    return BadRequest($"信号索引{signalConfig.SignalIndex}超出范围0-31");
 | 
			
		||||
 | 
			
		||||
                var signalResult = await analyzer.SetSignalTrigMode(
 | 
			
		||||
                    signalConfig.SignalIndex, signalConfig.Operator, signalConfig.Value);
 | 
			
		||||
@@ -276,6 +329,14 @@ public class LogicAnalyzerController : ControllerBase
 | 
			
		||||
                        $"设置信号{signalConfig.SignalIndex}触发模式失败");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            // 设置深度、预采样深度、有效通道
 | 
			
		||||
            var paramsResult = await analyzer.SetCaptureParams(
 | 
			
		||||
                config.CaptureLength, config.PreCaptureLength, config.ChannelDiv);
 | 
			
		||||
            if (!paramsResult.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"设置深度、预采样深度、有效通道失败: {paramsResult.Error}");
 | 
			
		||||
                return StatusCode(StatusCodes.Status500InternalServerError, "设置深度、预采样深度、有效通道失败");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Ok(true);
 | 
			
		||||
        }
 | 
			
		||||
@@ -330,7 +391,7 @@ public class LogicAnalyzerController : ControllerBase
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
 | 
			
		||||
    public async Task<IActionResult> GetCaptureData()
 | 
			
		||||
    public async Task<IActionResult> GetCaptureData(int capture_length = 2048 * 32)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
@@ -338,7 +399,7 @@ public class LogicAnalyzerController : ControllerBase
 | 
			
		||||
            if (analyzer == null)
 | 
			
		||||
                return BadRequest("用户未绑定有效的实验板");
 | 
			
		||||
 | 
			
		||||
            var result = await analyzer.ReadCaptureData();
 | 
			
		||||
            var result = await analyzer.ReadCaptureData(capture_length);
 | 
			
		||||
            if (!result.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"读取捕获数据失败: {result.Error}");
 | 
			
		||||
 
 | 
			
		||||
@@ -46,17 +46,26 @@ static class AnalyzerAddr
 | 
			
		||||
    ///                                                   111 SOME NUMBER <br/>
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public static readonly UInt32[] SIGNAL_TRIG_MODE = {
 | 
			
		||||
        BASE + 0x0000_0010,
 | 
			
		||||
        BASE + 0x0000_0011,
 | 
			
		||||
        BASE + 0x0000_0012,
 | 
			
		||||
        BASE + 0x0000_0013,
 | 
			
		||||
        BASE + 0x0000_0014,
 | 
			
		||||
        BASE + 0x0000_0015,
 | 
			
		||||
        BASE + 0x0000_0016,
 | 
			
		||||
        BASE + 0x0000_0017,
 | 
			
		||||
        BASE + 0x0000_0010, BASE + 0x0000_0011,
 | 
			
		||||
        BASE + 0x0000_0012, BASE + 0x0000_0013,
 | 
			
		||||
        BASE + 0x0000_0014, BASE + 0x0000_0015,
 | 
			
		||||
        BASE + 0x0000_0016, BASE + 0x0000_0017,
 | 
			
		||||
        BASE + 0x0000_0018, BASE + 0x0000_0019,
 | 
			
		||||
        BASE + 0x0000_001A, BASE + 0x0000_001B,
 | 
			
		||||
        BASE + 0x0000_001C, BASE + 0x0000_001D,
 | 
			
		||||
        BASE + 0x0000_001E, BASE + 0x0000_001F,
 | 
			
		||||
        BASE + 0x0000_0020, BASE + 0x0000_0021,
 | 
			
		||||
        BASE + 0x0000_0022, BASE + 0x0000_0023,
 | 
			
		||||
        BASE + 0x0000_0024, BASE + 0x0000_0025,
 | 
			
		||||
        BASE + 0x0000_0026, BASE + 0x0000_0027,
 | 
			
		||||
        BASE + 0x0000_0028, BASE + 0x0000_0029,
 | 
			
		||||
        BASE + 0x0000_002A, BASE + 0x0000_002B,
 | 
			
		||||
        BASE + 0x0000_002C, BASE + 0x0000_002D,
 | 
			
		||||
        BASE + 0x0000_002E, BASE + 0x0000_002F
 | 
			
		||||
    };
 | 
			
		||||
    public const UInt32 LOAD_NUM_ADDR = BASE + 0x0000_0002;
 | 
			
		||||
    public const UInt32 PRE_LOAD_NUM_ADDR = BASE + 0x0000_0003;
 | 
			
		||||
    public const UInt32 CAHNNEL_DIV_ADDR = BASE + 0x0000_0004;
 | 
			
		||||
    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;
 | 
			
		||||
@@ -198,6 +207,37 @@ public enum SignalValue : byte
 | 
			
		||||
    SomeNumber = 0b111     // SOME NUMBER
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// 逻辑分析仪有效通道数
 | 
			
		||||
/// </summary>
 | 
			
		||||
public enum AnalyzerChannelDiv
 | 
			
		||||
{
 | 
			
		||||
    /// <summary> 
 | 
			
		||||
    /// 1路
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    ONE = 0x0000_0000,
 | 
			
		||||
    /// <summary> 
 | 
			
		||||
    /// 2路
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    TWO = 0x0000_0001,
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 4路
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    FOUR = 0x0000_0002,
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 8路
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    EIGHT = 0x0000_0003,
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 16路
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    XVI = 0x0000_0004,
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 32路
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    XXXII = 0x0000_0005
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// FPGA逻辑分析仪客户端,用于控制FPGA上的逻辑分析仪模块进行信号捕获和分析
 | 
			
		||||
/// </summary>
 | 
			
		||||
@@ -239,58 +279,6 @@ public class Analyzer
 | 
			
		||||
    /// <returns>操作结果,成功返回true,否则返回异常信息</returns>
 | 
			
		||||
    public async ValueTask<Result<bool>> SetCaptureMode(bool captureOn, bool force)
 | 
			
		||||
    {
 | 
			
		||||
        {
 | 
			
		||||
            var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.LOAD_NUM_ADDR, AnalyzerAddr.CAPTURE_DATA_LENGTH - 1, this.timeout);
 | 
			
		||||
            if (!ret.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Failed to set LOAD_NUM_ADDR: {ret.Error}");
 | 
			
		||||
                return new(ret.Error);
 | 
			
		||||
            }
 | 
			
		||||
            if (!ret.Value)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error("WriteAddr to LOAD_NUM_ADDR returned false");
 | 
			
		||||
                return new(new Exception("Failed to set LOAD_NUM_ADDR"));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        {
 | 
			
		||||
            var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.PRE_LOAD_NUM_ADDR, AnalyzerAddr.CAPTURE_DATA_PRELOAD - 1, this.timeout);
 | 
			
		||||
            if (!ret.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Failed to set PRE_LOAD_NUM_ADDR: {ret.Error}");
 | 
			
		||||
                return new(ret.Error);
 | 
			
		||||
            }
 | 
			
		||||
            if (!ret.Value)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error("WriteAddr to PRE_LOAD_NUM_ADDR returned false");
 | 
			
		||||
                return new(new Exception("Failed to set PRE_LOAD_NUM_ADDR"));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        {
 | 
			
		||||
            var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.DMA1_START_WRITE_ADDR, AnalyzerAddr.STORE_OFFSET_ADDR, this.timeout);
 | 
			
		||||
            if (!ret.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Failed to set DMA1_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"));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        {
 | 
			
		||||
            var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.DMA1_END_WRITE_ADDR, AnalyzerAddr.STORE_OFFSET_ADDR + AnalyzerAddr.CAPTURE_DATA_LENGTH - 1, this.timeout);
 | 
			
		||||
            if (!ret.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Failed to set DMA1_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"));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // 构造寄存器值
 | 
			
		||||
        UInt32 value = 0;
 | 
			
		||||
        if (captureOn) value |= 1 << 0;
 | 
			
		||||
@@ -380,7 +368,7 @@ public class Analyzer
 | 
			
		||||
            return new(new ArgumentException($"Signal index must be 0~{AnalyzerAddr.SIGNAL_TRIG_MODE.Length}"));
 | 
			
		||||
 | 
			
		||||
        // 计算模式值: [2:0] 信号值, [5:3] 操作符
 | 
			
		||||
        UInt32 mode = ((UInt32)op << 3) | (UInt32) val;
 | 
			
		||||
        UInt32 mode = ((UInt32)op << 3) | (UInt32)val;
 | 
			
		||||
 | 
			
		||||
        var addr = AnalyzerAddr.SIGNAL_TRIG_MODE[signalIndex];
 | 
			
		||||
        var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, addr, mode, this.timeout);
 | 
			
		||||
@@ -397,17 +385,96 @@ public class Analyzer
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 设置逻辑分析仪的深度、预采样深度、有效通道
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="capture_length">深度</param>
 | 
			
		||||
    /// <param name="pre_capture_length">预采样深度</param>
 | 
			
		||||
    /// <param name="channel_div">有效通道(0-[1],1-[2],2-[4],3-[8],4-[16],5-[32])</param>
 | 
			
		||||
    /// <returns>操作结果,成功返回true,否则返回异常信息</returns>
 | 
			
		||||
    public async ValueTask<Result<bool>> SetCaptureParams(int capture_length, int pre_capture_length, AnalyzerChannelDiv channel_div)
 | 
			
		||||
    {
 | 
			
		||||
        if (capture_length == 0) capture_length = 1;
 | 
			
		||||
        if (pre_capture_length == 0) pre_capture_length = 1;
 | 
			
		||||
        {
 | 
			
		||||
            var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.LOAD_NUM_ADDR, (UInt32)(capture_length - 1), this.timeout);
 | 
			
		||||
            if (!ret.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Failed to set LOAD_NUM_ADDR: {ret.Error}");
 | 
			
		||||
                return new(ret.Error);
 | 
			
		||||
            }
 | 
			
		||||
            if (!ret.Value)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error("WriteAddr to LOAD_NUM_ADDR returned false");
 | 
			
		||||
                return new(new Exception("Failed to set LOAD_NUM_ADDR"));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        {
 | 
			
		||||
            var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.PRE_LOAD_NUM_ADDR, (UInt32)(pre_capture_length - 1), this.timeout);
 | 
			
		||||
            if (!ret.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Failed to set PRE_LOAD_NUM_ADDR: {ret.Error}");
 | 
			
		||||
                return new(ret.Error);
 | 
			
		||||
            }
 | 
			
		||||
            if (!ret.Value)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error("WriteAddr to PRE_LOAD_NUM_ADDR returned false");
 | 
			
		||||
                return new(new Exception("Failed to set PRE_LOAD_NUM_ADDR"));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        {
 | 
			
		||||
            var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.DMA1_START_WRITE_ADDR, AnalyzerAddr.STORE_OFFSET_ADDR, this.timeout);
 | 
			
		||||
            if (!ret.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Failed to set DMA1_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"));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        {
 | 
			
		||||
            var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.DMA1_END_WRITE_ADDR, AnalyzerAddr.STORE_OFFSET_ADDR + (UInt32)(capture_length - 1), this.timeout);
 | 
			
		||||
            if (!ret.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Failed to set DMA1_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"));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        {
 | 
			
		||||
            var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.CAHNNEL_DIV_ADDR, (UInt32)channel_div, this.timeout);
 | 
			
		||||
            if (!ret.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"Failed to set CAHNNEL_DIV_ADDR: {ret.Error}");
 | 
			
		||||
                return new(ret.Error);
 | 
			
		||||
            }
 | 
			
		||||
            if (!ret.Value)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error("WriteAddr to CAHNNEL_DIV_ADDR returned false");
 | 
			
		||||
                return new(new Exception("Failed to set CAHNNEL_DIV_ADDR"));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 读取捕获的波形数据
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns>操作结果,成功返回byte[],否则返回异常信息</returns>
 | 
			
		||||
    public async ValueTask<Result<byte[]>> ReadCaptureData()
 | 
			
		||||
    public async ValueTask<Result<byte[]>> ReadCaptureData(int capture_length = 2048 * 32)
 | 
			
		||||
    {
 | 
			
		||||
        var ret = await UDPClientPool.ReadAddr4BytesAsync(
 | 
			
		||||
            this.ep,
 | 
			
		||||
            this.taskID,
 | 
			
		||||
            AnalyzerAddr.STORE_OFFSET_ADDR,
 | 
			
		||||
            AnalyzerAddr.CAPTURE_DATA_LENGTH,
 | 
			
		||||
            capture_length,
 | 
			
		||||
            this.timeout
 | 
			
		||||
        );
 | 
			
		||||
        if (!ret.IsSuccessful)
 | 
			
		||||
@@ -416,7 +483,7 @@ public class Analyzer
 | 
			
		||||
            return new(ret.Error);
 | 
			
		||||
        }
 | 
			
		||||
        var data = ret.Value;
 | 
			
		||||
        if (data == null || data.Length != AnalyzerAddr.CAPTURE_DATA_LENGTH * 4)
 | 
			
		||||
        if (data == null || data.Length != capture_length * 4)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"Capture data length mismatch: {data?.Length}");
 | 
			
		||||
            return new(new Exception("Capture data length mismatch"));
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										123
									
								
								src/APIClient.ts
									
									
									
									
									
								
							
							
						
						
									
										123
									
								
								src/APIClient.ts
									
									
									
									
									
								
							@@ -3678,6 +3678,92 @@ export class LogicAnalyzerClient {
 | 
			
		||||
        return Promise.resolve<boolean>(null as any);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置深度、预采样深度、有效通道
 | 
			
		||||
     * @param capture_length (optional) 深度
 | 
			
		||||
     * @param pre_capture_length (optional) 预采样深度
 | 
			
		||||
     * @param channel_div (optional) 有效通道(0-[1],1-[2],2-[4],3-[8],4-[16],5-[32])
 | 
			
		||||
     * @return 操作结果
 | 
			
		||||
     */
 | 
			
		||||
    setCaptureParams(capture_length: number | undefined, pre_capture_length: number | undefined, channel_div: AnalyzerChannelDiv | undefined, cancelToken?: CancelToken): Promise<boolean> {
 | 
			
		||||
        let url_ = this.baseUrl + "/api/LogicAnalyzer/SetCaptureParams?";
 | 
			
		||||
        if (capture_length === null)
 | 
			
		||||
            throw new Error("The parameter 'capture_length' cannot be null.");
 | 
			
		||||
        else if (capture_length !== undefined)
 | 
			
		||||
            url_ += "capture_length=" + encodeURIComponent("" + capture_length) + "&";
 | 
			
		||||
        if (pre_capture_length === null)
 | 
			
		||||
            throw new Error("The parameter 'pre_capture_length' cannot be null.");
 | 
			
		||||
        else if (pre_capture_length !== undefined)
 | 
			
		||||
            url_ += "pre_capture_length=" + encodeURIComponent("" + pre_capture_length) + "&";
 | 
			
		||||
        if (channel_div === null)
 | 
			
		||||
            throw new Error("The parameter 'channel_div' cannot be null.");
 | 
			
		||||
        else if (channel_div !== undefined)
 | 
			
		||||
            url_ += "channel_div=" + encodeURIComponent("" + channel_div) + "&";
 | 
			
		||||
        url_ = url_.replace(/[?&]$/, "");
 | 
			
		||||
 | 
			
		||||
        let options_: AxiosRequestConfig = {
 | 
			
		||||
            method: "POST",
 | 
			
		||||
            url: url_,
 | 
			
		||||
            headers: {
 | 
			
		||||
                "Accept": "application/json"
 | 
			
		||||
            },
 | 
			
		||||
            cancelToken
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return this.instance.request(options_).catch((_error: any) => {
 | 
			
		||||
            if (isAxiosError(_error) && _error.response) {
 | 
			
		||||
                return _error.response;
 | 
			
		||||
            } else {
 | 
			
		||||
                throw _error;
 | 
			
		||||
            }
 | 
			
		||||
        }).then((_response: AxiosResponse) => {
 | 
			
		||||
            return this.processSetCaptureParams(_response);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected processSetCaptureParams(response: AxiosResponse): Promise<boolean> {
 | 
			
		||||
        const status = response.status;
 | 
			
		||||
        let _headers: any = {};
 | 
			
		||||
        if (response.headers && typeof response.headers === "object") {
 | 
			
		||||
            for (const k in response.headers) {
 | 
			
		||||
                if (response.headers.hasOwnProperty(k)) {
 | 
			
		||||
                    _headers[k] = response.headers[k];
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (status === 200) {
 | 
			
		||||
            const _responseText = response.data;
 | 
			
		||||
            let result200: any = null;
 | 
			
		||||
            let resultData200  = _responseText;
 | 
			
		||||
                result200 = resultData200 !== undefined ? resultData200 : <any>null;
 | 
			
		||||
    
 | 
			
		||||
            return Promise.resolve<boolean>(result200);
 | 
			
		||||
 | 
			
		||||
        } else if (status === 400) {
 | 
			
		||||
            const _responseText = response.data;
 | 
			
		||||
            let result400: any = null;
 | 
			
		||||
            let resultData400  = _responseText;
 | 
			
		||||
            result400 = ProblemDetails.fromJS(resultData400);
 | 
			
		||||
            return throwException("A server side error occurred.", status, _responseText, _headers, result400);
 | 
			
		||||
 | 
			
		||||
        } else if (status === 500) {
 | 
			
		||||
            const _responseText = response.data;
 | 
			
		||||
            return throwException("A server side error occurred.", status, _responseText, _headers);
 | 
			
		||||
 | 
			
		||||
        } else if (status === 401) {
 | 
			
		||||
            const _responseText = response.data;
 | 
			
		||||
            let result401: any = null;
 | 
			
		||||
            let resultData401  = _responseText;
 | 
			
		||||
            result401 = ProblemDetails.fromJS(resultData401);
 | 
			
		||||
            return throwException("A server side error occurred.", status, _responseText, _headers, result401);
 | 
			
		||||
 | 
			
		||||
        } else if (status !== 200 && status !== 204) {
 | 
			
		||||
            const _responseText = response.data;
 | 
			
		||||
            return throwException("An unexpected server error occurred.", status, _responseText, _headers);
 | 
			
		||||
        }
 | 
			
		||||
        return Promise.resolve<boolean>(null as any);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 批量配置捕获参数
 | 
			
		||||
     * @param config 捕获配置
 | 
			
		||||
@@ -3827,10 +3913,15 @@ export class LogicAnalyzerClient {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 读取捕获数据
 | 
			
		||||
     * @param capture_length (optional) 
 | 
			
		||||
     * @return 捕获的波形数据(Base64编码)
 | 
			
		||||
     */
 | 
			
		||||
    getCaptureData( cancelToken?: CancelToken): Promise<string> {
 | 
			
		||||
        let url_ = this.baseUrl + "/api/LogicAnalyzer/GetCaptureData";
 | 
			
		||||
    getCaptureData(capture_length: number | undefined, cancelToken?: CancelToken): Promise<string> {
 | 
			
		||||
        let url_ = this.baseUrl + "/api/LogicAnalyzer/GetCaptureData?";
 | 
			
		||||
        if (capture_length === null)
 | 
			
		||||
            throw new Error("The parameter 'capture_length' cannot be null.");
 | 
			
		||||
        else if (capture_length !== undefined)
 | 
			
		||||
            url_ += "capture_length=" + encodeURIComponent("" + capture_length) + "&";
 | 
			
		||||
        url_ = url_.replace(/[?&]$/, "");
 | 
			
		||||
 | 
			
		||||
        let options_: AxiosRequestConfig = {
 | 
			
		||||
@@ -7237,10 +7328,26 @@ export enum SignalValue {
 | 
			
		||||
    SomeNumber = 7,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 逻辑分析仪有效通道数 */
 | 
			
		||||
export enum AnalyzerChannelDiv {
 | 
			
		||||
    ONE = 0,
 | 
			
		||||
    TWO = 1,
 | 
			
		||||
    FOUR = 2,
 | 
			
		||||
    EIGHT = 3,
 | 
			
		||||
    XVI = 4,
 | 
			
		||||
    XXXII = 5,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 捕获配置 */
 | 
			
		||||
export class CaptureConfig implements ICaptureConfig {
 | 
			
		||||
    /** 全局触发模式 */
 | 
			
		||||
    globalMode!: GlobalCaptureMode;
 | 
			
		||||
    /** 捕获深度 */
 | 
			
		||||
    captureLength!: number;
 | 
			
		||||
    /** 预采样深度 */
 | 
			
		||||
    preCaptureLength!: number;
 | 
			
		||||
    /** 有效通道 */
 | 
			
		||||
    channelDiv!: AnalyzerChannelDiv;
 | 
			
		||||
    /** 信号触发配置列表 */
 | 
			
		||||
    signalConfigs!: SignalTriggerConfig[];
 | 
			
		||||
 | 
			
		||||
@@ -7259,6 +7366,9 @@ export class CaptureConfig implements ICaptureConfig {
 | 
			
		||||
    init(_data?: any) {
 | 
			
		||||
        if (_data) {
 | 
			
		||||
            this.globalMode = _data["globalMode"];
 | 
			
		||||
            this.captureLength = _data["captureLength"];
 | 
			
		||||
            this.preCaptureLength = _data["preCaptureLength"];
 | 
			
		||||
            this.channelDiv = _data["channelDiv"];
 | 
			
		||||
            if (Array.isArray(_data["signalConfigs"])) {
 | 
			
		||||
                this.signalConfigs = [] as any;
 | 
			
		||||
                for (let item of _data["signalConfigs"])
 | 
			
		||||
@@ -7277,6 +7387,9 @@ export class CaptureConfig implements ICaptureConfig {
 | 
			
		||||
    toJSON(data?: any) {
 | 
			
		||||
        data = typeof data === 'object' ? data : {};
 | 
			
		||||
        data["globalMode"] = this.globalMode;
 | 
			
		||||
        data["captureLength"] = this.captureLength;
 | 
			
		||||
        data["preCaptureLength"] = this.preCaptureLength;
 | 
			
		||||
        data["channelDiv"] = this.channelDiv;
 | 
			
		||||
        if (Array.isArray(this.signalConfigs)) {
 | 
			
		||||
            data["signalConfigs"] = [];
 | 
			
		||||
            for (let item of this.signalConfigs)
 | 
			
		||||
@@ -7290,6 +7403,12 @@ export class CaptureConfig implements ICaptureConfig {
 | 
			
		||||
export interface ICaptureConfig {
 | 
			
		||||
    /** 全局触发模式 */
 | 
			
		||||
    globalMode: GlobalCaptureMode;
 | 
			
		||||
    /** 捕获深度 */
 | 
			
		||||
    captureLength: number;
 | 
			
		||||
    /** 预采样深度 */
 | 
			
		||||
    preCaptureLength: number;
 | 
			
		||||
    /** 有效通道 */
 | 
			
		||||
    channelDiv: AnalyzerChannelDiv;
 | 
			
		||||
    /** 信号触发配置列表 */
 | 
			
		||||
    signalConfigs: SignalTriggerConfig[];
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ import {
 | 
			
		||||
  SignalOperator,
 | 
			
		||||
  SignalTriggerConfig,
 | 
			
		||||
  SignalValue,
 | 
			
		||||
  AnalyzerChannelDiv,
 | 
			
		||||
} from "@/APIClient";
 | 
			
		||||
import { AuthManager } from "@/utils/AuthManager";
 | 
			
		||||
import { useAlertStore } from "@/components/Alert";
 | 
			
		||||
@@ -65,6 +66,39 @@ const signalValues = [
 | 
			
		||||
  { value: SignalValue.SomeNumber, label: "#" },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// 通道组选项
 | 
			
		||||
const channelDivOptions = [
 | 
			
		||||
  { value: 1, label: "1通道", description: "启用1个通道 (CH0)" },
 | 
			
		||||
  { value: 2, label: "2通道", description: "启用2个通道 (CH0-CH1)" },
 | 
			
		||||
  { value: 4, label: "4通道", description: "启用4个通道 (CH0-CH3)" },
 | 
			
		||||
  { value: 8, label: "8通道", description: "启用8个通道 (CH0-CH7)" },
 | 
			
		||||
  { value: 16, label: "16通道", description: "启用16个通道 (CH0-CH15)" },
 | 
			
		||||
  { value: 32, label: "32通道", description: "启用32个通道 (CH0-CH31)" },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// 捕获深度选项
 | 
			
		||||
const captureLengthOptions = [
 | 
			
		||||
  { value: 256, label: "256" },
 | 
			
		||||
  { value: 512, label: "512" },
 | 
			
		||||
  { value: 1024, label: "1K" },
 | 
			
		||||
  { value: 2048, label: "2K" },
 | 
			
		||||
  { value: 4096, label: "4K" },
 | 
			
		||||
  { value: 8192, label: "8K" },
 | 
			
		||||
  { value: 16384, label: "16K" },
 | 
			
		||||
  { value: 32768, label: "32K" },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// 预捕获深度选项
 | 
			
		||||
const preCaptureLengthOptions = [
 | 
			
		||||
  { value: 0, label: "0" },
 | 
			
		||||
  { value: 16, label: "16" },
 | 
			
		||||
  { value: 32, label: "32" },
 | 
			
		||||
  { value: 64, label: "64" },
 | 
			
		||||
  { value: 128, label: "128" },
 | 
			
		||||
  { value: 256, label: "256" },
 | 
			
		||||
  { value: 512, label: "512" },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// 默认颜色数组
 | 
			
		||||
const defaultColors = [
 | 
			
		||||
  "#FF5733",
 | 
			
		||||
@@ -78,7 +112,7 @@ const defaultColors = [
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
// 添加逻辑分析仪频率常量
 | 
			
		||||
const LOGIC_ANALYZER_FREQUENCY = 5_000_000; // 5MHz
 | 
			
		||||
const LOGIC_ANALYZER_FREQUENCY = 125_000_000; // 125MHz
 | 
			
		||||
const SAMPLE_PERIOD_NS = 1_000_000_000 / LOGIC_ANALYZER_FREQUENCY; // 采样周期,单位:纳秒
 | 
			
		||||
 | 
			
		||||
const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
 | 
			
		||||
@@ -91,22 +125,25 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
 | 
			
		||||
 | 
			
		||||
    // 触发设置相关状态
 | 
			
		||||
    const currentGlobalMode = ref<GlobalCaptureMode>(GlobalCaptureMode.AND);
 | 
			
		||||
    const currentChannelDiv = ref<number>(8); // 默认启用8个通道
 | 
			
		||||
    const captureLength = ref<number>(1024); // 捕获深度,默认1024
 | 
			
		||||
    const preCaptureLength = ref<number>(0); // 预捕获深度,默认0
 | 
			
		||||
    const isApplying = ref(false);
 | 
			
		||||
    const isCapturing = ref(false); // 添加捕获状态标识
 | 
			
		||||
 | 
			
		||||
    // 通道配置
 | 
			
		||||
    const channels = reactive<Channel[]>(
 | 
			
		||||
      Array.from({ length: 8 }, (_, index) => ({
 | 
			
		||||
        enabled: false,
 | 
			
		||||
      Array.from({ length: 32 }, (_, index) => ({
 | 
			
		||||
        enabled: index < 8, // 默认启用前8个通道
 | 
			
		||||
        label: `CH${index}`,
 | 
			
		||||
        color: defaultColors[index],
 | 
			
		||||
        color: defaultColors[index % defaultColors.length], // 使用模运算避免数组越界
 | 
			
		||||
      })),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // 8个信号通道的配置
 | 
			
		||||
    // 32个信号通道的配置
 | 
			
		||||
    const signalConfigs = reactive<SignalTriggerConfig[]>(
 | 
			
		||||
      Array.from(
 | 
			
		||||
        { length: 8 },
 | 
			
		||||
        { length: 32 },
 | 
			
		||||
        (_, index) =>
 | 
			
		||||
          new SignalTriggerConfig({
 | 
			
		||||
            signalIndex: index,
 | 
			
		||||
@@ -131,101 +168,52 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
 | 
			
		||||
      channels.filter((channel) => channel.enabled),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const enableAllChannels = () => {
 | 
			
		||||
      channels.forEach((channel) => {
 | 
			
		||||
        channel.enabled = true;
 | 
			
		||||
      });
 | 
			
		||||
    // 转换通道数字到枚举值
 | 
			
		||||
    const getChannelDivEnum = (channelCount: number): AnalyzerChannelDiv => {
 | 
			
		||||
      switch (channelCount) {
 | 
			
		||||
        case 1: return AnalyzerChannelDiv.ONE;
 | 
			
		||||
        case 2: return AnalyzerChannelDiv.TWO;
 | 
			
		||||
        case 4: return AnalyzerChannelDiv.FOUR;
 | 
			
		||||
        case 8: return AnalyzerChannelDiv.EIGHT;
 | 
			
		||||
        case 16: return AnalyzerChannelDiv.XVI;
 | 
			
		||||
        case 32: return AnalyzerChannelDiv.XXXII;
 | 
			
		||||
        default: return AnalyzerChannelDiv.EIGHT;
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const disableAllChannels = () => {
 | 
			
		||||
    // 设置通道组
 | 
			
		||||
    const setChannelDiv = (channelCount: number) => {
 | 
			
		||||
      // 验证通道数量是否有效
 | 
			
		||||
      if (!channelDivOptions.find(option => option.value === channelCount)) {
 | 
			
		||||
        console.error(`无效的通道组设置: ${channelCount}`);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      currentChannelDiv.value = channelCount;
 | 
			
		||||
      
 | 
			
		||||
      // 禁用所有通道
 | 
			
		||||
      channels.forEach((channel) => {
 | 
			
		||||
        channel.enabled = false;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      // 启用指定数量的通道(从CH0开始)
 | 
			
		||||
      for (let i = 0; i < channelCount && i < channels.length; i++) {
 | 
			
		||||
        channels[i].enabled = true;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const option = channelDivOptions.find(opt => opt.value === channelCount);
 | 
			
		||||
      alert?.success(`已设置为${option?.label}`, 2000);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const setGlobalMode = async (mode: GlobalCaptureMode) => {
 | 
			
		||||
      // 检查是否有其他操作正在进行
 | 
			
		||||
      if (operationMutex.isLocked()) {
 | 
			
		||||
        alert.warn("有其他操作正在进行中,请稍后再试", 3000);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const release = await operationMutex.acquire();
 | 
			
		||||
      try {
 | 
			
		||||
        const client = AuthManager.createAuthenticatedLogicAnalyzerClient();
 | 
			
		||||
        const success = await client.setGlobalTrigMode(mode);
 | 
			
		||||
 | 
			
		||||
        if (success) {
 | 
			
		||||
          currentGlobalMode.value = mode;
 | 
			
		||||
          alert?.success(
 | 
			
		||||
            `全局触发模式已设置为 ${globalModes.find((m) => m.value === mode)?.label}`,
 | 
			
		||||
            3000,
 | 
			
		||||
          );
 | 
			
		||||
        } else {
 | 
			
		||||
          throw new Error("设置失败");
 | 
			
		||||
        }
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        console.error("设置全局触发模式失败:", error);
 | 
			
		||||
        alert?.error("设置全局触发模式失败", 3000);
 | 
			
		||||
      } finally {
 | 
			
		||||
        release();
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const applyConfiguration = async () => {
 | 
			
		||||
      // 检查是否有其他操作正在进行
 | 
			
		||||
      if (operationMutex.isLocked()) {
 | 
			
		||||
        alert.warn("有其他操作正在进行中,请稍后再试", 3000);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const release = await operationMutex.acquire();
 | 
			
		||||
      isApplying.value = true;
 | 
			
		||||
 | 
			
		||||
      try {
 | 
			
		||||
        const client = AuthManager.createAuthenticatedLogicAnalyzerClient();
 | 
			
		||||
 | 
			
		||||
        // 准备配置数据 - 只包含启用的通道
 | 
			
		||||
        const enabledSignals = signalConfigs.filter(
 | 
			
		||||
          (signal, index) => channels[index].enabled,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        const config = new CaptureConfig({
 | 
			
		||||
          globalMode: currentGlobalMode.value,
 | 
			
		||||
          signalConfigs: enabledSignals,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // 发送配置
 | 
			
		||||
        const success = await client.configureCapture(config);
 | 
			
		||||
 | 
			
		||||
        if (success) {
 | 
			
		||||
          const enabledChannelCount = channels.filter(
 | 
			
		||||
            (ch) => ch.enabled,
 | 
			
		||||
          ).length;
 | 
			
		||||
          alert?.success(
 | 
			
		||||
            `配置已成功应用,启用了 ${enabledChannelCount} 个通道和触发条件`,
 | 
			
		||||
            3000,
 | 
			
		||||
          );
 | 
			
		||||
        } else {
 | 
			
		||||
          throw new Error("应用配置失败");
 | 
			
		||||
        }
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        console.error("应用配置失败:", error);
 | 
			
		||||
        alert?.error("应用配置失败,请检查设备连接", 3000);
 | 
			
		||||
      } finally {
 | 
			
		||||
        isApplying.value = false;
 | 
			
		||||
        release();
 | 
			
		||||
      }
 | 
			
		||||
    const setGlobalMode = (mode: GlobalCaptureMode) => {
 | 
			
		||||
      currentGlobalMode.value = mode;
 | 
			
		||||
      const modeOption = globalModes.find((m) => m.value === mode);
 | 
			
		||||
      alert?.info(`全局触发模式已设置为 ${modeOption?.label}`, 2000);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const resetConfiguration = () => {
 | 
			
		||||
      currentGlobalMode.value = GlobalCaptureMode.AND;
 | 
			
		||||
 | 
			
		||||
      channels.forEach((channel, index) => {
 | 
			
		||||
        channel.enabled = false;
 | 
			
		||||
        channel.label = `CH${index}`;
 | 
			
		||||
        channel.color = defaultColors[index];
 | 
			
		||||
      });
 | 
			
		||||
      currentChannelDiv.value = 8; // 重置为默认的8通道
 | 
			
		||||
      setChannelDiv(8); // 重置为默认的8通道
 | 
			
		||||
 | 
			
		||||
      signalConfigs.forEach((signal) => {
 | 
			
		||||
        signal.operator = SignalOperator.Equal;
 | 
			
		||||
@@ -243,51 +231,223 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
 | 
			
		||||
    const getCaptureData = async () => {
 | 
			
		||||
      try {
 | 
			
		||||
        const client = AuthManager.createAuthenticatedLogicAnalyzerClient();
 | 
			
		||||
        // 3. 获取捕获数据
 | 
			
		||||
        const base64Data = await client.getCaptureData();
 | 
			
		||||
        // 获取捕获数据,使用当前设置的捕获长度
 | 
			
		||||
        const base64Data = await client.getCaptureData(captureLength.value);
 | 
			
		||||
 | 
			
		||||
        // 4. 将base64数据转换为bytes
 | 
			
		||||
        // 将base64数据转换为bytes
 | 
			
		||||
        const binaryString = atob(base64Data);
 | 
			
		||||
        const bytes = new Uint8Array(binaryString.length);
 | 
			
		||||
        for (let i = 0; i < binaryString.length; i++) {
 | 
			
		||||
          bytes[i] = binaryString.charCodeAt(i);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 5. 解析数据为8个通道的数字信号
 | 
			
		||||
        const sampleCount = bytes.length;
 | 
			
		||||
        const timeStepNs = SAMPLE_PERIOD_NS; // 每个采样点间隔200ns (1/5MHz)
 | 
			
		||||
        // 根据当前通道数量解析数据
 | 
			
		||||
        const channelCount = currentChannelDiv.value;
 | 
			
		||||
        const timeStepNs = SAMPLE_PERIOD_NS;
 | 
			
		||||
        
 | 
			
		||||
        let sampleCount: number;
 | 
			
		||||
        let x: number[];
 | 
			
		||||
        let y: number[][];
 | 
			
		||||
 | 
			
		||||
        // 创建时间轴(转换为合适的单位)
 | 
			
		||||
        const x = Array.from(
 | 
			
		||||
          { length: sampleCount },
 | 
			
		||||
          (_, i) => (i * timeStepNs) / 1000,
 | 
			
		||||
        ); // 转换为微秒
 | 
			
		||||
 | 
			
		||||
        // 创建8个通道的数据
 | 
			
		||||
        const y: number[][] = Array.from(
 | 
			
		||||
          { length: 8 },
 | 
			
		||||
          () => new Array(sampleCount),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // 解析每个字节的8个位到对应通道
 | 
			
		||||
        for (let i = 0; i < sampleCount; i++) {
 | 
			
		||||
          const byte = bytes[i];
 | 
			
		||||
          for (let channel = 0; channel < 8; channel++) {
 | 
			
		||||
            // bit0对应ch0, bit1对应ch1, ..., bit7对应ch7
 | 
			
		||||
            y[channel][i] = (byte >> channel) & 1;
 | 
			
		||||
        if (channelCount === 1) {
 | 
			
		||||
          // 1通道:每个字节包含8个时间单位的数据
 | 
			
		||||
          sampleCount = bytes.length * 8;
 | 
			
		||||
          
 | 
			
		||||
          // 创建时间轴
 | 
			
		||||
          x = Array.from(
 | 
			
		||||
            { length: sampleCount },
 | 
			
		||||
            (_, i) => (i * timeStepNs) / 1000,
 | 
			
		||||
          ); // 转换为微秒
 | 
			
		||||
          
 | 
			
		||||
          // 创建通道数据数组
 | 
			
		||||
          y = Array.from(
 | 
			
		||||
            { length: 1 },
 | 
			
		||||
            () => new Array(sampleCount),
 | 
			
		||||
          );
 | 
			
		||||
          
 | 
			
		||||
          // 解析数据:每个字节的8个位对应8个时间单位
 | 
			
		||||
          for (let byteIndex = 0; byteIndex < bytes.length; byteIndex++) {
 | 
			
		||||
            const byte = bytes[byteIndex];
 | 
			
		||||
            for (let bitIndex = 0; bitIndex < 8; bitIndex++) {
 | 
			
		||||
              const timeIndex = byteIndex * 8 + bitIndex;
 | 
			
		||||
              y[0][timeIndex] = (byte >> bitIndex) & 1;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        } else if (channelCount === 2) {
 | 
			
		||||
          // 2通道:每个字节包含4个时间单位的数据
 | 
			
		||||
          sampleCount = bytes.length * 4;
 | 
			
		||||
          
 | 
			
		||||
          // 创建时间轴
 | 
			
		||||
          x = Array.from(
 | 
			
		||||
            { length: sampleCount },
 | 
			
		||||
            (_, i) => (i * timeStepNs) / 1000,
 | 
			
		||||
          ); // 转换为微秒
 | 
			
		||||
          
 | 
			
		||||
          // 创建通道数据数组
 | 
			
		||||
          y = Array.from(
 | 
			
		||||
            { length: 2 },
 | 
			
		||||
            () => new Array(sampleCount),
 | 
			
		||||
          );
 | 
			
		||||
          
 | 
			
		||||
          // 解析数据:每个字节的8个位对应4个时间单位的2通道数据
 | 
			
		||||
          // 位分布:[T3_CH1, T3_CH0, T2_CH1, T2_CH0, T1_CH1, T1_CH0, T0_CH1, T0_CH0]
 | 
			
		||||
          for (let byteIndex = 0; byteIndex < bytes.length; byteIndex++) {
 | 
			
		||||
            const byte = bytes[byteIndex];
 | 
			
		||||
            for (let timeUnit = 0; timeUnit < 4; timeUnit++) {
 | 
			
		||||
              const timeIndex = byteIndex * 4 + timeUnit;
 | 
			
		||||
              const bitOffset = timeUnit * 2;
 | 
			
		||||
              y[0][timeIndex] = (byte >> bitOffset) & 1;       // CH0
 | 
			
		||||
              y[1][timeIndex] = (byte >> (bitOffset + 1)) & 1; // CH1
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        } else if (channelCount === 4) {
 | 
			
		||||
          // 4通道:每个字节包含2个时间单位的数据
 | 
			
		||||
          sampleCount = bytes.length * 2;
 | 
			
		||||
          
 | 
			
		||||
          // 创建时间轴
 | 
			
		||||
          x = Array.from(
 | 
			
		||||
            { length: sampleCount },
 | 
			
		||||
            (_, i) => (i * timeStepNs) / 1000,
 | 
			
		||||
          ); // 转换为微秒
 | 
			
		||||
          
 | 
			
		||||
          // 创建通道数据数组
 | 
			
		||||
          y = Array.from(
 | 
			
		||||
            { length: 4 },
 | 
			
		||||
            () => new Array(sampleCount),
 | 
			
		||||
          );
 | 
			
		||||
          
 | 
			
		||||
          // 解析数据:每个字节的8个位对应2个时间单位的4通道数据
 | 
			
		||||
          // 位分布:[T1_CH3, T1_CH2, T1_CH1, T1_CH0, T0_CH3, T0_CH2, T0_CH1, T0_CH0]
 | 
			
		||||
          for (let byteIndex = 0; byteIndex < bytes.length; byteIndex++) {
 | 
			
		||||
            const byte = bytes[byteIndex];
 | 
			
		||||
            
 | 
			
		||||
            // 处理第一个时间单位(低4位)
 | 
			
		||||
            const timeIndex1 = byteIndex * 2;
 | 
			
		||||
            for (let channel = 0; channel < 4; channel++) {
 | 
			
		||||
              y[channel][timeIndex1] = (byte >> channel) & 1;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // 处理第二个时间单位(高4位)
 | 
			
		||||
            const timeIndex2 = byteIndex * 2 + 1;
 | 
			
		||||
            for (let channel = 0; channel < 4; channel++) {
 | 
			
		||||
              y[channel][timeIndex2] = (byte >> (channel + 4)) & 1;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        } else if (channelCount === 8) {
 | 
			
		||||
          // 8通道:每个字节包含1个时间单位的8个通道数据
 | 
			
		||||
          sampleCount = bytes.length;
 | 
			
		||||
          
 | 
			
		||||
          // 创建时间轴
 | 
			
		||||
          x = Array.from(
 | 
			
		||||
            { length: sampleCount },
 | 
			
		||||
            (_, i) => (i * timeStepNs) / 1000,
 | 
			
		||||
          ); // 转换为微秒
 | 
			
		||||
          
 | 
			
		||||
          // 创建8个通道的数据
 | 
			
		||||
          y = Array.from(
 | 
			
		||||
            { length: 8 },
 | 
			
		||||
            () => new Array(sampleCount),
 | 
			
		||||
          );
 | 
			
		||||
          
 | 
			
		||||
          // 解析每个字节的8个位到对应通道
 | 
			
		||||
          for (let i = 0; i < sampleCount; i++) {
 | 
			
		||||
            const byte = bytes[i];
 | 
			
		||||
            for (let channel = 0; channel < 8; channel++) {
 | 
			
		||||
              // bit0对应ch0, bit1对应ch1, ..., bit7对应ch7
 | 
			
		||||
              y[channel][i] = (byte >> channel) & 1;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        } else if (channelCount === 16) {
 | 
			
		||||
          // 16通道:每2个字节包含1个时间单位的16个通道数据
 | 
			
		||||
          sampleCount = bytes.length / 2;
 | 
			
		||||
          
 | 
			
		||||
          // 创建时间轴
 | 
			
		||||
          x = Array.from(
 | 
			
		||||
            { length: sampleCount },
 | 
			
		||||
            (_, i) => (i * timeStepNs) / 1000,
 | 
			
		||||
          ); // 转换为微秒
 | 
			
		||||
          
 | 
			
		||||
          // 创建16个通道的数据
 | 
			
		||||
          y = Array.from(
 | 
			
		||||
            { length: 16 },
 | 
			
		||||
            () => new Array(sampleCount),
 | 
			
		||||
          );
 | 
			
		||||
          
 | 
			
		||||
          // 解析数据:每2个字节为一个时间单位
 | 
			
		||||
          for (let timeIndex = 0; timeIndex < sampleCount; timeIndex++) {
 | 
			
		||||
            const byteIndex = timeIndex * 2;
 | 
			
		||||
            const byte1 = bytes[byteIndex];     // [7:0]
 | 
			
		||||
            const byte2 = bytes[byteIndex + 1]; // [15:8]
 | 
			
		||||
            
 | 
			
		||||
            // 处理低8位通道 [7:0]
 | 
			
		||||
            for (let channel = 0; channel < 8; channel++) {
 | 
			
		||||
              y[channel][timeIndex] = (byte1 >> channel) & 1;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // 处理高8位通道 [15:8]
 | 
			
		||||
            for (let channel = 0; channel < 8; channel++) {
 | 
			
		||||
              y[channel + 8][timeIndex] = (byte2 >> channel) & 1;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        } else if (channelCount === 32) {
 | 
			
		||||
          // 32通道:每4个字节包含1个时间单位的32个通道数据
 | 
			
		||||
          sampleCount = bytes.length / 4;
 | 
			
		||||
          
 | 
			
		||||
          // 创建时间轴
 | 
			
		||||
          x = Array.from(
 | 
			
		||||
            { length: sampleCount },
 | 
			
		||||
            (_, i) => (i * timeStepNs) / 1000,
 | 
			
		||||
          ); // 转换为微秒
 | 
			
		||||
          
 | 
			
		||||
          // 创建32个通道的数据
 | 
			
		||||
          y = Array.from(
 | 
			
		||||
            { length: 32 },
 | 
			
		||||
            () => new Array(sampleCount),
 | 
			
		||||
          );
 | 
			
		||||
          
 | 
			
		||||
          // 解析数据:每4个字节为一个时间单位
 | 
			
		||||
          for (let timeIndex = 0; timeIndex < sampleCount; timeIndex++) {
 | 
			
		||||
            const byteIndex = timeIndex * 4;
 | 
			
		||||
            const byte1 = bytes[byteIndex];     // [7:0]
 | 
			
		||||
            const byte2 = bytes[byteIndex + 1]; // [15:8]
 | 
			
		||||
            const byte3 = bytes[byteIndex + 2]; // [23:16]
 | 
			
		||||
            const byte4 = bytes[byteIndex + 3]; // [31:24]
 | 
			
		||||
            
 | 
			
		||||
            // 处理 [7:0]
 | 
			
		||||
            for (let channel = 0; channel < 8; channel++) {
 | 
			
		||||
              y[channel][timeIndex] = (byte1 >> channel) & 1;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // 处理 [15:8]
 | 
			
		||||
            for (let channel = 0; channel < 8; channel++) {
 | 
			
		||||
              y[channel + 8][timeIndex] = (byte2 >> channel) & 1;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // 处理 [23:16]
 | 
			
		||||
            for (let channel = 0; channel < 8; channel++) {
 | 
			
		||||
              y[channel + 16][timeIndex] = (byte3 >> channel) & 1;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            // 处理 [31:24]
 | 
			
		||||
            for (let channel = 0; channel < 8; channel++) {
 | 
			
		||||
              y[channel + 24][timeIndex] = (byte4 >> channel) & 1;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          throw new Error(`不支持的通道数量: ${channelCount}`);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 6. 设置逻辑数据
 | 
			
		||||
        // 设置逻辑数据
 | 
			
		||||
        const logicData: LogicDataType = {
 | 
			
		||||
          x,
 | 
			
		||||
          y,
 | 
			
		||||
          xUnit: "us", // 改为微秒单位
 | 
			
		||||
          xUnit: "us", // 微秒单位
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        setLogicData(logicData);
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        console.error("获取捕获数据失败:", error);
 | 
			
		||||
        alert?.error("获取捕获数据失败", 3000);
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@@ -303,7 +463,45 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
 | 
			
		||||
      try {
 | 
			
		||||
        const client = AuthManager.createAuthenticatedLogicAnalyzerClient();
 | 
			
		||||
 | 
			
		||||
        // 1. 设置捕获模式为开始捕获
 | 
			
		||||
        // 1. 先应用配置
 | 
			
		||||
        alert?.info("正在应用配置...", 2000);
 | 
			
		||||
        
 | 
			
		||||
        // 准备配置数据 - 包含所有32个通道,未启用的通道设置为默认值
 | 
			
		||||
        const allSignals = signalConfigs.map((signal, index) => {
 | 
			
		||||
          if (channels[index].enabled) {
 | 
			
		||||
            // 启用的通道使用用户配置的触发条件
 | 
			
		||||
            return signal;
 | 
			
		||||
          } else {
 | 
			
		||||
            // 未启用的通道设置为默认触发条件
 | 
			
		||||
            return new SignalTriggerConfig({
 | 
			
		||||
              signalIndex: index,
 | 
			
		||||
              operator: SignalOperator.Equal,
 | 
			
		||||
              value: SignalValue.NotCare,
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const config = new CaptureConfig({
 | 
			
		||||
          globalMode: currentGlobalMode.value,
 | 
			
		||||
          channelDiv: getChannelDivEnum(currentChannelDiv.value),
 | 
			
		||||
          captureLength: captureLength.value,
 | 
			
		||||
          preCaptureLength: preCaptureLength.value,
 | 
			
		||||
          signalConfigs: allSignals,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // 发送配置
 | 
			
		||||
        const configSuccess = await client.configureCapture(config);
 | 
			
		||||
        if (!configSuccess) {
 | 
			
		||||
          throw new Error("配置应用失败");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const enabledChannelCount = channels.filter((ch) => ch.enabled).length;
 | 
			
		||||
        alert?.success(
 | 
			
		||||
          `配置已应用,启用了 ${enabledChannelCount} 个通道,捕获深度: ${captureLength.value}`,
 | 
			
		||||
          2000,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // 2. 设置捕获模式为开始捕获
 | 
			
		||||
        const captureStarted = await client.setCaptureMode(true, false);
 | 
			
		||||
        if (!captureStarted) {
 | 
			
		||||
          throw new Error("无法启动捕获");
 | 
			
		||||
@@ -311,7 +509,7 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
 | 
			
		||||
 | 
			
		||||
        alert?.info("开始捕获信号...", 2000);
 | 
			
		||||
 | 
			
		||||
        // 2. 轮询捕获状态
 | 
			
		||||
        // 3. 轮询捕获状态
 | 
			
		||||
        let captureCompleted = false;
 | 
			
		||||
        while (isCapturing.value) {
 | 
			
		||||
          const status = await client.getCaptureStatus();
 | 
			
		||||
@@ -390,8 +588,11 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const forceCapture = async () => {
 | 
			
		||||
      // 设置捕获状态为false,这会使轮询停止
 | 
			
		||||
      isCapturing.value = false;
 | 
			
		||||
      // 检查是否有其他操作正在进行
 | 
			
		||||
      if (operationMutex.isLocked()) {
 | 
			
		||||
        alert.warn("有其他操作正在进行中,请稍后再试", 3000);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const release = await operationMutex.acquire();
 | 
			
		||||
      try {
 | 
			
		||||
@@ -404,7 +605,7 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await getCaptureData();
 | 
			
		||||
        alert.success(`捕获完成!`, 3000);
 | 
			
		||||
        alert.success(`强制捕获完成!`, 3000);
 | 
			
		||||
      } catch (error) {
 | 
			
		||||
        console.error("强制捕获失败:", error);
 | 
			
		||||
        alert.error(
 | 
			
		||||
@@ -487,7 +688,7 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      // 设置逻辑数据
 | 
			
		||||
      enableAllChannels();
 | 
			
		||||
      setChannelDiv(8);
 | 
			
		||||
      setLogicData({ x, y, xUnit: "us" }); // 改为微秒单位
 | 
			
		||||
 | 
			
		||||
      alert?.success("测试数据生成成功", 2000);
 | 
			
		||||
@@ -499,6 +700,9 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
 | 
			
		||||
 | 
			
		||||
      // 触发设置状态
 | 
			
		||||
      currentGlobalMode,
 | 
			
		||||
      currentChannelDiv, // 导出当前通道组状态
 | 
			
		||||
      captureLength, // 导出捕获深度
 | 
			
		||||
      preCaptureLength, // 导出预捕获深度
 | 
			
		||||
      isApplying,
 | 
			
		||||
      isCapturing, // 导出捕获状态
 | 
			
		||||
      isOperationInProgress, // 导出操作进行状态
 | 
			
		||||
@@ -512,12 +716,13 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
 | 
			
		||||
      globalModes,
 | 
			
		||||
      operators,
 | 
			
		||||
      signalValues,
 | 
			
		||||
      channelDivOptions, // 导出通道组选项
 | 
			
		||||
      captureLengthOptions, // 导出捕获深度选项
 | 
			
		||||
      preCaptureLengthOptions, // 导出预捕获深度选项
 | 
			
		||||
 | 
			
		||||
      // 触发设置方法
 | 
			
		||||
      enableAllChannels,
 | 
			
		||||
      disableAllChannels,
 | 
			
		||||
      setChannelDiv, // 导出设置通道组方法
 | 
			
		||||
      setGlobalMode,
 | 
			
		||||
      applyConfiguration,
 | 
			
		||||
      resetConfiguration,
 | 
			
		||||
      setLogicData,
 | 
			
		||||
      startCapture,
 | 
			
		||||
 
 | 
			
		||||
@@ -21,60 +21,7 @@
 | 
			
		||||
        <h3 class="text-xl font-semibold text-slate-600 mb-2">
 | 
			
		||||
          暂无逻辑分析数据
 | 
			
		||||
        </h3>
 | 
			
		||||
        <p class="text-sm text-slate-500">点击下方按钮开始捕获</p>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <!-- <button
 | 
			
		||||
        class="group relative px-8 py-3 bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white font-medium rounded-lg shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-200 ease-in-out focus:outline-none focus:ring-4 focus:ring-blue-300 active:scale-95"
 | 
			
		||||
        @click="analyzer.generateTestData"
 | 
			
		||||
      >
 | 
			
		||||
        <span class="flex items-center gap-2">
 | 
			
		||||
          <RefreshCcw
 | 
			
		||||
            class="w-5 h-5 group-hover:rotate-180 transition-transform duration-300"
 | 
			
		||||
          />
 | 
			
		||||
          生成测试数据
 | 
			
		||||
        </span>
 | 
			
		||||
        <div
 | 
			
		||||
          class="absolute inset-0 bg-white opacity-0 group-hover:opacity-20 rounded-lg transition-opacity duration-200"
 | 
			
		||||
        ></div>
 | 
			
		||||
      </button> -->
 | 
			
		||||
      <button
 | 
			
		||||
        class="group relative px-8 py-3 bg-gradient-to-r text-white font-medium rounded-lg shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-200 ease-in-out focus:outline-none focus:ring-4 active:scale-95"
 | 
			
		||||
        :class="{
 | 
			
		||||
          'from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 focus:ring-blue-300':
 | 
			
		||||
            !analyzer.isCapturing.value,
 | 
			
		||||
          'from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 focus:ring-red-300':
 | 
			
		||||
            analyzer.isCapturing.value,
 | 
			
		||||
        }"
 | 
			
		||||
        @click="
 | 
			
		||||
          analyzer.isCapturing.value
 | 
			
		||||
            ? analyzer.stopCapture()
 | 
			
		||||
            : analyzer.startCapture()
 | 
			
		||||
        "
 | 
			
		||||
      >
 | 
			
		||||
        <span class="flex items-center gap-2">
 | 
			
		||||
          <template v-if="analyzer.isCapturing.value">
 | 
			
		||||
            <Square class="w-5 h-5" />
 | 
			
		||||
            停止捕获
 | 
			
		||||
          </template>
 | 
			
		||||
          <template v-else>
 | 
			
		||||
            <Play class="w-5 h-5" />
 | 
			
		||||
            开始捕获
 | 
			
		||||
          </template>
 | 
			
		||||
        </span>
 | 
			
		||||
      </button>
 | 
			
		||||
 | 
			
		||||
      <!-- 强制捕获按钮 - 只在正在捕获时显示 -->
 | 
			
		||||
      <button
 | 
			
		||||
        v-if="analyzer.isCapturing.value"
 | 
			
		||||
        class="group relative px-8 py-3 bg-gradient-to-r from-orange-500 to-orange-600 hover:from-orange-600 hover:to-orange-700 text-white font-medium rounded-lg shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-200 ease-in-out focus:outline-none focus:ring-4 focus:ring-orange-300 active:scale-95"
 | 
			
		||||
        @click="analyzer.forceCapture()"
 | 
			
		||||
      >
 | 
			
		||||
        <span class="flex items-center gap-2">
 | 
			
		||||
          <Square class="w-5 h-5" />
 | 
			
		||||
          强制捕获
 | 
			
		||||
        </span>
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -82,7 +29,6 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { computed, shallowRef } from "vue";
 | 
			
		||||
import VChart from "vue-echarts";
 | 
			
		||||
import { RefreshCcw, Play, Square } from "lucide-vue-next";
 | 
			
		||||
 | 
			
		||||
// Echarts
 | 
			
		||||
import { use } from "echarts/core";
 | 
			
		||||
 
 | 
			
		||||
@@ -1,345 +1,175 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="space-y-6">
 | 
			
		||||
    <!-- 通道状态概览 -->
 | 
			
		||||
    <div class="stats stats-horizontal bg-base-100 shadow flex justify-between">
 | 
			
		||||
      <div class="stat">
 | 
			
		||||
        <div class="stat-title">总通道数</div>
 | 
			
		||||
        <div class="stat-value text-primary">8</div>
 | 
			
		||||
        <div class="stat-desc">逻辑分析仪通道</div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="stat">
 | 
			
		||||
        <div class="stat-title">启用通道</div>
 | 
			
		||||
        <div class="stat-value text-success">{{ enabledChannelCount }}</div>
 | 
			
		||||
        <div class="stat-desc">当前激活通道</div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="stat">
 | 
			
		||||
        <div class="stat-title">采样率</div>
 | 
			
		||||
        <div class="stat-value text-info">5MHz</div>
 | 
			
		||||
        <div class="stat-desc">最大采样频率</div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <!-- 通道配置 -->
 | 
			
		||||
    <div class="form-control">
 | 
			
		||||
      <!-- 全局触发模式选择 -->
 | 
			
		||||
      <div class="flex flex-row justify-between my-4 mx-2">
 | 
			
		||||
        <div class="flex flex-row gap-4">
 | 
			
		||||
          <label class="label">
 | 
			
		||||
            <span class="label-text text-sm">全局触发逻辑</span>
 | 
			
		||||
          </label>
 | 
			
		||||
          <select
 | 
			
		||||
            v-model="currentGlobalMode"
 | 
			
		||||
            @change="setGlobalMode(currentGlobalMode)"
 | 
			
		||||
            class="select select-sm select-bordered w-full"
 | 
			
		||||
          >
 | 
			
		||||
            <option
 | 
			
		||||
              v-for="mode in globalModes"
 | 
			
		||||
              :key="mode.value"
 | 
			
		||||
              :value="mode.value"
 | 
			
		||||
      <!-- 全局触发模式选择和通道组配置 -->
 | 
			
		||||
      <div class="flex flex-col lg:flex-row justify-between gap-4 my-4 mx-2">
 | 
			
		||||
        <!-- 左侧:全局触发模式和通道组选择 -->
 | 
			
		||||
        <div class="flex flex-col lg:flex-row gap-4">
 | 
			
		||||
          <div class="flex flex-row gap-2 items-center">
 | 
			
		||||
            <label class="label">
 | 
			
		||||
              <span class="label-text text-sm">全局触发逻辑</span>
 | 
			
		||||
            </label>
 | 
			
		||||
            <select
 | 
			
		||||
              v-model="currentGlobalMode"
 | 
			
		||||
              @change="setGlobalMode(currentGlobalMode)"
 | 
			
		||||
              class="select select-sm select-bordered"
 | 
			
		||||
            >
 | 
			
		||||
              {{ mode.label }} - {{ mode.description }}
 | 
			
		||||
            </option>
 | 
			
		||||
          </select>
 | 
			
		||||
              <option
 | 
			
		||||
                v-for="mode in globalModes"
 | 
			
		||||
                :key="mode.value"
 | 
			
		||||
                :value="mode.value"
 | 
			
		||||
              >
 | 
			
		||||
                {{ mode.label }} - {{ mode.description }}
 | 
			
		||||
              </option>
 | 
			
		||||
            </select>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div class="flex flex-row gap-2 items-center">
 | 
			
		||||
            <label class="label">
 | 
			
		||||
              <span class="label-text text-sm">通道组</span>
 | 
			
		||||
            </label>
 | 
			
		||||
            <select
 | 
			
		||||
              v-model="currentChannelDiv"
 | 
			
		||||
              @change="setChannelDiv(currentChannelDiv)"
 | 
			
		||||
              class="select select-sm select-bordered"
 | 
			
		||||
            >
 | 
			
		||||
              <option
 | 
			
		||||
                v-for="option in channelDivOptions"
 | 
			
		||||
                :key="option.value"
 | 
			
		||||
                :value="option.value"
 | 
			
		||||
              >
 | 
			
		||||
                {{ option.label }}
 | 
			
		||||
              </option>
 | 
			
		||||
            </select>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div class="flex flex-row gap-2 items-center">
 | 
			
		||||
            <label class="label">
 | 
			
		||||
              <span class="label-text text-sm">捕获深度</span>
 | 
			
		||||
            </label>
 | 
			
		||||
            <select
 | 
			
		||||
              v-model="captureLength"
 | 
			
		||||
              class="select select-sm select-bordered"
 | 
			
		||||
            >
 | 
			
		||||
              <option
 | 
			
		||||
                v-for="option in captureLengthOptions"
 | 
			
		||||
                :key="option.value"
 | 
			
		||||
                :value="option.value"
 | 
			
		||||
              >
 | 
			
		||||
                {{ option.label }}
 | 
			
		||||
              </option>
 | 
			
		||||
            </select>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div class="flex flex-row gap-2 items-center">
 | 
			
		||||
            <label class="label">
 | 
			
		||||
              <span class="label-text text-sm">预捕获深度</span>
 | 
			
		||||
            </label>
 | 
			
		||||
            <select
 | 
			
		||||
              v-model="preCaptureLength"
 | 
			
		||||
              class="select select-sm select-bordered"
 | 
			
		||||
            >
 | 
			
		||||
              <option
 | 
			
		||||
                v-for="option in preCaptureLengthOptions"
 | 
			
		||||
                :key="option.value"
 | 
			
		||||
                :value="option.value"
 | 
			
		||||
              >
 | 
			
		||||
                {{ option.label }}
 | 
			
		||||
              </option>
 | 
			
		||||
            </select>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="flex flex-row gap-4">
 | 
			
		||||
          <button @click="toggleAllChannels" class="btn btn-primary btn-sm">
 | 
			
		||||
            {{ enabledChannelCount > 0 ? "全部禁用" : "全部启用" }}
 | 
			
		||||
          </button>
 | 
			
		||||
          <button
 | 
			
		||||
            @click="applyConfiguration"
 | 
			
		||||
            :disabled="isApplying"
 | 
			
		||||
            class="btn btn-primary btn-sm"
 | 
			
		||||
          >
 | 
			
		||||
            <span
 | 
			
		||||
              v-if="isApplying"
 | 
			
		||||
              class="loading loading-spinner loading-sm"
 | 
			
		||||
            ></span>
 | 
			
		||||
            应用配置
 | 
			
		||||
          </button>
 | 
			
		||||
 | 
			
		||||
        <!-- 右侧:操作按钮 -->
 | 
			
		||||
        <div class="flex flex-row gap-2">
 | 
			
		||||
          <button @click="resetConfiguration" class="btn btn-outline btn-sm">
 | 
			
		||||
            重置
 | 
			
		||||
            重置配置
 | 
			
		||||
          </button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <!-- 通道列表 -->
 | 
			
		||||
      <div class="space-y-2">
 | 
			
		||||
        <!-- 表头 - 小屏幕单列时显示 -->
 | 
			
		||||
        <!-- 表头 -->
 | 
			
		||||
        <div
 | 
			
		||||
          class="flex items-center gap-2 p-2 bg-base-300 rounded-lg text-sm font-medium lg:hidden"
 | 
			
		||||
          class="flex items-center gap-2 p-2 bg-base-300 rounded-lg text-sm font-medium"
 | 
			
		||||
        >
 | 
			
		||||
          <span class="w-16">通道</span>
 | 
			
		||||
          <span class="w-20">启用/触发</span>
 | 
			
		||||
          <span class="w-32">标签</span>
 | 
			
		||||
          <span class="w-16">颜色</span>
 | 
			
		||||
          <span class="w-32">触发操作</span>
 | 
			
		||||
          <span class="w-32">触发值</span>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <!-- 通道配置网格 -->
 | 
			
		||||
        <div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
 | 
			
		||||
          <!-- 左列 (CH0-CH3) -->
 | 
			
		||||
          <div class="space-y-2">
 | 
			
		||||
            <!-- 左列表头 - 大屏幕时显示 -->
 | 
			
		||||
            <div
 | 
			
		||||
              class="hidden lg:flex items-center gap-2 p-2 bg-base-300 rounded-lg text-sm font-medium"
 | 
			
		||||
            >
 | 
			
		||||
              <span class="w-16">通道</span>
 | 
			
		||||
              <span class="w-20">启用</span>
 | 
			
		||||
              <span class="w-32">标签</span>
 | 
			
		||||
              <span class="w-16">颜色</span>
 | 
			
		||||
              <span class="w-32">触发操作</span>
 | 
			
		||||
              <span class="w-32">触发值</span>
 | 
			
		||||
        <!-- 通道配置网格 - 根据当前通道组动态显示 -->
 | 
			
		||||
        <div class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-4">
 | 
			
		||||
          <div
 | 
			
		||||
            v-for="(channel, index) in channels.filter(ch => ch.enabled)"
 | 
			
		||||
            :key="index"
 | 
			
		||||
            class="flex items-center gap-2 p-3 bg-base-200 rounded-lg hover:bg-base-300 transition-colors"
 | 
			
		||||
          >
 | 
			
		||||
            <!-- 通道编号和颜色指示 -->
 | 
			
		||||
            <div class="flex items-center gap-2 w-16">
 | 
			
		||||
              <span class="font-mono font-medium">CH{{ channels.indexOf(channel) }}</span>
 | 
			
		||||
              <div
 | 
			
		||||
                class="w-3 h-3 rounded-full border-2 border-white shadow-sm"
 | 
			
		||||
                :style="{ backgroundColor: channel.color }"
 | 
			
		||||
              ></div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <!-- 左列通道 (0-3) -->
 | 
			
		||||
            <div
 | 
			
		||||
              v-for="(channel, index) in channels.slice(0, 4)"
 | 
			
		||||
              :key="index"
 | 
			
		||||
              class="flex items-center gap-2 p-3 bg-base-200 rounded-lg hover:bg-base-300 transition-colors"
 | 
			
		||||
            >
 | 
			
		||||
              <!-- 通道编号和颜色指示 -->
 | 
			
		||||
              <div class="flex items-center gap-2 w-16">
 | 
			
		||||
                <span class="font-mono font-medium">CH{{ index }}</span>
 | 
			
		||||
                <div
 | 
			
		||||
                  class="w-3 h-3 rounded-full border-2 border-white shadow-sm"
 | 
			
		||||
                  :style="{ backgroundColor: channel.color }"
 | 
			
		||||
                ></div>
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <!-- 通道启用开关(同时控制触发) -->
 | 
			
		||||
              <div class="form-control w-20">
 | 
			
		||||
                <input
 | 
			
		||||
                  type="checkbox"
 | 
			
		||||
                  v-model="channel.enabled"
 | 
			
		||||
                  class="toggle toggle-sm toggle-primary"
 | 
			
		||||
                />
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <!-- 通道标签 -->
 | 
			
		||||
              <div class="form-control w-32">
 | 
			
		||||
                <input
 | 
			
		||||
                  type="text"
 | 
			
		||||
                  v-model="channel.label"
 | 
			
		||||
                  :placeholder="`通道 ${index}`"
 | 
			
		||||
                  class="input input-sm input-bordered w-full"
 | 
			
		||||
                  :disabled="!channel.enabled"
 | 
			
		||||
                />
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <!-- 颜色选择 -->
 | 
			
		||||
              <div class="form-control w-16">
 | 
			
		||||
                <input
 | 
			
		||||
                  type="color"
 | 
			
		||||
                  v-model="channel.color"
 | 
			
		||||
                  class="w-8 h-8 rounded border-2 border-base-300 cursor-pointer"
 | 
			
		||||
                  :disabled="!channel.enabled"
 | 
			
		||||
                />
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <!-- 触发操作符选择 -->
 | 
			
		||||
              <select
 | 
			
		||||
                v-model="signalConfigs[index].operator"
 | 
			
		||||
                class="select select-sm select-bordered w-32"
 | 
			
		||||
                :disabled="!channel.enabled"
 | 
			
		||||
              >
 | 
			
		||||
                <option
 | 
			
		||||
                  v-for="op in operators"
 | 
			
		||||
                  :key="op.value"
 | 
			
		||||
                  :value="op.value"
 | 
			
		||||
                >
 | 
			
		||||
                  {{ op.label }}
 | 
			
		||||
                </option>
 | 
			
		||||
              </select>
 | 
			
		||||
 | 
			
		||||
              <!-- 触发信号值选择 -->
 | 
			
		||||
              <select
 | 
			
		||||
                v-model="signalConfigs[index].value"
 | 
			
		||||
                class="select select-sm select-bordered w-32"
 | 
			
		||||
                :disabled="!channel.enabled"
 | 
			
		||||
              >
 | 
			
		||||
                <option
 | 
			
		||||
                  v-for="val in signalValues"
 | 
			
		||||
                  :key="val.value"
 | 
			
		||||
                  :value="val.value"
 | 
			
		||||
                >
 | 
			
		||||
                  {{ val.label }}
 | 
			
		||||
                </option>
 | 
			
		||||
              </select>
 | 
			
		||||
            <!-- 通道标签 -->
 | 
			
		||||
            <div class="form-control w-32">
 | 
			
		||||
              <input
 | 
			
		||||
                type="text"
 | 
			
		||||
                v-model="channel.label"
 | 
			
		||||
                :placeholder="`通道 ${channels.indexOf(channel)}`"
 | 
			
		||||
                class="input input-sm input-bordered w-full"
 | 
			
		||||
              />
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <!-- 颜色选择 -->
 | 
			
		||||
            <div class="form-control w-16">
 | 
			
		||||
              <input
 | 
			
		||||
                type="color"
 | 
			
		||||
                v-model="channel.color"
 | 
			
		||||
                class="w-8 h-8 rounded border-2 border-base-300 cursor-pointer"
 | 
			
		||||
              />
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <!-- 触发操作符选择 -->
 | 
			
		||||
            <select
 | 
			
		||||
              v-model="signalConfigs[channels.indexOf(channel)].operator"
 | 
			
		||||
              class="select select-sm select-bordered w-32"
 | 
			
		||||
            >
 | 
			
		||||
              <option
 | 
			
		||||
                v-for="op in operators"
 | 
			
		||||
                :key="op.value"
 | 
			
		||||
                :value="op.value"
 | 
			
		||||
              >
 | 
			
		||||
                {{ op.label }}
 | 
			
		||||
              </option>
 | 
			
		||||
            </select>
 | 
			
		||||
 | 
			
		||||
            <!-- 触发信号值选择 -->
 | 
			
		||||
            <select
 | 
			
		||||
              v-model="signalConfigs[channels.indexOf(channel)].value"
 | 
			
		||||
              class="select select-sm select-bordered w-32"
 | 
			
		||||
            >
 | 
			
		||||
              <option
 | 
			
		||||
                v-for="val in signalValues"
 | 
			
		||||
                :key="val.value"
 | 
			
		||||
                :value="val.value"
 | 
			
		||||
              >
 | 
			
		||||
                {{ val.label }}
 | 
			
		||||
              </option>
 | 
			
		||||
            </select>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
          <!-- 右列 (CH4-CH7) - 仅在大屏幕显示 -->
 | 
			
		||||
          <div class="hidden lg:block space-y-2">
 | 
			
		||||
            <!-- 右列表头 -->
 | 
			
		||||
            <div
 | 
			
		||||
              class="flex items-center gap-2 p-2 bg-base-300 rounded-lg text-sm font-medium"
 | 
			
		||||
            >
 | 
			
		||||
              <span class="w-16">通道</span>
 | 
			
		||||
              <span class="w-20">启用/触发</span>
 | 
			
		||||
              <span class="w-32">标签</span>
 | 
			
		||||
              <span class="w-16">颜色</span>
 | 
			
		||||
              <span class="w-32">触发操作</span>
 | 
			
		||||
              <span class="w-32">触发值</span>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <!-- 右列通道 (4-7) -->
 | 
			
		||||
            <div
 | 
			
		||||
              v-for="(channel, index) in channels.slice(4, 8)"
 | 
			
		||||
              :key="index + 4"
 | 
			
		||||
              class="flex items-center gap-2 p-3 bg-base-200 rounded-lg hover:bg-base-300 transition-colors"
 | 
			
		||||
            >
 | 
			
		||||
              <!-- 通道编号和颜色指示 -->
 | 
			
		||||
              <div class="flex items-center gap-2 w-16">
 | 
			
		||||
                <span class="font-mono font-medium">CH{{ index + 4 }}</span>
 | 
			
		||||
                <div
 | 
			
		||||
                  class="w-3 h-3 rounded-full border-2 border-white shadow-sm"
 | 
			
		||||
                  :style="{ backgroundColor: channel.color }"
 | 
			
		||||
                ></div>
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <!-- 通道启用开关(同时控制触发) -->
 | 
			
		||||
              <div class="form-control w-20">
 | 
			
		||||
                <input
 | 
			
		||||
                  type="checkbox"
 | 
			
		||||
                  v-model="channel.enabled"
 | 
			
		||||
                  class="toggle toggle-sm toggle-primary"
 | 
			
		||||
                />
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <!-- 通道标签 -->
 | 
			
		||||
              <div class="form-control w-32">
 | 
			
		||||
                <input
 | 
			
		||||
                  type="text"
 | 
			
		||||
                  v-model="channel.label"
 | 
			
		||||
                  :placeholder="`通道 ${index + 4}`"
 | 
			
		||||
                  class="input input-sm input-bordered w-full"
 | 
			
		||||
                  :disabled="!channel.enabled"
 | 
			
		||||
                />
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <!-- 颜色选择 -->
 | 
			
		||||
              <div class="form-control w-16">
 | 
			
		||||
                <input
 | 
			
		||||
                  type="color"
 | 
			
		||||
                  v-model="channel.color"
 | 
			
		||||
                  class="w-8 h-8 rounded border-2 border-base-300 cursor-pointer"
 | 
			
		||||
                  :disabled="!channel.enabled"
 | 
			
		||||
                />
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <!-- 触发操作符选择 -->
 | 
			
		||||
              <select
 | 
			
		||||
                v-model="signalConfigs[index + 4].operator"
 | 
			
		||||
                class="select select-sm select-bordered w-32"
 | 
			
		||||
                :disabled="!channel.enabled"
 | 
			
		||||
              >
 | 
			
		||||
                <option
 | 
			
		||||
                  v-for="op in operators"
 | 
			
		||||
                  :key="op.value"
 | 
			
		||||
                  :value="op.value"
 | 
			
		||||
                >
 | 
			
		||||
                  {{ op.label }}
 | 
			
		||||
                </option>
 | 
			
		||||
              </select>
 | 
			
		||||
 | 
			
		||||
              <!-- 触发信号值选择 -->
 | 
			
		||||
              <select
 | 
			
		||||
                v-model="signalConfigs[index + 4].value"
 | 
			
		||||
                class="select select-sm select-bordered w-32"
 | 
			
		||||
                :disabled="!channel.enabled"
 | 
			
		||||
              >
 | 
			
		||||
                <option
 | 
			
		||||
                  v-for="val in signalValues"
 | 
			
		||||
                  :key="val.value"
 | 
			
		||||
                  :value="val.value"
 | 
			
		||||
                >
 | 
			
		||||
                  {{ val.label }}
 | 
			
		||||
                </option>
 | 
			
		||||
              </select>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <!-- 小屏幕时继续显示 CH4-CH7 -->
 | 
			
		||||
          <div class="lg:hidden space-y-2">
 | 
			
		||||
            <div
 | 
			
		||||
              v-for="(channel, index) in channels.slice(4, 8)"
 | 
			
		||||
              :key="index + 4"
 | 
			
		||||
              class="flex items-center gap-2 p-3 bg-base-200 rounded-lg hover:bg-base-300 transition-colors"
 | 
			
		||||
            >
 | 
			
		||||
              <!-- 通道编号和颜色指示 -->
 | 
			
		||||
              <div class="flex items-center gap-2 w-16">
 | 
			
		||||
                <span class="font-mono font-medium">CH{{ index + 4 }}</span>
 | 
			
		||||
                <div
 | 
			
		||||
                  class="w-3 h-3 rounded-full border-2 border-white shadow-sm"
 | 
			
		||||
                  :style="{ backgroundColor: channel.color }"
 | 
			
		||||
                ></div>
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <!-- 通道启用开关(同时控制触发) -->
 | 
			
		||||
              <div class="form-control w-20">
 | 
			
		||||
                <input
 | 
			
		||||
                  type="checkbox"
 | 
			
		||||
                  v-model="channel.enabled"
 | 
			
		||||
                  class="toggle toggle-sm toggle-primary"
 | 
			
		||||
                />
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <!-- 通道标签 -->
 | 
			
		||||
              <div class="form-control w-32">
 | 
			
		||||
                <input
 | 
			
		||||
                  type="text"
 | 
			
		||||
                  v-model="channel.label"
 | 
			
		||||
                  :placeholder="`通道 ${index + 4}`"
 | 
			
		||||
                  class="input input-sm input-bordered w-full"
 | 
			
		||||
                  :disabled="!channel.enabled"
 | 
			
		||||
                />
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <!-- 颜色选择 -->
 | 
			
		||||
              <div class="form-control w-16">
 | 
			
		||||
                <input
 | 
			
		||||
                  type="color"
 | 
			
		||||
                  v-model="channel.color"
 | 
			
		||||
                  class="w-8 h-8 rounded border-2 border-base-300 cursor-pointer"
 | 
			
		||||
                  :disabled="!channel.enabled"
 | 
			
		||||
                />
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <!-- 触发操作符选择 -->
 | 
			
		||||
              <select
 | 
			
		||||
                v-model="signalConfigs[index + 4].operator"
 | 
			
		||||
                class="select select-sm select-bordered w-32"
 | 
			
		||||
                :disabled="!channel.enabled"
 | 
			
		||||
              >
 | 
			
		||||
                <option
 | 
			
		||||
                  v-for="op in operators"
 | 
			
		||||
                  :key="op.value"
 | 
			
		||||
                  :value="op.value"
 | 
			
		||||
                >
 | 
			
		||||
                  {{ op.label }}
 | 
			
		||||
                </option>
 | 
			
		||||
              </select>
 | 
			
		||||
 | 
			
		||||
              <!-- 触发信号值选择 -->
 | 
			
		||||
              <select
 | 
			
		||||
                v-model="signalConfigs[index + 4].value"
 | 
			
		||||
                class="select select-sm select-bordered w-32"
 | 
			
		||||
                :disabled="!channel.enabled"
 | 
			
		||||
              >
 | 
			
		||||
                <option
 | 
			
		||||
                  v-for="val in signalValues"
 | 
			
		||||
                  :key="val.value"
 | 
			
		||||
                  :value="val.value"
 | 
			
		||||
                >
 | 
			
		||||
                  {{ val.label }}
 | 
			
		||||
                </option>
 | 
			
		||||
              </select>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        <!-- 当没有启用通道时的提示 -->
 | 
			
		||||
        <div v-if="enabledChannelCount === 0" class="text-center py-8 text-base-content/60">
 | 
			
		||||
          <p class="text-lg font-medium">未启用任何通道</p>
 | 
			
		||||
          <p class="text-sm">请选择通道组来配置逻辑分析仪</p>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -352,6 +182,9 @@ import { useLogicAnalyzerState } from "./LogicAnalyzerManager";
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
  currentGlobalMode,
 | 
			
		||||
  currentChannelDiv,
 | 
			
		||||
  captureLength,
 | 
			
		||||
  preCaptureLength,
 | 
			
		||||
  isApplying,
 | 
			
		||||
  channels,
 | 
			
		||||
  signalConfigs,
 | 
			
		||||
@@ -359,18 +192,11 @@ const {
 | 
			
		||||
  globalModes,
 | 
			
		||||
  operators,
 | 
			
		||||
  signalValues,
 | 
			
		||||
  enableAllChannels,
 | 
			
		||||
  disableAllChannels,
 | 
			
		||||
  channelDivOptions,
 | 
			
		||||
  captureLengthOptions,
 | 
			
		||||
  preCaptureLengthOptions,
 | 
			
		||||
  setChannelDiv,
 | 
			
		||||
  setGlobalMode,
 | 
			
		||||
  applyConfiguration,
 | 
			
		||||
  resetConfiguration,
 | 
			
		||||
} = useRequiredInjection(useLogicAnalyzerState);
 | 
			
		||||
 | 
			
		||||
const toggleAllChannels = () => {
 | 
			
		||||
  if (enabledChannelCount.value > 0) {
 | 
			
		||||
    disableAllChannels();
 | 
			
		||||
  } else {
 | 
			
		||||
    enableAllChannels();
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,135 +1,135 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="flex flex-col bg-base-100 justify-center items-center">
 | 
			
		||||
    <!-- Title -->
 | 
			
		||||
    <h1 class="font-bold text-2xl">上传比特流文件</h1>
 | 
			
		||||
 | 
			
		||||
    <!-- Input File -->
 | 
			
		||||
    <fieldset class="fieldset w-full">
 | 
			
		||||
      <legend class="fieldset-legend text-sm">选择或拖拽上传文件</legend>
 | 
			
		||||
      <input type="file" ref="fileInput" class="file-input w-full" @change="handleFileChange" />
 | 
			
		||||
      <label class="fieldset-label">文件最大容量: {{ maxMemory }}MB</label>
 | 
			
		||||
    </fieldset>
 | 
			
		||||
 | 
			
		||||
    <!-- Upload Button -->
 | 
			
		||||
    <div class="card-actions w-full">
 | 
			
		||||
      <button @click="handleClick" class="btn btn-primary grow" :disabled="isUploading">
 | 
			
		||||
        <div v-if="isUploading">
 | 
			
		||||
          <span class="loading loading-spinner"></span>
 | 
			
		||||
          下载中...
 | 
			
		||||
        </div>
 | 
			
		||||
        <div v-else>
 | 
			
		||||
          {{ buttonText }}
 | 
			
		||||
        </div>
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { computed, ref, useTemplateRef, onMounted } from "vue";
 | 
			
		||||
import { useDialogStore } from "@/stores/dialog";
 | 
			
		||||
import { isNull, isUndefined } from "lodash";
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
  uploadEvent?: (file: File) => Promise<boolean>;
 | 
			
		||||
  downloadEvent?: () => Promise<boolean>;
 | 
			
		||||
  maxMemory?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<Props>(), {
 | 
			
		||||
  maxMemory: 4,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits<{
 | 
			
		||||
  finishedUpload: [file: File];
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const dialog = useDialogStore();
 | 
			
		||||
 | 
			
		||||
const isUploading = ref(false);
 | 
			
		||||
const buttonText = computed(() => {
 | 
			
		||||
  return isUndefined(props.downloadEvent) ? "上传" : "上传并下载";
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const fileInput = useTemplateRef("fileInput");
 | 
			
		||||
const bitstream = defineModel("bitstreamFile", {
 | 
			
		||||
  type: File,
 | 
			
		||||
  default: undefined,
 | 
			
		||||
});
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  if (!isUndefined(bitstream.value) && !isNull(fileInput.value)) {
 | 
			
		||||
    let fileList = new DataTransfer();
 | 
			
		||||
    fileList.items.add(bitstream.value);
 | 
			
		||||
    fileInput.value.files = fileList.files;
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function handleFileChange(event: Event): void {
 | 
			
		||||
  const target = event.target as HTMLInputElement;
 | 
			
		||||
  const file = target.files?.[0]; // 获取选中的第一个文件
 | 
			
		||||
 | 
			
		||||
  if (!file) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bitstream.value = file;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function checkFile(file: File): boolean {
 | 
			
		||||
  const maxBytes = props.maxMemory! * 1024 * 1024; // 将最大容量从 MB 转换为字节
 | 
			
		||||
  if (file.size > maxBytes) {
 | 
			
		||||
    dialog.error(`文件大小超过最大限制: ${props.maxMemory}MB`);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function handleClick(event: Event): Promise<void> {
 | 
			
		||||
  if (isNull(bitstream.value) || isUndefined(bitstream.value)) {
 | 
			
		||||
    dialog.error(`未选择文件`);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!checkFile(bitstream.value)) return;
 | 
			
		||||
  if (isUndefined(props.uploadEvent)) {
 | 
			
		||||
    dialog.error("无法上传");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  isUploading.value = true;
 | 
			
		||||
  try {
 | 
			
		||||
    const ret = await props.uploadEvent(bitstream.value);
 | 
			
		||||
    if (isUndefined(props.downloadEvent)) {
 | 
			
		||||
      if (ret) {
 | 
			
		||||
        dialog.info("上传成功");
 | 
			
		||||
        emits("finishedUpload", bitstream.value);
 | 
			
		||||
      } else dialog.error("上传失败");
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (!ret) {
 | 
			
		||||
      isUploading.value = false;
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    dialog.error("上传失败");
 | 
			
		||||
    console.error(e);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Download
 | 
			
		||||
  try {
 | 
			
		||||
    const ret = await props.downloadEvent();
 | 
			
		||||
    if (ret) dialog.info("下载成功");
 | 
			
		||||
    else dialog.error("下载失败");
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    dialog.error("下载失败");
 | 
			
		||||
    console.error(e);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  isUploading.value = false;
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="postcss">
 | 
			
		||||
@import "../assets/main.css";
 | 
			
		||||
</style>
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="flex flex-col bg-base-100 justify-center items-center">
 | 
			
		||||
    <!-- Title -->
 | 
			
		||||
    <h1 class="font-bold text-2xl">上传比特流文件</h1>
 | 
			
		||||
 | 
			
		||||
    <!-- Input File -->
 | 
			
		||||
    <fieldset class="fieldset w-full">
 | 
			
		||||
      <legend class="fieldset-legend text-sm">选择或拖拽上传文件</legend>
 | 
			
		||||
      <input type="file" ref="fileInput" class="file-input w-full" @change="handleFileChange" />
 | 
			
		||||
      <label class="fieldset-label">文件最大容量: {{ maxMemory }}MB</label>
 | 
			
		||||
    </fieldset>
 | 
			
		||||
 | 
			
		||||
    <!-- Upload Button -->
 | 
			
		||||
    <div class="card-actions w-full">
 | 
			
		||||
      <button @click="handleClick" class="btn btn-primary grow" :disabled="isUploading">
 | 
			
		||||
        <div v-if="isUploading">
 | 
			
		||||
          <span class="loading loading-spinner"></span>
 | 
			
		||||
          下载中...
 | 
			
		||||
        </div>
 | 
			
		||||
        <div v-else>
 | 
			
		||||
          {{ buttonText }}
 | 
			
		||||
        </div>
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { computed, ref, useTemplateRef, onMounted } from "vue";
 | 
			
		||||
import { useDialogStore } from "@/stores/dialog";
 | 
			
		||||
import { isNull, isUndefined } from "lodash";
 | 
			
		||||
 | 
			
		||||
interface Props {
 | 
			
		||||
  uploadEvent?: (file: File) => Promise<boolean>;
 | 
			
		||||
  downloadEvent?: () => Promise<boolean>;
 | 
			
		||||
  maxMemory?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<Props>(), {
 | 
			
		||||
  maxMemory: 4,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits<{
 | 
			
		||||
  finishedUpload: [file: File];
 | 
			
		||||
}>();
 | 
			
		||||
 | 
			
		||||
const dialog = useDialogStore();
 | 
			
		||||
 | 
			
		||||
const isUploading = ref(false);
 | 
			
		||||
const buttonText = computed(() => {
 | 
			
		||||
  return isUndefined(props.downloadEvent) ? "上传" : "上传并下载";
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const fileInput = useTemplateRef("fileInput");
 | 
			
		||||
const bitstream = defineModel("bitstreamFile", {
 | 
			
		||||
  type: File,
 | 
			
		||||
  default: undefined,
 | 
			
		||||
});
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  if (!isUndefined(bitstream.value) && !isNull(fileInput.value)) {
 | 
			
		||||
    let fileList = new DataTransfer();
 | 
			
		||||
    fileList.items.add(bitstream.value);
 | 
			
		||||
    fileInput.value.files = fileList.files;
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function handleFileChange(event: Event): void {
 | 
			
		||||
  const target = event.target as HTMLInputElement;
 | 
			
		||||
  const file = target.files?.[0]; // 获取选中的第一个文件
 | 
			
		||||
 | 
			
		||||
  if (!file) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bitstream.value = file;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function checkFile(file: File): boolean {
 | 
			
		||||
  const maxBytes = props.maxMemory! * 1024 * 1024; // 将最大容量从 MB 转换为字节
 | 
			
		||||
  if (file.size > maxBytes) {
 | 
			
		||||
    dialog.error(`文件大小超过最大限制: ${props.maxMemory}MB`);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function handleClick(event: Event): Promise<void> {
 | 
			
		||||
  if (isNull(bitstream.value) || isUndefined(bitstream.value)) {
 | 
			
		||||
    dialog.error(`未选择文件`);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!checkFile(bitstream.value)) return;
 | 
			
		||||
  if (isUndefined(props.uploadEvent)) {
 | 
			
		||||
    dialog.error("无法上传");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  isUploading.value = true;
 | 
			
		||||
  try {
 | 
			
		||||
    const ret = await props.uploadEvent(bitstream.value);
 | 
			
		||||
    if (isUndefined(props.downloadEvent)) {
 | 
			
		||||
      if (ret) {
 | 
			
		||||
        dialog.info("上传成功");
 | 
			
		||||
        emits("finishedUpload", bitstream.value);
 | 
			
		||||
      } else dialog.error("上传失败");
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (!ret) {
 | 
			
		||||
      isUploading.value = false;
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    dialog.error("上传失败");
 | 
			
		||||
    console.error(e);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Download
 | 
			
		||||
  try {
 | 
			
		||||
    const ret = await props.downloadEvent();
 | 
			
		||||
    if (ret) dialog.info("下载成功");
 | 
			
		||||
    else dialog.error("下载失败");
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    dialog.error("下载失败");
 | 
			
		||||
    console.error(e);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  isUploading.value = false;
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="postcss">
 | 
			
		||||
@import "../assets/main.css";
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -9,8 +9,42 @@
 | 
			
		||||
            逻辑信号分析
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="flex items-center gap-2">
 | 
			
		||||
            <!-- 空闲状态:只显示开始捕获按钮 -->
 | 
			
		||||
            <button 
 | 
			
		||||
              v-if="!analyzer.isCapturing.value"
 | 
			
		||||
              @click="analyzer.startCapture"
 | 
			
		||||
              :disabled="analyzer.isApplying.value"
 | 
			
		||||
              class="btn btn-sm btn-primary"
 | 
			
		||||
            >
 | 
			
		||||
              开始捕获
 | 
			
		||||
            </button>
 | 
			
		||||
            
 | 
			
		||||
            <!-- 捕获状态:显示停止捕获和强制捕获按钮 -->
 | 
			
		||||
            <button 
 | 
			
		||||
              v-if="analyzer.isCapturing.value"
 | 
			
		||||
              @click="analyzer.stopCapture"
 | 
			
		||||
              class="btn btn-sm btn-warning"
 | 
			
		||||
            >
 | 
			
		||||
              <span class="loading loading-spinner loading-sm"></span>
 | 
			
		||||
              停止捕获
 | 
			
		||||
            </button>
 | 
			
		||||
            <button 
 | 
			
		||||
              v-if="analyzer.isCapturing.value"
 | 
			
		||||
              @click="analyzer.forceCapture"
 | 
			
		||||
              class="btn btn-sm btn-secondary"
 | 
			
		||||
            >
 | 
			
		||||
              强制捕获
 | 
			
		||||
            </button>
 | 
			
		||||
            
 | 
			
		||||
            <!-- 其他按钮保持不变 -->
 | 
			
		||||
            <button 
 | 
			
		||||
              @click="analyzer.generateTestData"
 | 
			
		||||
              class="btn btn-sm btn-info"
 | 
			
		||||
            >
 | 
			
		||||
              测试数据
 | 
			
		||||
            </button>
 | 
			
		||||
            <button class="btn btn-sm btn-error" @click="handleDeleteData">
 | 
			
		||||
              清空
 | 
			
		||||
              清空数据
 | 
			
		||||
            </button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </h2>
 | 
			
		||||
@@ -21,9 +55,45 @@
 | 
			
		||||
    <!-- 触发设置 -->
 | 
			
		||||
    <div class="card bg-base-200 shadow-xl mx-5">
 | 
			
		||||
      <div class="card-body">
 | 
			
		||||
        <h2 class="card-title">
 | 
			
		||||
          <Settings class="w-5 h-5" />
 | 
			
		||||
          触发设置
 | 
			
		||||
        <h2 class="card-title flex justify-between items-center">
 | 
			
		||||
          <div class="flex gap-8">
 | 
			
		||||
            <div class="flex items-center gap-2">
 | 
			
		||||
              <Settings class="w-5 h-5" />
 | 
			
		||||
              触发设置
 | 
			
		||||
            </div>
 | 
			
		||||
            <!-- 配置摘要 -->
 | 
			
		||||
            <div class="flex items-center gap-4 text-sm text-gray-500">
 | 
			
		||||
              <span>{{ analyzer.enabledChannelCount.value }}/32 通道</span>
 | 
			
		||||
              <span>捕获: {{ analyzer.captureLength.value }}</span>
 | 
			
		||||
              <span>预捕获: {{ analyzer.preCaptureLength.value }}</span>
 | 
			
		||||
              <span>{{ analyzer.globalModes.find(m => m.value === analyzer.currentGlobalMode.value)?.label || '未知' }}</span>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="flex items-center gap-4">
 | 
			
		||||
            <!-- 状态指示 -->
 | 
			
		||||
            <div class="flex items-center gap-2 text-sm">
 | 
			
		||||
              <span 
 | 
			
		||||
                v-if="analyzer.isCapturing.value" 
 | 
			
		||||
                class="flex items-center gap-1 text-warning"
 | 
			
		||||
              >
 | 
			
		||||
                <span class="loading loading-spinner loading-xs"></span>
 | 
			
		||||
                捕获中
 | 
			
		||||
              </span>
 | 
			
		||||
              <span 
 | 
			
		||||
                v-else-if="analyzer.isApplying.value" 
 | 
			
		||||
                class="flex items-center gap-1 text-info"
 | 
			
		||||
              >
 | 
			
		||||
                <span class="loading loading-spinner loading-xs"></span>
 | 
			
		||||
                配置中
 | 
			
		||||
              </span>
 | 
			
		||||
              <span 
 | 
			
		||||
                v-else 
 | 
			
		||||
                class="text-success"
 | 
			
		||||
              >
 | 
			
		||||
                就绪
 | 
			
		||||
              </span>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </h2>
 | 
			
		||||
        <TriggerSettings />
 | 
			
		||||
      </div>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user