From 7e53b805ae576fddbac045134e524f79caf3f9bc Mon Sep 17 00:00:00 2001 From: SikongJueluo Date: Tue, 19 Aug 2025 12:55:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8SignalR=E5=AE=9E?= =?UTF-8?q?=E6=97=B6=E5=8F=91=E9=80=81=E7=A4=BA=E6=B3=A2=E5=99=A8=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=EF=BC=8C=E5=B9=B6=E7=BE=8E=E5=8C=96=E7=A4=BA=E6=B3=A2?= =?UTF-8?q?=E5=99=A8=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/Program.cs | 4 +- .../src/Controllers/OscilloscopeController.cs | 83 +-- server/src/Hubs/DigitalTubesHub.cs | 27 +- server/src/Hubs/OscilloscopeHub.cs | 386 ++++++++++++ server/src/Peripherals/OscilloscopeClient.cs | 68 ++- src/APIClient.ts | 50 +- .../Oscilloscope/OscilloscopeManager.ts | 250 +++++--- .../OscilloscopeWaveformDisplay.vue | 403 +++++++++++- src/utils/AuthManager.ts | 7 +- .../signalR/TypedSignalR.Client/index.ts | 82 ++- .../TypedSignalR.Client/server.Hubs.ts | 50 +- src/utils/signalR/server.Hubs.ts | 30 + src/views/Project/Oscilloscope.vue | 571 +++++++++++++++--- 13 files changed, 1664 insertions(+), 347 deletions(-) create mode 100644 server/src/Hubs/OscilloscopeHub.cs diff --git a/server/Program.cs b/server/Program.cs index 9bfc86b..43e4cd1 100644 --- a/server/Program.cs +++ b/server/Program.cs @@ -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("/hubs/ProgressHub"); app.MapHub("/hubs/DigitalTubesHub"); app.MapHub("/hubs/RotaryEncoderHub"); + app.MapHub("/hubs/OscilloscopeHub"); // Setup Program MsgBus.Init(); diff --git a/server/src/Controllers/OscilloscopeController.cs b/server/src/Controllers/OscilloscopeController.cs index 046426a..85b57cb 100644 --- a/server/src/Controllers/OscilloscopeController.cs +++ b/server/src/Controllers/OscilloscopeController.cs @@ -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控制器 - 普通用户权限 /// [ApiController] +[EnableCors("Development")] [Route("api/[controller]")] [Authorize] public class OscilloscopeApiController : ControllerBase @@ -20,7 +22,7 @@ public class OscilloscopeApiController : ControllerBase /// /// 获取示波器实例 /// - 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 /// 示波器配置 /// 操作结果 [HttpPost("Initialize")] - [EnableCors("Users")] [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] - public async Task Initialize([FromBody] OscilloscopeFullConfig config) + public async Task Initialize([FromBody] OscilloscopeConfig config) { try { @@ -151,7 +152,6 @@ public class OscilloscopeApiController : ControllerBase /// /// 操作结果 [HttpPost("StartCapture")] - [EnableCors("Users")] [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] @@ -185,7 +185,6 @@ public class OscilloscopeApiController : ControllerBase /// /// 操作结果 [HttpPost("StopCapture")] - [EnableCors("Users")] [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] @@ -219,7 +218,6 @@ public class OscilloscopeApiController : ControllerBase /// /// 示波器数据和状态信息 [HttpGet("GetData")] - [EnableCors("Users")] [ProducesResponseType(typeof(OscilloscopeDataResponse), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] @@ -293,7 +291,6 @@ public class OscilloscopeApiController : ControllerBase /// 触发边沿(true为上升沿,false为下降沿) /// 操作结果 [HttpPost("UpdateTrigger")] - [EnableCors("Users")] [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] @@ -338,7 +335,6 @@ public class OscilloscopeApiController : ControllerBase /// 抽样率(0-1023) /// 操作结果 [HttpPost("UpdateSampling")] - [EnableCors("Users")] [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] @@ -387,7 +383,6 @@ public class OscilloscopeApiController : ControllerBase /// /// 操作结果 [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, "操作失败,请稍后重试"); } } - - /// - /// 示波器完整配置 - /// - public class OscilloscopeFullConfig - { - /// - /// 是否启动捕获 - /// - public bool CaptureEnabled { get; set; } - - /// - /// 触发电平(0-255) - /// - public byte TriggerLevel { get; set; } - - /// - /// 触发边沿(true为上升沿,false为下降沿) - /// - public bool TriggerRisingEdge { get; set; } - - /// - /// 水平偏移量(0-1023) - /// - public ushort HorizontalShift { get; set; } - - /// - /// 抽样率(0-1023) - /// - public ushort DecimationRate { get; set; } - - /// - /// 是否自动刷新RAM - /// - public bool AutoRefreshRAM { get; set; } = true; - } - - /// - /// 示波器状态和数据 - /// - public class OscilloscopeDataResponse - { - /// - /// AD采样频率 - /// - public uint ADFrequency { get; set; } - - /// - /// AD采样幅度 - /// - public byte ADVpp { get; set; } - - /// - /// AD采样最大值 - /// - public byte ADMax { get; set; } - - /// - /// AD采样最小值 - /// - public byte ADMin { get; set; } - - /// - /// 波形数据(Base64编码) - /// - public string WaveformData { get; set; } = string.Empty; - } - } diff --git a/server/src/Hubs/DigitalTubesHub.cs b/server/src/Hubs/DigitalTubesHub.cs index 1187e90..809c838 100644 --- a/server/src/Hubs/DigitalTubesHub.cs +++ b/server/src/Hubs/DigitalTubesHub.cs @@ -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, IDigitalTubesHub private readonly IHubContext _hubContext; private readonly Database.UserManager _userManager = new(); - private ConcurrentDictionary<(string, string), ScanTaskInfo> _scanTasks = new(); + private ConcurrentDictionary<(string, string), DigitalTubesScanTaskInfo> _scanTasks = new(); public DigitalTubesHub(IHubContext hubContext) { @@ -100,7 +103,7 @@ public class DigitalTubesHub : Hub, 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, 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, IDigitalTubesHub if (_scanTasks.TryGetValue(key, out var scanInfo)) { - return new DigitalTubeTaskStatus(scanInfo); + return scanInfo.ToDigitalTubeTaskStatus(); } else { diff --git a/server/src/Hubs/OscilloscopeHub.cs b/server/src/Hubs/OscilloscopeHub.cs new file mode 100644 index 0000000..8b68530 --- /dev/null +++ b/server/src/Hubs/OscilloscopeHub.cs @@ -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 Initialize(OscilloscopeFullConfig config); + Task StartCapture(); + Task StopCapture(); + Task GetData(); + Task SetTrigger(byte level); + Task SetRisingEdge(bool risingEdge); + Task SetSampling(ushort decimationRate); + Task 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, IOscilloscopeHub +{ + private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); + private readonly IHubContext _hubContext; + private readonly Database.UserManager _userManager = new(); + + private ConcurrentDictionary<(string, string), OscilloscopeScanTaskInfo> _scanTasks = new(); + + public OscilloscopeHub(IHubContext hubContext) + { + _hubContext = hubContext; + } + + private Optional 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 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 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 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 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 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 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 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 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 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; + } + } +} diff --git a/server/src/Peripherals/OscilloscopeClient.cs b/server/src/Peripherals/OscilloscopeClient.cs index 1726775..11d6a32 100644 --- a/server/src/Peripherals/OscilloscopeClient.cs +++ b/server/src/Peripherals/OscilloscopeClient.cs @@ -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 /// 示波器设备IP地址 /// 示波器设备端口 /// 超时时间(毫秒) - 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; } + /// + /// 一次性初始化/配置示波器 + /// + /// 完整配置 + /// 操作结果,全部成功返回true,否则返回异常信息 + public async ValueTask> 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; + } + /// /// 控制示波器的捕获开关 /// @@ -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++) diff --git a/src/APIClient.ts b/src/APIClient.ts index 86468c1..0ebd0c3 100644 --- a/src/APIClient.ts +++ b/src/APIClient.ts @@ -299,7 +299,7 @@ export class VideoStreamClient { return Promise.resolve(null as any); } - setVideoStreamEnable(enable: boolean | undefined, cancelToken?: CancelToken): Promise { + setVideoStreamEnable(enable: boolean | undefined, cancelToken?: CancelToken): Promise { let url_ = this.baseUrl + "/api/VideoStream/SetVideoStreamEnable?"; if (enable === null) throw new Error("The parameter 'enable' cannot be null."); @@ -327,7 +327,7 @@ export class VideoStreamClient { }); } - protected processSetVideoStreamEnable(response: AxiosResponse): Promise { + protected processSetVideoStreamEnable(response: AxiosResponse): Promise { const status = response.status; let _headers: any = {}; if (response.headers && typeof response.headers === "object") { @@ -343,7 +343,7 @@ export class VideoStreamClient { let resultData200 = _responseText; result200 = resultData200 !== undefined ? resultData200 : null; - return Promise.resolve(result200); + return Promise.resolve(result200); } else if (status === 500) { const _responseText = response.data; @@ -357,7 +357,7 @@ export class VideoStreamClient { const _responseText = response.data; return throwException("An unexpected server error occurred.", status, _responseText, _headers); } - return Promise.resolve(null as any); + return Promise.resolve(null as any); } /** @@ -5569,7 +5569,7 @@ export class OscilloscopeApiClient { * @param config 示波器配置 * @return 操作结果 */ - initialize(config: OscilloscopeFullConfig, cancelToken?: CancelToken): Promise { + initialize(config: OscilloscopeConfig, cancelToken?: CancelToken): Promise { let url_ = this.baseUrl + "/api/OscilloscopeApi/Initialize"; url_ = url_.replace(/[?&]$/, ""); @@ -9090,22 +9090,14 @@ export interface INetworkInterfaceDto { macAddress: string; } -/** 示波器完整配置 */ -export class OscilloscopeFullConfig implements IOscilloscopeFullConfig { - /** 是否启动捕获 */ +export class OscilloscopeConfig implements IOscilloscopeConfig { captureEnabled!: boolean; - /** 触发电平(0-255) */ triggerLevel!: number; - /** 触发边沿(true为上升沿,false为下降沿) */ triggerRisingEdge!: boolean; - /** 水平偏移量(0-1023) */ horizontalShift!: number; - /** 抽样率(0-1023) */ decimationRate!: number; - /** 是否自动刷新RAM */ - autoRefreshRAM!: boolean; - constructor(data?: IOscilloscopeFullConfig) { + constructor(data?: IOscilloscopeConfig) { if (data) { for (var property in data) { if (data.hasOwnProperty(property)) @@ -9121,13 +9113,12 @@ export class OscilloscopeFullConfig implements IOscilloscopeFullConfig { this.triggerRisingEdge = _data["triggerRisingEdge"]; this.horizontalShift = _data["horizontalShift"]; this.decimationRate = _data["decimationRate"]; - this.autoRefreshRAM = _data["autoRefreshRAM"]; } } - static fromJS(data: any): OscilloscopeFullConfig { + static fromJS(data: any): OscilloscopeConfig { data = typeof data === 'object' ? data : {}; - let result = new OscilloscopeFullConfig(); + let result = new OscilloscopeConfig(); result.init(data); return result; } @@ -9139,38 +9130,23 @@ export class OscilloscopeFullConfig implements IOscilloscopeFullConfig { data["triggerRisingEdge"] = this.triggerRisingEdge; data["horizontalShift"] = this.horizontalShift; data["decimationRate"] = this.decimationRate; - data["autoRefreshRAM"] = this.autoRefreshRAM; return data; } } -/** 示波器完整配置 */ -export interface IOscilloscopeFullConfig { - /** 是否启动捕获 */ +export interface IOscilloscopeConfig { captureEnabled: boolean; - /** 触发电平(0-255) */ triggerLevel: number; - /** 触发边沿(true为上升沿,false为下降沿) */ triggerRisingEdge: boolean; - /** 水平偏移量(0-1023) */ horizontalShift: number; - /** 抽样率(0-1023) */ decimationRate: number; - /** 是否自动刷新RAM */ - autoRefreshRAM: boolean; } -/** 示波器状态和数据 */ export class OscilloscopeDataResponse implements IOscilloscopeDataResponse { - /** AD采样频率 */ adFrequency!: number; - /** AD采样幅度 */ adVpp!: number; - /** AD采样最大值 */ adMax!: number; - /** AD采样最小值 */ adMin!: number; - /** 波形数据(Base64编码) */ waveformData!: string; constructor(data?: IOscilloscopeDataResponse) { @@ -9210,17 +9186,11 @@ export class OscilloscopeDataResponse implements IOscilloscopeDataResponse { } } -/** 示波器状态和数据 */ export interface IOscilloscopeDataResponse { - /** AD采样频率 */ adFrequency: number; - /** AD采样幅度 */ adVpp: number; - /** AD采样最大值 */ adMax: number; - /** AD采样最小值 */ adMin: number; - /** 波形数据(Base64编码) */ waveformData: string; } diff --git a/src/components/Oscilloscope/OscilloscopeManager.ts b/src/components/Oscilloscope/OscilloscopeManager.ts index 6d007f5..71bf682 100644 --- a/src/components/Oscilloscope/OscilloscopeManager.ts +++ b/src/components/Oscilloscope/OscilloscopeManager.ts @@ -1,14 +1,35 @@ -import { autoResetRef, createInjectionState } from "@vueuse/core"; -import { shallowRef, reactive, ref, computed } from "vue"; -import { Mutex } from "async-mutex"; import { - OscilloscopeFullConfig, - OscilloscopeDataResponse, - OscilloscopeApiClient, -} from "@/APIClient"; + autoResetRef, + createInjectionState, + watchDebounced, +} from "@vueuse/core"; +import { + shallowRef, + reactive, + ref, + computed, + onMounted, + onUnmounted, + watchEffect, +} from "vue"; +import { Mutex } from "async-mutex"; +import { OscilloscopeApiClient } from "@/APIClient"; import { AuthManager } from "@/utils/AuthManager"; import { useAlertStore } from "@/components/Alert"; import { useRequiredInjection } from "@/utils/Common"; +import type { HubConnection } from "@microsoft/signalr"; +import type { + IOscilloscopeHub, + IOscilloscopeReceiver, +} from "@/utils/signalR/TypedSignalR.Client/server.Hubs"; +import { + getHubProxyFactory, + getReceiverRegister, +} from "@/utils/signalR/TypedSignalR.Client"; +import type { + OscilloscopeDataResponse, + OscilloscopeFullConfig, +} from "@/utils/signalR/server.Hubs"; export type OscilloscopeDataType = { x: number[]; @@ -22,41 +43,103 @@ export type OscilloscopeDataType = { }; // 默认配置 -const DEFAULT_CONFIG: OscilloscopeFullConfig = new OscilloscopeFullConfig({ +const DEFAULT_CONFIG: OscilloscopeFullConfig = { captureEnabled: false, triggerLevel: 128, triggerRisingEdge: true, horizontalShift: 0, decimationRate: 50, - autoRefreshRAM: false, -}); + captureFrequency: 100, +}; // 采样频率常量(后端返回) const [useProvideOscilloscope, useOscilloscopeState] = createInjectionState( () => { - const oscData = shallowRef(); + // Global Store const alert = useRequiredInjection(useAlertStore); + // Data + const oscData = shallowRef(); + const clearOscilloscopeData = () => { + oscData.value = undefined; + }; + + // SignalR Hub + const oscilloscopeHub = shallowRef<{ + connection: HubConnection; + proxy: IOscilloscopeHub; + } | null>(null); + + const oscilloscopeReceiver: IOscilloscopeReceiver = { + onDataReceived: async (data) => { + analyzeOscilloscopeData(data); + }, + }; + + onMounted(() => { + initHub(); + }); + + onUnmounted(() => { + clearHub(); + }); + + function initHub() { + if (oscilloscopeHub.value) return; + + const connection = AuthManager.createHubConnection("OscilloscopeHub"); + + const proxy = + getHubProxyFactory("IOscilloscopeHub").createHubProxy(connection); + + getReceiverRegister("IOscilloscopeReceiver").register( + connection, + oscilloscopeReceiver, + ); + connection.start(); + oscilloscopeHub.value = { connection, proxy }; + } + + function clearHub() { + if (!oscilloscopeHub.value) return; + oscilloscopeHub.value.connection.stop(); + oscilloscopeHub.value = null; + } + + function reinitializeHub() { + clearHub(); + initHub(); + } + + function getHubProxy() { + if (!oscilloscopeHub.value) throw new Error("Hub not initialized"); + return oscilloscopeHub.value.proxy; + } + // 互斥锁 const operationMutex = new Mutex(); // 状态 const isApplying = ref(false); const isCapturing = ref(false); + const isAutoApplying = ref(false); // 配置 - const config = reactive( - new OscilloscopeFullConfig({ ...DEFAULT_CONFIG }), - ); + const config = reactive({ ...DEFAULT_CONFIG }); + watchDebounced( + config, + () => { + if (!isAutoApplying.value) return; - // 采样点数(由后端数据决定) - const sampleCount = ref(0); - - // 采样周期(ns),由adFrequency计算 - const samplePeriodNs = computed(() => - oscData.value?.adFrequency - ? 1_000_000_000 / oscData.value.adFrequency - : 200, + if ( + !isApplying.value || + !isCapturing.value || + !operationMutex.isLocked() + ) { + applyConfiguration(); + } + }, + { debounce: 200, maxWait: 1000 }, ); // 应用配置 @@ -68,14 +151,18 @@ const [useProvideOscilloscope, useOscilloscopeState] = createInjectionState( const release = await operationMutex.acquire(); isApplying.value = true; try { - const client = AuthManager.createClient(OscilloscopeApiClient); - const success = await client.initialize({ ...config }); + const proxy = getHubProxy(); + + const success = await proxy.initialize(config); + if (success) { alert.success("示波器配置已应用", 2000); } else { throw new Error("应用失败"); } } catch (error) { + if (error instanceof Error && error.message === "Hub not initialized") + reinitializeHub(); alert.error("应用配置失败", 3000); } finally { isApplying.value = false; @@ -89,68 +176,55 @@ const [useProvideOscilloscope, useOscilloscopeState] = createInjectionState( alert.info("配置已重置", 2000); }; - const clearOscilloscopeData = () => { - oscData.value = undefined; + // 采样点数(由后端数据决定) + const sampleCount = ref(0); + + // 采样周期(ns),由adFrequency计算 + const samplePeriodNs = computed(() => + oscData.value?.adFrequency + ? 1_000_000_000 / oscData.value.adFrequency + : 200, + ); + + const analyzeOscilloscopeData = (resp: OscilloscopeDataResponse) => { + // 解析波形数据 + const binaryString = atob(resp.waveformData); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + sampleCount.value = bytes.length; + + // 构建时间轴 + const x = Array.from( + { length: bytes.length }, + (_, i) => (i * samplePeriodNs.value) / 1000, // us + ); + const y = Array.from(bytes); + + oscData.value = { + x, + y, + xUnit: "us", + yUnit: "V", + adFrequency: resp.aDFrequency, + adVpp: resp.aDVpp, + adMax: resp.aDMax, + adMin: resp.aDMin, + }; }; // 获取数据 const getOscilloscopeData = async () => { try { - const client = AuthManager.createClient(OscilloscopeApiClient); - const resp: OscilloscopeDataResponse = await client.getData(); - - // 解析波形数据 - const binaryString = atob(resp.waveformData); - const bytes = new Uint8Array(binaryString.length); - for (let i = 0; i < binaryString.length; i++) { - bytes[i] = binaryString.charCodeAt(i); - } - sampleCount.value = bytes.length; - - // 构建时间轴 - const x = Array.from( - { length: bytes.length }, - (_, i) => (i * samplePeriodNs.value) / 1000, // us - ); - const y = Array.from(bytes); - - oscData.value = { - x, - y, - xUnit: "us", - yUnit: "V", - adFrequency: resp.adFrequency, - adVpp: resp.adVpp, - adMax: resp.adMax, - adMin: resp.adMin, - }; + const proxy = getHubProxy(); + const resp = await proxy.getData(); + analyzeOscilloscopeData(resp); } catch (error) { alert.error("获取示波器数据失败", 3000); } }; - // 定时器引用 - let refreshIntervalId: number | undefined; - // 刷新间隔(毫秒),可根据需要调整 - const refreshIntervalMs = ref(1000); - - // 定时刷新函数 - const startAutoRefresh = () => { - if (refreshIntervalId !== undefined) return; - refreshIntervalId = window.setInterval(async () => { - await refreshRAM(); - await getOscilloscopeData(); - }, refreshIntervalMs.value); - }; - - const stopAutoRefresh = () => { - if (refreshIntervalId !== undefined) { - clearInterval(refreshIntervalId); - refreshIntervalId = undefined; - isCapturing.value = false; - } - }; - // 启动捕获 const startCapture = async () => { if (operationMutex.isLocked()) { @@ -160,17 +234,13 @@ const [useProvideOscilloscope, useOscilloscopeState] = createInjectionState( isCapturing.value = true; const release = await operationMutex.acquire(); try { - const client = AuthManager.createClient(OscilloscopeApiClient); - const started = await client.startCapture(); + const proxy = getHubProxy(); + const started = await proxy.startCapture(); if (!started) throw new Error("无法启动捕获"); alert.info("开始捕获...", 2000); - - // 启动定时刷新 - startAutoRefresh(); } catch (error) { alert.error("捕获失败", 3000); isCapturing.value = false; - stopAutoRefresh(); } finally { release(); } @@ -183,11 +253,10 @@ const [useProvideOscilloscope, useOscilloscopeState] = createInjectionState( return; } isCapturing.value = false; - stopAutoRefresh(); const release = await operationMutex.acquire(); try { - const client = AuthManager.createClient(OscilloscopeApiClient); - const stopped = await client.stopCapture(); + const proxy = getHubProxy(); + const stopped = await proxy.stopCapture(); if (!stopped) throw new Error("无法停止捕获"); alert.info("捕获已停止", 2000); } catch (error) { @@ -197,6 +266,14 @@ const [useProvideOscilloscope, useOscilloscopeState] = createInjectionState( } }; + const toggleCapture = async () => { + if (isCapturing.value) { + await stopCapture(); + } else { + await startCapture(); + } + }; + // 更新触发参数 const updateTrigger = async (level: number, risingEdge: boolean) => { const client = AuthManager.createClient(OscilloscopeApiClient); @@ -279,9 +356,9 @@ const [useProvideOscilloscope, useOscilloscopeState] = createInjectionState( config, isApplying, isCapturing, + isAutoApplying, sampleCount, samplePeriodNs, - refreshIntervalMs, applyConfiguration, resetConfiguration, @@ -289,6 +366,7 @@ const [useProvideOscilloscope, useOscilloscopeState] = createInjectionState( getOscilloscopeData, startCapture, stopCapture, + toggleCapture, updateTrigger, updateSampling, refreshRAM, diff --git a/src/components/Oscilloscope/OscilloscopeWaveformDisplay.vue b/src/components/Oscilloscope/OscilloscopeWaveformDisplay.vue index c689aec..7779138 100644 --- a/src/components/Oscilloscope/OscilloscopeWaveformDisplay.vue +++ b/src/components/Oscilloscope/OscilloscopeWaveformDisplay.vue @@ -1,36 +1,93 @@ @@ -61,7 +118,7 @@ import type { GridComponentOption, } from "echarts/components"; import { useRequiredInjection } from "@/utils/Common"; -import { Play, Square } from "lucide-vue-next"; +import { Play, Square, Activity } from "lucide-vue-next"; use([ TooltipComponent, @@ -113,12 +170,23 @@ const option = computed((): EChartsOption => { ? (oscData.value.y as number[][]) : [oscData.value.y as number[]]; + // 预定义的通道颜色 + const channelColors = [ + "#3B82F6", // blue-500 + "#EF4444", // red-500 + "#10B981", // emerald-500 + "#F59E0B", // amber-500 + "#8B5CF6", // violet-500 + "#06B6D4", // cyan-500 + ]; + forEach(yChannels, (yData, index) => { if (!oscData.value || !yData) return; const seriesData = oscData.value.x.map((xValue, i) => [ xValue, yData && yData[i] !== undefined ? yData[i] : 0, ]); + series.push({ type: "line", name: `通道 ${index + 1}`, @@ -126,41 +194,82 @@ const option = computed((): EChartsOption => { smooth: false, symbol: "none", lineStyle: { - width: 2, + width: 2.5, + color: channelColors[index % channelColors.length], + shadowColor: channelColors[index % channelColors.length], + shadowBlur: isCapturing ? 0 : 4, + shadowOffsetY: 2, }, - // 关闭系列动画 + itemStyle: { + color: channelColors[index % channelColors.length], + }, + // 动画配置 animation: !isCapturing, - animationDuration: isCapturing ? 0 : 1000, + animationDuration: isCapturing ? 0 : 1200, animationEasing: isCapturing ? "linear" : "cubicOut", + animationDelay: index * 100, // 错开动画时间 }); }); return { + backgroundColor: "transparent", grid: { - left: "10%", - right: "10%", - top: "15%", - bottom: "25%", + left: "8%", + right: "5%", + top: "12%", + bottom: "20%", + borderWidth: 1, + borderColor: "#E2E8F0", + backgroundColor: "rgba(248, 250, 252, 0.8)", }, tooltip: { trigger: "axis", + backgroundColor: "rgba(255, 255, 255, 0.95)", + borderColor: "#E2E8F0", + borderWidth: 1, + textStyle: { + color: "#334155", + fontSize: 12, + }, formatter: (params: any) => { if (!oscData.value) return ""; - let result = `时间: ${params[0].data[0].toFixed(2)} ${oscData.value.xUnit}
`; + let result = `
时间: ${params[0].data[0].toFixed(2)} ${oscData.value.xUnit}
`; params.forEach((param: any) => { - result += `${param.seriesName}: ${param.data[1].toFixed(3)} ${oscData.value?.yUnit ?? ""}
`; + result += `
● ${param.seriesName}: ${param.data[1].toFixed(3)} ${oscData.value?.yUnit ?? ""}
`; }); return result; }, }, legend: { - top: "5%", + top: "2%", + left: "center", + textStyle: { + color: "#64748B", + fontSize: 12, + fontWeight: 500, + }, + itemGap: 20, data: series.map((s) => s.name) as string[], }, toolbox: { + right: "2%", + top: "2%", feature: { - restore: {}, - saveAsImage: {}, + restore: { + title: "重置缩放", + }, + saveAsImage: { + title: "保存图片", + name: `oscilloscope_${new Date().toISOString().slice(0, 19)}`, + }, + }, + iconStyle: { + borderColor: "#64748B", + }, + emphasis: { + iconStyle: { + borderColor: "#3B82F6", + }, }, }, dataZoom: [ @@ -168,47 +277,275 @@ const option = computed((): EChartsOption => { type: "inside", start: 0, end: 100, + filterMode: "weakFilter", }, { start: 0, end: 100, + height: 25, + bottom: "8%", + borderColor: "#E2E8F0", + fillerColor: "rgba(59, 130, 246, 0.1)", + handleStyle: { + color: "#3B82F6", + borderColor: "#1E40AF", + }, + textStyle: { + color: "#64748B", + fontSize: 11, + }, }, ], xAxis: { type: "value", name: oscData.value ? `时间 (${oscData.value.xUnit})` : "时间", nameLocation: "middle", - nameGap: 30, + nameGap: 35, + nameTextStyle: { + color: "#64748B", + fontSize: 12, + fontWeight: 500, + }, axisLine: { show: true, + lineStyle: { + color: "#CBD5E1", + width: 1.5, + }, }, axisTick: { show: true, + lineStyle: { + color: "#E2E8F0", + }, + }, + axisLabel: { + color: "#64748B", + fontSize: 11, }, splitLine: { - show: false, + show: true, + lineStyle: { + color: "#F1F5F9", + type: "dashed", + }, }, }, yAxis: { type: "value", name: oscData.value ? `电压 (${oscData.value.yUnit})` : "电压", nameLocation: "middle", - nameGap: 40, + nameGap: 50, + nameTextStyle: { + color: "#64748B", + fontSize: 12, + fontWeight: 500, + }, axisLine: { show: true, + lineStyle: { + color: "#CBD5E1", + width: 1.5, + }, }, axisTick: { show: true, + lineStyle: { + color: "#E2E8F0", + }, + }, + axisLabel: { + color: "#64748B", + fontSize: 11, }, splitLine: { - show: false, + show: true, + lineStyle: { + color: "#F1F5F9", + type: "dashed", + }, }, }, // 全局动画开关 animation: !isCapturing, - animationDuration: isCapturing ? 0 : 1000, + animationDuration: isCapturing ? 0 : 1200, animationEasing: isCapturing ? "linear" : "cubicOut", series: series, }; }); + + diff --git a/src/utils/AuthManager.ts b/src/utils/AuthManager.ts index bb9846d..6928b59 100644 --- a/src/utils/AuthManager.ts +++ b/src/utils/AuthManager.ts @@ -45,7 +45,12 @@ export class AuthManager { // SignalR连接 - 简单明了 static createHubConnection( - hubPath: "ProgressHub" | "JtagHub" | "DigitalTubesHub" | "RotaryEncoderHub", + hubPath: + | "ProgressHub" + | "JtagHub" + | "DigitalTubesHub" + | "RotaryEncoderHub" + | "OscilloscopeHub", ) { return new HubConnectionBuilder() .withUrl(`http://127.0.0.1:5000/hubs/${hubPath}`, { diff --git a/src/utils/signalR/TypedSignalR.Client/index.ts b/src/utils/signalR/TypedSignalR.Client/index.ts index f740eae..26364da 100644 --- a/src/utils/signalR/TypedSignalR.Client/index.ts +++ b/src/utils/signalR/TypedSignalR.Client/index.ts @@ -3,8 +3,8 @@ /* tslint:disable */ // @ts-nocheck import type { HubConnection, IStreamResult, Subject } from '@microsoft/signalr'; -import type { IDigitalTubesHub, IJtagHub, IProgressHub, IRotaryEncoderHub, IDigitalTubesReceiver, IJtagReceiver, IProgressReceiver, IRotaryEncoderReceiver } from './server.Hubs'; -import type { DigitalTubeTaskStatus, ProgressInfo } from '../server.Hubs'; +import type { IDigitalTubesHub, IJtagHub, IOscilloscopeHub, IProgressHub, IRotaryEncoderHub, IDigitalTubesReceiver, IJtagReceiver, IOscilloscopeReceiver, IProgressReceiver, IRotaryEncoderReceiver } from './server.Hubs'; +import type { DigitalTubeTaskStatus, OscilloscopeFullConfig, OscilloscopeDataResponse, ProgressInfo } from '../server.Hubs'; import type { RotaryEncoderDirection } from '../Peripherals.RotaryEncoderClient'; @@ -46,6 +46,7 @@ class ReceiverMethodSubscription implements Disposable { export type HubProxyFactoryProvider = { (hubType: "IDigitalTubesHub"): HubProxyFactory; (hubType: "IJtagHub"): HubProxyFactory; + (hubType: "IOscilloscopeHub"): HubProxyFactory; (hubType: "IProgressHub"): HubProxyFactory; (hubType: "IRotaryEncoderHub"): HubProxyFactory; } @@ -57,6 +58,9 @@ export const getHubProxyFactory = ((hubType: string) => { if(hubType === "IJtagHub") { return IJtagHub_HubProxyFactory.Instance; } + if(hubType === "IOscilloscopeHub") { + return IOscilloscopeHub_HubProxyFactory.Instance; + } if(hubType === "IProgressHub") { return IProgressHub_HubProxyFactory.Instance; } @@ -68,6 +72,7 @@ export const getHubProxyFactory = ((hubType: string) => { export type ReceiverRegisterProvider = { (receiverType: "IDigitalTubesReceiver"): ReceiverRegister; (receiverType: "IJtagReceiver"): ReceiverRegister; + (receiverType: "IOscilloscopeReceiver"): ReceiverRegister; (receiverType: "IProgressReceiver"): ReceiverRegister; (receiverType: "IRotaryEncoderReceiver"): ReceiverRegister; } @@ -79,6 +84,9 @@ export const getReceiverRegister = ((receiverType: string) => { if(receiverType === "IJtagReceiver") { return IJtagReceiver_Binder.Instance; } + if(receiverType === "IOscilloscopeReceiver") { + return IOscilloscopeReceiver_Binder.Instance; + } if(receiverType === "IProgressReceiver") { return IProgressReceiver_Binder.Instance; } @@ -151,6 +159,55 @@ class IJtagHub_HubProxy implements IJtagHub { } } +class IOscilloscopeHub_HubProxyFactory implements HubProxyFactory { + public static Instance = new IOscilloscopeHub_HubProxyFactory(); + + private constructor() { + } + + public readonly createHubProxy = (connection: HubConnection): IOscilloscopeHub => { + return new IOscilloscopeHub_HubProxy(connection); + } +} + +class IOscilloscopeHub_HubProxy implements IOscilloscopeHub { + + public constructor(private connection: HubConnection) { + } + + public readonly initialize = async (config: OscilloscopeFullConfig): Promise => { + return await this.connection.invoke("Initialize", config); + } + + public readonly startCapture = async (): Promise => { + return await this.connection.invoke("StartCapture"); + } + + public readonly stopCapture = async (): Promise => { + return await this.connection.invoke("StopCapture"); + } + + public readonly getData = async (): Promise => { + return await this.connection.invoke("GetData"); + } + + public readonly setTrigger = async (level: number): Promise => { + return await this.connection.invoke("SetTrigger", level); + } + + public readonly setRisingEdge = async (risingEdge: boolean): Promise => { + return await this.connection.invoke("SetRisingEdge", risingEdge); + } + + public readonly setSampling = async (decimationRate: number): Promise => { + return await this.connection.invoke("SetSampling", decimationRate); + } + + public readonly setFrequency = async (frequency: number): Promise => { + return await this.connection.invoke("SetFrequency", frequency); + } +} + class IProgressHub_HubProxyFactory implements HubProxyFactory { public static Instance = new IProgressHub_HubProxyFactory(); @@ -258,6 +315,27 @@ class IJtagReceiver_Binder implements ReceiverRegister { } } +class IOscilloscopeReceiver_Binder implements ReceiverRegister { + + public static Instance = new IOscilloscopeReceiver_Binder(); + + private constructor() { + } + + public readonly register = (connection: HubConnection, receiver: IOscilloscopeReceiver): Disposable => { + + const __onDataReceived = (...args: [OscilloscopeDataResponse]) => receiver.onDataReceived(...args); + + connection.on("OnDataReceived", __onDataReceived); + + const methodList: ReceiverMethod[] = [ + { methodName: "OnDataReceived", method: __onDataReceived } + ] + + return new ReceiverMethodSubscription(connection, methodList); + } +} + class IProgressReceiver_Binder implements ReceiverRegister { public static Instance = new IProgressReceiver_Binder(); diff --git a/src/utils/signalR/TypedSignalR.Client/server.Hubs.ts b/src/utils/signalR/TypedSignalR.Client/server.Hubs.ts index 4385eb3..7ce63f2 100644 --- a/src/utils/signalR/TypedSignalR.Client/server.Hubs.ts +++ b/src/utils/signalR/TypedSignalR.Client/server.Hubs.ts @@ -3,7 +3,7 @@ /* tslint:disable */ // @ts-nocheck import type { IStreamResult, Subject } from '@microsoft/signalr'; -import type { DigitalTubeTaskStatus, ProgressInfo } from '../server.Hubs'; +import type { DigitalTubeTaskStatus, OscilloscopeFullConfig, OscilloscopeDataResponse, ProgressInfo } from '../server.Hubs'; import type { RotaryEncoderDirection } from '../Peripherals.RotaryEncoderClient'; export type IDigitalTubesHub = { @@ -43,6 +43,46 @@ export type IJtagHub = { stopBoundaryScan(): Promise; } +export type IOscilloscopeHub = { + /** + * @param config Transpiled from server.Hubs.OscilloscopeFullConfig + * @returns Transpiled from System.Threading.Tasks.Task + */ + initialize(config: OscilloscopeFullConfig): Promise; + /** + * @returns Transpiled from System.Threading.Tasks.Task + */ + startCapture(): Promise; + /** + * @returns Transpiled from System.Threading.Tasks.Task + */ + stopCapture(): Promise; + /** + * @returns Transpiled from System.Threading.Tasks.Task + */ + getData(): Promise; + /** + * @param level Transpiled from byte + * @returns Transpiled from System.Threading.Tasks.Task + */ + setTrigger(level: number): Promise; + /** + * @param risingEdge Transpiled from bool + * @returns Transpiled from System.Threading.Tasks.Task + */ + setRisingEdge(risingEdge: boolean): Promise; + /** + * @param decimationRate Transpiled from ushort + * @returns Transpiled from System.Threading.Tasks.Task + */ + setSampling(decimationRate: number): Promise; + /** + * @param frequency Transpiled from int + * @returns Transpiled from System.Threading.Tasks.Task + */ + setFrequency(frequency: number): Promise; +} + export type IProgressHub = { /** * @param taskId Transpiled from string @@ -102,6 +142,14 @@ export type IJtagReceiver = { onReceiveBoundaryScanData(msg: Partial>): Promise; } +export type IOscilloscopeReceiver = { + /** + * @param data Transpiled from server.Hubs.OscilloscopeDataResponse + * @returns Transpiled from System.Threading.Tasks.Task + */ + onDataReceived(data: OscilloscopeDataResponse): Promise; +} + export type IProgressReceiver = { /** * @param message Transpiled from server.Hubs.ProgressInfo diff --git a/src/utils/signalR/server.Hubs.ts b/src/utils/signalR/server.Hubs.ts index 647c84f..3da53ca 100644 --- a/src/utils/signalR/server.Hubs.ts +++ b/src/utils/signalR/server.Hubs.ts @@ -10,6 +10,36 @@ export type DigitalTubeTaskStatus = { isRunning: boolean; } +/** Transpiled from server.Hubs.OscilloscopeDataResponse */ +export type OscilloscopeDataResponse = { + /** Transpiled from uint */ + aDFrequency: number; + /** Transpiled from byte */ + aDVpp: number; + /** Transpiled from byte */ + aDMax: number; + /** Transpiled from byte */ + aDMin: number; + /** Transpiled from string */ + waveformData: string; +} + +/** Transpiled from server.Hubs.OscilloscopeFullConfig */ +export type OscilloscopeFullConfig = { + /** Transpiled from bool */ + captureEnabled: boolean; + /** Transpiled from byte */ + triggerLevel: number; + /** Transpiled from bool */ + triggerRisingEdge: boolean; + /** Transpiled from ushort */ + horizontalShift: number; + /** Transpiled from ushort */ + decimationRate: number; + /** Transpiled from int */ + captureFrequency: number; +} + /** Transpiled from server.Hubs.ProgressStatus */ export enum ProgressStatus { Running = 0, diff --git a/src/views/Project/Oscilloscope.vue b/src/views/Project/Oscilloscope.vue index ac1561e..9cfa268 100644 --- a/src/views/Project/Oscilloscope.vue +++ b/src/views/Project/Oscilloscope.vue @@ -1,109 +1,336 @@ + +