feat: 使用SignalR实时发送示波器数据,并美化示波器界面
This commit is contained in:
		@@ -88,7 +88,8 @@ try
 | 
			
		||||
                            path.StartsWithSegments("/hubs/JtagHub") ||
 | 
			
		||||
                            path.StartsWithSegments("/hubs/ProgressHub") ||
 | 
			
		||||
                            path.StartsWithSegments("/hubs/DigitalTubesHub") ||
 | 
			
		||||
                            path.StartsWithSegments("/hubs/RotaryEncoderHub")
 | 
			
		||||
                            path.StartsWithSegments("/hubs/RotaryEncoderHub") ||
 | 
			
		||||
                            path.StartsWithSegments("/hubs/OscilloscopeHub")
 | 
			
		||||
                        ))
 | 
			
		||||
                    {
 | 
			
		||||
                        // Read the token out of the query string
 | 
			
		||||
@@ -256,6 +257,7 @@ try
 | 
			
		||||
    app.MapHub<server.Hubs.ProgressHub>("/hubs/ProgressHub");
 | 
			
		||||
    app.MapHub<server.Hubs.DigitalTubesHub>("/hubs/DigitalTubesHub");
 | 
			
		||||
    app.MapHub<server.Hubs.RotaryEncoderHub>("/hubs/RotaryEncoderHub");
 | 
			
		||||
    app.MapHub<server.Hubs.OscilloscopeHub>("/hubs/OscilloscopeHub");
 | 
			
		||||
 | 
			
		||||
    // Setup Program
 | 
			
		||||
    MsgBus.Init();
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using Microsoft.AspNetCore.Cors;
 | 
			
		||||
using Microsoft.AspNetCore.Mvc;
 | 
			
		||||
using Peripherals.OscilloscopeClient;
 | 
			
		||||
using server.Hubs;
 | 
			
		||||
 | 
			
		||||
namespace server.Controllers;
 | 
			
		||||
 | 
			
		||||
@@ -9,6 +10,7 @@ namespace server.Controllers;
 | 
			
		||||
/// 示波器API控制器 - 普通用户权限
 | 
			
		||||
/// </summary>
 | 
			
		||||
[ApiController]
 | 
			
		||||
[EnableCors("Development")]
 | 
			
		||||
[Route("api/[controller]")]
 | 
			
		||||
[Authorize]
 | 
			
		||||
public class OscilloscopeApiController : ControllerBase
 | 
			
		||||
@@ -20,7 +22,7 @@ public class OscilloscopeApiController : ControllerBase
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 获取示波器实例
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    private Oscilloscope? GetOscilloscope()
 | 
			
		||||
    private OscilloscopeCtrl? GetOscilloscope()
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
@@ -41,7 +43,7 @@ public class OscilloscopeApiController : ControllerBase
 | 
			
		||||
                return null;
 | 
			
		||||
 | 
			
		||||
            var board = boardRet.Value.Value;
 | 
			
		||||
            return new Oscilloscope(board.IpAddr, board.Port);
 | 
			
		||||
            return new OscilloscopeCtrl(board.IpAddr, board.Port);
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
@@ -56,12 +58,11 @@ public class OscilloscopeApiController : ControllerBase
 | 
			
		||||
    /// <param name="config">示波器配置</param>
 | 
			
		||||
    /// <returns>操作结果</returns>
 | 
			
		||||
    [HttpPost("Initialize")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
 | 
			
		||||
    public async Task<IActionResult> Initialize([FromBody] OscilloscopeFullConfig config)
 | 
			
		||||
    public async Task<IActionResult> Initialize([FromBody] OscilloscopeConfig config)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
@@ -151,7 +152,6 @@ public class OscilloscopeApiController : ControllerBase
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns>操作结果</returns>
 | 
			
		||||
    [HttpPost("StartCapture")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
@@ -185,7 +185,6 @@ public class OscilloscopeApiController : ControllerBase
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns>操作结果</returns>
 | 
			
		||||
    [HttpPost("StopCapture")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
@@ -219,7 +218,6 @@ public class OscilloscopeApiController : ControllerBase
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns>示波器数据和状态信息</returns>
 | 
			
		||||
    [HttpGet("GetData")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(OscilloscopeDataResponse), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
@@ -293,7 +291,6 @@ public class OscilloscopeApiController : ControllerBase
 | 
			
		||||
    /// <param name="risingEdge">触发边沿(true为上升沿,false为下降沿)</param>
 | 
			
		||||
    /// <returns>操作结果</returns>
 | 
			
		||||
    [HttpPost("UpdateTrigger")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
@@ -338,7 +335,6 @@ public class OscilloscopeApiController : ControllerBase
 | 
			
		||||
    /// <param name="decimationRate">抽样率(0-1023)</param>
 | 
			
		||||
    /// <returns>操作结果</returns>
 | 
			
		||||
    [HttpPost("UpdateSampling")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
@@ -387,7 +383,6 @@ public class OscilloscopeApiController : ControllerBase
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <returns>操作结果</returns>
 | 
			
		||||
    [HttpPost("RefreshRAM")]
 | 
			
		||||
    [EnableCors("Users")]
 | 
			
		||||
    [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
			
		||||
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
 | 
			
		||||
@@ -415,72 +410,4 @@ public class OscilloscopeApiController : ControllerBase
 | 
			
		||||
            return StatusCode(StatusCodes.Status500InternalServerError, "操作失败,请稍后重试");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 示波器完整配置
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class OscilloscopeFullConfig
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 是否启动捕获
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public bool CaptureEnabled { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 触发电平(0-255)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public byte TriggerLevel { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 触发边沿(true为上升沿,false为下降沿)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public bool TriggerRisingEdge { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 水平偏移量(0-1023)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public ushort HorizontalShift { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 抽样率(0-1023)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public ushort DecimationRate { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 是否自动刷新RAM
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public bool AutoRefreshRAM { get; set; } = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 示波器状态和数据
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public class OscilloscopeDataResponse
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// AD采样频率
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public uint ADFrequency { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// AD采样幅度
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public byte ADVpp { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// AD采样最大值
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public byte ADMax { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// AD采样最小值
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public byte ADMin { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// 波形数据(Base64编码)
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public string WaveformData { get; set; } = string.Empty;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -32,15 +32,9 @@ public class DigitalTubeTaskStatus
 | 
			
		||||
{
 | 
			
		||||
    public int Frequency { get; set; } = 100;
 | 
			
		||||
    public bool IsRunning { get; set; } = false;
 | 
			
		||||
 | 
			
		||||
    public DigitalTubeTaskStatus(ScanTaskInfo info)
 | 
			
		||||
    {
 | 
			
		||||
        Frequency = info.Frequency;
 | 
			
		||||
        IsRunning = info.IsRunning;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public class ScanTaskInfo
 | 
			
		||||
class DigitalTubesScanTaskInfo
 | 
			
		||||
{
 | 
			
		||||
    public string BoardID { get; set; }
 | 
			
		||||
    public string ClientID { get; set; }
 | 
			
		||||
@@ -50,13 +44,22 @@ public class ScanTaskInfo
 | 
			
		||||
    public int Frequency { get; set; } = 100;
 | 
			
		||||
    public bool IsRunning { get; set; } = false;
 | 
			
		||||
 | 
			
		||||
    public ScanTaskInfo(
 | 
			
		||||
    public DigitalTubesScanTaskInfo(
 | 
			
		||||
        string boardID, string clientID, SevenDigitalTubesCtrl client)
 | 
			
		||||
    {
 | 
			
		||||
        BoardID = boardID;
 | 
			
		||||
        ClientID = clientID;
 | 
			
		||||
        TubeClient = client;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public DigitalTubeTaskStatus ToDigitalTubeTaskStatus()
 | 
			
		||||
    {
 | 
			
		||||
        return new DigitalTubeTaskStatus
 | 
			
		||||
        {
 | 
			
		||||
            Frequency = Frequency,
 | 
			
		||||
            IsRunning = IsRunning
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[Authorize]
 | 
			
		||||
@@ -67,7 +70,7 @@ public class DigitalTubesHub : Hub<IDigitalTubesReceiver>, IDigitalTubesHub
 | 
			
		||||
    private readonly IHubContext<DigitalTubesHub, IDigitalTubesReceiver> _hubContext;
 | 
			
		||||
    private readonly Database.UserManager _userManager = new();
 | 
			
		||||
 | 
			
		||||
    private ConcurrentDictionary<(string, string), ScanTaskInfo> _scanTasks = new();
 | 
			
		||||
    private ConcurrentDictionary<(string, string), DigitalTubesScanTaskInfo> _scanTasks = new();
 | 
			
		||||
 | 
			
		||||
    public DigitalTubesHub(IHubContext<DigitalTubesHub, IDigitalTubesReceiver> hubContext)
 | 
			
		||||
    {
 | 
			
		||||
@@ -100,7 +103,7 @@ public class DigitalTubesHub : Hub<IDigitalTubesReceiver>, IDigitalTubesHub
 | 
			
		||||
        return boardRet.Value.Value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Task ScanAllTubes(ScanTaskInfo scanInfo)
 | 
			
		||||
    private Task ScanAllTubes(DigitalTubesScanTaskInfo scanInfo)
 | 
			
		||||
    {
 | 
			
		||||
        var token = scanInfo.CTS.Token;
 | 
			
		||||
        return Task.Run(async () =>
 | 
			
		||||
@@ -163,7 +166,7 @@ public class DigitalTubesHub : Hub<IDigitalTubesReceiver>, IDigitalTubesHub
 | 
			
		||||
                return true;
 | 
			
		||||
 | 
			
		||||
            var cts = new CancellationTokenSource();
 | 
			
		||||
            var scanTaskInfo = new ScanTaskInfo(
 | 
			
		||||
            var scanTaskInfo = new DigitalTubesScanTaskInfo(
 | 
			
		||||
                board.ID.ToString(), Context.ConnectionId,
 | 
			
		||||
                new SevenDigitalTubesCtrl(board.IpAddr, board.Port, 0)
 | 
			
		||||
            );
 | 
			
		||||
@@ -240,7 +243,7 @@ public class DigitalTubesHub : Hub<IDigitalTubesReceiver>, IDigitalTubesHub
 | 
			
		||||
 | 
			
		||||
            if (_scanTasks.TryGetValue(key, out var scanInfo))
 | 
			
		||||
            {
 | 
			
		||||
                return new DigitalTubeTaskStatus(scanInfo);
 | 
			
		||||
                return scanInfo.ToDigitalTubeTaskStatus();
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										386
									
								
								server/src/Hubs/OscilloscopeHub.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										386
									
								
								server/src/Hubs/OscilloscopeHub.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,386 @@
 | 
			
		||||
using Microsoft.AspNetCore.Authorization;
 | 
			
		||||
using System.Security.Claims;
 | 
			
		||||
using Microsoft.AspNetCore.SignalR;
 | 
			
		||||
using Microsoft.AspNetCore.Cors;
 | 
			
		||||
using TypedSignalR.Client;
 | 
			
		||||
using DotNext;
 | 
			
		||||
using Tapper;
 | 
			
		||||
using System.Collections.Concurrent;
 | 
			
		||||
using Peripherals.OscilloscopeClient;
 | 
			
		||||
 | 
			
		||||
#pragma warning disable 1998
 | 
			
		||||
 | 
			
		||||
namespace server.Hubs;
 | 
			
		||||
 | 
			
		||||
[Hub]
 | 
			
		||||
public interface IOscilloscopeHub
 | 
			
		||||
{
 | 
			
		||||
    Task<bool> Initialize(OscilloscopeFullConfig config);
 | 
			
		||||
    Task<bool> StartCapture();
 | 
			
		||||
    Task<bool> StopCapture();
 | 
			
		||||
    Task<OscilloscopeDataResponse?> GetData();
 | 
			
		||||
    Task<bool> SetTrigger(byte level);
 | 
			
		||||
    Task<bool> SetRisingEdge(bool risingEdge);
 | 
			
		||||
    Task<bool> SetSampling(ushort decimationRate);
 | 
			
		||||
    Task<bool> SetFrequency(int frequency);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[Receiver]
 | 
			
		||||
public interface IOscilloscopeReceiver
 | 
			
		||||
{
 | 
			
		||||
    Task OnDataReceived(OscilloscopeDataResponse data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[TranspilationSource]
 | 
			
		||||
public class OscilloscopeDataResponse
 | 
			
		||||
{
 | 
			
		||||
    public uint ADFrequency { get; set; }
 | 
			
		||||
    public byte ADVpp { get; set; }
 | 
			
		||||
    public byte ADMax { get; set; }
 | 
			
		||||
    public byte ADMin { get; set; }
 | 
			
		||||
    public string WaveformData { get; set; } = "";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[TranspilationSource]
 | 
			
		||||
public class OscilloscopeFullConfig
 | 
			
		||||
{
 | 
			
		||||
    public bool CaptureEnabled { get; set; }
 | 
			
		||||
    public byte TriggerLevel { get; set; }
 | 
			
		||||
    public bool TriggerRisingEdge { get; set; }
 | 
			
		||||
    public ushort HorizontalShift { get; set; }
 | 
			
		||||
    public ushort DecimationRate { get; set; }
 | 
			
		||||
    public int CaptureFrequency { get; set; }
 | 
			
		||||
    // public bool AutoRefreshRAM { get; set; }
 | 
			
		||||
 | 
			
		||||
    public OscilloscopeConfig ToOscilloscopeConfig()
 | 
			
		||||
    {
 | 
			
		||||
        return new OscilloscopeConfig
 | 
			
		||||
        {
 | 
			
		||||
            CaptureEnabled = CaptureEnabled,
 | 
			
		||||
            TriggerLevel = TriggerLevel,
 | 
			
		||||
            TriggerRisingEdge = TriggerRisingEdge,
 | 
			
		||||
            HorizontalShift = HorizontalShift,
 | 
			
		||||
            DecimationRate = DecimationRate,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class OscilloscopeScanTaskInfo
 | 
			
		||||
{
 | 
			
		||||
    public Task? ScanTask { get; set; }
 | 
			
		||||
    public OscilloscopeCtrl Client { get; set; }
 | 
			
		||||
    public CancellationTokenSource CTS { get; set; } = new();
 | 
			
		||||
    public int Frequency { get; set; } = 100;
 | 
			
		||||
    public bool IsRunning { get; set; } = false;
 | 
			
		||||
 | 
			
		||||
    public OscilloscopeScanTaskInfo(OscilloscopeCtrl client)
 | 
			
		||||
    {
 | 
			
		||||
        Client = client;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
[Authorize]
 | 
			
		||||
[EnableCors("SignalR")]
 | 
			
		||||
public class OscilloscopeHub : Hub<IOscilloscopeReceiver>, IOscilloscopeHub
 | 
			
		||||
{
 | 
			
		||||
    private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
			
		||||
    private readonly IHubContext<OscilloscopeHub, IOscilloscopeReceiver> _hubContext;
 | 
			
		||||
    private readonly Database.UserManager _userManager = new();
 | 
			
		||||
 | 
			
		||||
    private ConcurrentDictionary<(string, string), OscilloscopeScanTaskInfo> _scanTasks = new();
 | 
			
		||||
 | 
			
		||||
    public OscilloscopeHub(IHubContext<OscilloscopeHub, IOscilloscopeReceiver> hubContext)
 | 
			
		||||
    {
 | 
			
		||||
        _hubContext = hubContext;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Optional<Database.Board> TryGetBoard()
 | 
			
		||||
    {
 | 
			
		||||
        var userName = Context.User?.FindFirstValue(ClaimTypes.Name);
 | 
			
		||||
        if (string.IsNullOrEmpty(userName))
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error("User name is null or empty");
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var boardRet = _userManager.GetBoardByUserName(userName);
 | 
			
		||||
        if (!boardRet.IsSuccessful || !boardRet.Value.HasValue)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"Board not found");
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        return boardRet.Value.Value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Optional<OscilloscopeCtrl> GetOscilloscope()
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var board = TryGetBoard().OrThrow(() => new Exception("Board not found"));
 | 
			
		||||
            var client = new OscilloscopeCtrl(board.IpAddr, board.Port, 0);
 | 
			
		||||
            return client;
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error(ex, "Failed to get oscilloscope");
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<bool> Initialize(OscilloscopeFullConfig config)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var client = GetOscilloscope().OrThrow(() => new Exception("Oscilloscope not found"));
 | 
			
		||||
 | 
			
		||||
            var result = await client.Init(config.ToOscilloscopeConfig());
 | 
			
		||||
            if (!result.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error(result.Error, "Initialize failed");
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            return result.Value;
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error(ex, "Failed to initialize oscilloscope");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<bool> StartCapture()
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var board = TryGetBoard().OrThrow(() => new Exception("Board not found"));
 | 
			
		||||
            var key = (board.ID.ToString(), Context.ConnectionId);
 | 
			
		||||
            var client = GetOscilloscope().OrThrow(() => new Exception("Oscilloscope not found"));
 | 
			
		||||
 | 
			
		||||
            if (_scanTasks.TryGetValue(key, out var existing) && existing.IsRunning)
 | 
			
		||||
                return true;
 | 
			
		||||
 | 
			
		||||
            var result = await client.SetCaptureEnable(true);
 | 
			
		||||
            if (!result.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error(result.Error, "StartCapture failed");
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var scanTaskInfo = new OscilloscopeScanTaskInfo(client);
 | 
			
		||||
            var token = scanTaskInfo.CTS.Token;
 | 
			
		||||
            scanTaskInfo.ScanTask = Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                while (!token.IsCancellationRequested)
 | 
			
		||||
                {
 | 
			
		||||
                    var data = await GetData();
 | 
			
		||||
                    if (data == null)
 | 
			
		||||
                    {
 | 
			
		||||
                        logger.Error("GetData failed");
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    await Clients.Client(Context.ConnectionId).OnDataReceived(data);
 | 
			
		||||
                    await Task.Delay(1000 / scanTaskInfo.Frequency, token);
 | 
			
		||||
                }
 | 
			
		||||
            }, token);
 | 
			
		||||
 | 
			
		||||
            _scanTasks[key] = scanTaskInfo;
 | 
			
		||||
 | 
			
		||||
            return result.Value;
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error(ex, "Failed to start capture");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<bool> StopCapture()
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var board = TryGetBoard().OrThrow(() => new Exception("Board not found"));
 | 
			
		||||
            var client = GetOscilloscope().OrThrow(() => new Exception("Oscilloscope not found"));
 | 
			
		||||
 | 
			
		||||
            var key = (board.ID.ToString(), Context.ConnectionId);
 | 
			
		||||
 | 
			
		||||
            if (_scanTasks.TryRemove(key, out var scanInfo))
 | 
			
		||||
            {
 | 
			
		||||
                scanInfo.IsRunning = false;
 | 
			
		||||
                scanInfo.CTS.Cancel();
 | 
			
		||||
                if (scanInfo.ScanTask != null) await scanInfo.ScanTask;
 | 
			
		||||
                scanInfo.CTS.Dispose();
 | 
			
		||||
 | 
			
		||||
                var result = await client.SetCaptureEnable(false);
 | 
			
		||||
                if (!result.IsSuccessful)
 | 
			
		||||
                {
 | 
			
		||||
                    logger.Error(result.Error, "StopCapture failed");
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
                return result.Value;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            throw new Exception("Task not found");
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error(ex, "Failed to stop capture");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<OscilloscopeDataResponse?> GetData()
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var oscilloscope = GetOscilloscope().OrThrow(() => new Exception("用户未绑定有效的实验板"));
 | 
			
		||||
 | 
			
		||||
            var freqResult = await oscilloscope.GetADFrequency();
 | 
			
		||||
            var vppResult = await oscilloscope.GetADVpp();
 | 
			
		||||
            var maxResult = await oscilloscope.GetADMax();
 | 
			
		||||
            var minResult = await oscilloscope.GetADMin();
 | 
			
		||||
            var waveformResult = await oscilloscope.GetWaveformData();
 | 
			
		||||
 | 
			
		||||
            if (!freqResult.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"获取AD采样频率失败: {freqResult.Error}");
 | 
			
		||||
                throw new Exception($"获取AD采样频率失败: {freqResult.Error}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!vppResult.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"获取AD采样幅度失败: {vppResult.Error}");
 | 
			
		||||
                throw new Exception($"获取AD采样幅度失败: {vppResult.Error}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!maxResult.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"获取AD采样最大值失败: {maxResult.Error}");
 | 
			
		||||
                throw new Exception($"获取AD采样最大值失败: {maxResult.Error}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!minResult.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"获取AD采样最小值失败: {minResult.Error}");
 | 
			
		||||
                throw new Exception($"获取AD采样最小值失败: {minResult.Error}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!waveformResult.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error($"获取波形数据失败: {waveformResult.Error}");
 | 
			
		||||
                throw new Exception($"获取波形数据失败: {waveformResult.Error}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var response = new OscilloscopeDataResponse
 | 
			
		||||
            {
 | 
			
		||||
                ADFrequency = freqResult.Value,
 | 
			
		||||
                ADVpp = vppResult.Value,
 | 
			
		||||
                ADMax = maxResult.Value,
 | 
			
		||||
                ADMin = minResult.Value,
 | 
			
		||||
                WaveformData = Convert.ToBase64String(waveformResult.Value)
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            return new OscilloscopeDataResponse
 | 
			
		||||
            {
 | 
			
		||||
                ADFrequency = freqResult.Value,
 | 
			
		||||
                ADVpp = vppResult.Value,
 | 
			
		||||
                ADMax = maxResult.Value,
 | 
			
		||||
                ADMin = minResult.Value,
 | 
			
		||||
                WaveformData = Convert.ToBase64String(waveformResult.Value)
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error(ex, "获取示波器数据时发生异常");
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<bool> SetTrigger(byte level)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var client = GetOscilloscope().OrThrow(() => new Exception("Oscilloscope not found"));
 | 
			
		||||
            var ret = await client.SetTriggerLevel(level);
 | 
			
		||||
            if (!ret.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error(ret.Error, "UpdateTrigger failed");
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            return ret.Value;
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error(ex, "Failed to update trigger");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<bool> SetRisingEdge(bool risingEdge)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var client = GetOscilloscope().OrThrow(() => new Exception("Oscilloscope not found"));
 | 
			
		||||
            var ret = await client.SetTriggerEdge(risingEdge);
 | 
			
		||||
            if (!ret.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error(ret.Error, "Update Rising Edge failed");
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            return ret.Value;
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error(ex, "SetRisingEdge failed");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<bool> SetSampling(ushort decimationRate)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var client = GetOscilloscope().OrThrow(() => new Exception("Oscilloscope not found"));
 | 
			
		||||
            var result = await client.SetDecimationRate(decimationRate);
 | 
			
		||||
            if (!result.IsSuccessful)
 | 
			
		||||
            {
 | 
			
		||||
                logger.Error(result.Error, "UpdateSampling failed");
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            return result.Value;
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error(ex, "Failed to update sampling");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<bool> SetFrequency(int frequency)
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            if (frequency < 1 || frequency > 1000)
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            var board = TryGetBoard().OrThrow(() => new Exception("Board not found"));
 | 
			
		||||
            var key = (board.ID.ToString(), Context.ConnectionId);
 | 
			
		||||
 | 
			
		||||
            if (_scanTasks.TryGetValue(key, out var scanInfo) && scanInfo.IsRunning)
 | 
			
		||||
            {
 | 
			
		||||
                scanInfo.Frequency = frequency;
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                logger.Warn($"SetFrequency called but no running scan for board {board.ID} and client {Context.ConnectionId}");
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error(ex, "Failed to set frequency");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,9 +2,20 @@ using System.Net;
 | 
			
		||||
using Common;
 | 
			
		||||
using DotNext;
 | 
			
		||||
using WebProtocol;
 | 
			
		||||
using Tapper;
 | 
			
		||||
 | 
			
		||||
namespace Peripherals.OscilloscopeClient;
 | 
			
		||||
 | 
			
		||||
public class OscilloscopeConfig
 | 
			
		||||
{
 | 
			
		||||
    public bool CaptureEnabled { get; set; }
 | 
			
		||||
    public byte TriggerLevel { get; set; }
 | 
			
		||||
    public bool TriggerRisingEdge { get; set; }
 | 
			
		||||
    public ushort HorizontalShift { get; set; }
 | 
			
		||||
    public ushort DecimationRate { get; set; }
 | 
			
		||||
    // public bool AutoRefreshRAM { get; set; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static class OscilloscopeAddr
 | 
			
		||||
{
 | 
			
		||||
    const UInt32 BASE = 0x8000_0000;
 | 
			
		||||
@@ -71,7 +82,7 @@ static class OscilloscopeAddr
 | 
			
		||||
    public const UInt32 RD_DATA_LENGTH = 0x0000_0400;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Oscilloscope
 | 
			
		||||
class OscilloscopeCtrl
 | 
			
		||||
{
 | 
			
		||||
    private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
 | 
			
		||||
 | 
			
		||||
@@ -88,7 +99,7 @@ class Oscilloscope
 | 
			
		||||
    /// <param name="address">示波器设备IP地址</param>
 | 
			
		||||
    /// <param name="port">示波器设备端口</param>
 | 
			
		||||
    /// <param name="timeout">超时时间(毫秒)</param>
 | 
			
		||||
    public Oscilloscope(string address, int port, int timeout = 2000)
 | 
			
		||||
    public OscilloscopeCtrl(string address, int port, int timeout = 2000)
 | 
			
		||||
    {
 | 
			
		||||
        if (timeout < 0)
 | 
			
		||||
            throw new ArgumentException("Timeout couldn't be negative", nameof(timeout));
 | 
			
		||||
@@ -98,6 +109,49 @@ class Oscilloscope
 | 
			
		||||
        this.timeout = timeout;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 一次性初始化/配置示波器
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="config">完整配置</param>
 | 
			
		||||
    /// <returns>操作结果,全部成功返回true,否则返回异常信息</returns>
 | 
			
		||||
    public async ValueTask<Result<bool>> Init(OscilloscopeConfig config)
 | 
			
		||||
    {
 | 
			
		||||
        // 1. 捕获使能
 | 
			
		||||
        var ret = await SetCaptureEnable(config.CaptureEnabled);
 | 
			
		||||
        if (!ret.IsSuccessful || !ret.Value)
 | 
			
		||||
            return new(ret.Error ?? new Exception("Failed to set capture enable"));
 | 
			
		||||
 | 
			
		||||
        // 2. 触发电平
 | 
			
		||||
        ret = await SetTriggerLevel(config.TriggerLevel);
 | 
			
		||||
        if (!ret.IsSuccessful || !ret.Value)
 | 
			
		||||
            return new(ret.Error ?? new Exception("Failed to set trigger level"));
 | 
			
		||||
 | 
			
		||||
        // 3. 触发边沿
 | 
			
		||||
        ret = await SetTriggerEdge(config.TriggerRisingEdge);
 | 
			
		||||
        if (!ret.IsSuccessful || !ret.Value)
 | 
			
		||||
            return new(ret.Error ?? new Exception("Failed to set trigger edge"));
 | 
			
		||||
 | 
			
		||||
        // 4. 水平偏移
 | 
			
		||||
        ret = await SetHorizontalShift(config.HorizontalShift);
 | 
			
		||||
        if (!ret.IsSuccessful || !ret.Value)
 | 
			
		||||
            return new(ret.Error ?? new Exception("Failed to set horizontal shift"));
 | 
			
		||||
 | 
			
		||||
        // 5. 抽样率
 | 
			
		||||
        ret = await SetDecimationRate(config.DecimationRate);
 | 
			
		||||
        if (!ret.IsSuccessful || !ret.Value)
 | 
			
		||||
            return new(ret.Error ?? new Exception("Failed to set decimation rate"));
 | 
			
		||||
 | 
			
		||||
        // 6. RAM刷新(如果需要)
 | 
			
		||||
        // if (config.AutoRefreshRAM)
 | 
			
		||||
        // {
 | 
			
		||||
        //     ret = await RefreshRAM();
 | 
			
		||||
        //     if (!ret.IsSuccessful || !ret.Value)
 | 
			
		||||
        //         return new(ret.Error ?? new Exception("Failed to refresh RAM"));
 | 
			
		||||
        // }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// 控制示波器的捕获开关
 | 
			
		||||
    /// </summary>
 | 
			
		||||
@@ -309,13 +363,13 @@ class Oscilloscope
 | 
			
		||||
        // 等待WAVE_READY[0]位为1,最多等待50ms(5次x10ms间隔)
 | 
			
		||||
        var readyResult = await UDPClientPool.ReadAddrWithWait(
 | 
			
		||||
            this.ep, this.taskID, OscilloscopeAddr.WAVE_READY, 0b00, 0x01, 10, 50);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        if (!readyResult.IsSuccessful)
 | 
			
		||||
        {
 | 
			
		||||
            logger.Error($"Failed to wait for wave ready: {readyResult.Error}");
 | 
			
		||||
            return new(readyResult.Error);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // 无论准备好与否,都继续读取数据(readyResult.Value表示是否在超时前准备好)
 | 
			
		||||
        if (!readyResult.Value)
 | 
			
		||||
        {
 | 
			
		||||
@@ -365,14 +419,14 @@ class Oscilloscope
 | 
			
		||||
            logger.Error("ReadAddr returned invalid data for trigger position");
 | 
			
		||||
            return new(new Exception("Failed to read trigger position"));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        UInt32 trigAddr = Number.BytesToUInt32(trigPosResult.Value.Options.Data).Value;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // 根据触发地址对数据进行偏移,使触发点位于数据中间
 | 
			
		||||
        int targetPos = sampleCount / 2; // 目标位置:数据中间
 | 
			
		||||
        int actualTrigPos = (int)(trigAddr % (UInt32)sampleCount); // 实际触发位置
 | 
			
		||||
        int shiftAmount = targetPos - actualTrigPos;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // 创建偏移后的数据数组
 | 
			
		||||
        byte[] offsetData = new byte[sampleCount];
 | 
			
		||||
        for (int i = 0; i < sampleCount; i++)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user