feat: 使用SignalR实时发送示波器数据,并美化示波器界面
This commit is contained in:
@@ -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