diff --git a/components.d.ts b/components.d.ts index c80ebed..6ba3abd 100644 --- a/components.d.ts +++ b/components.d.ts @@ -28,6 +28,7 @@ declare module 'vue' { MotherBoard: typeof import('./src/components/equipments/MotherBoard.vue')['default'] MotherBoardCaps: typeof import('./src/components/equipments/MotherBoardCaps.vue')['default'] Navbar: typeof import('./src/components/Navbar.vue')['default'] + OscilloscopeWaveformDisplay: typeof import('./src/components/Oscilloscope/OscilloscopeWaveformDisplay.vue')['default'] PG2L100H_FBG676: typeof import('./src/components/equipments/PG2L100H_FBG676.vue')['default'] Pin: typeof import('./src/components/equipments/Pin.vue')['default'] PopButton: typeof import('./src/components/PopButton.vue')['default'] @@ -48,7 +49,7 @@ declare module 'vue' { TriggerSettings: typeof import('./src/components/LogicAnalyzer/TriggerSettings.vue')['default'] TutorialCarousel: typeof import('./src/components/TutorialCarousel.vue')['default'] UploadCard: typeof import('./src/components/UploadCard.vue')['default'] - WaveformDisplay: typeof import('./src/components/Oscilloscope/WaveformDisplay.vue')['default'] + WaveformDisplay: typeof import('./src/components/WaveformDisplay/WaveformDisplay.vue')['default'] Wire: typeof import('./src/components/equipments/Wire.vue')['default'] } } diff --git a/server/src/Controllers/OscilloscopeController.cs b/server/src/Controllers/OscilloscopeController.cs new file mode 100644 index 0000000..f7bce73 --- /dev/null +++ b/server/src/Controllers/OscilloscopeController.cs @@ -0,0 +1,494 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Mvc; +using Peripherals.OscilloscopeClient; + +namespace server.Controllers; + +/// +/// 示波器API控制器 - 普通用户权限 +/// +[ApiController] +[Route("api/[controller]")] +[Authorize(Roles = "User")] +public class OscilloscopeApiController : ControllerBase +{ + private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); + + /// + /// 示波器完整配置 + /// + 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; + } + + /// + /// 获取示波器实例 + /// + private Oscilloscope? GetOscilloscope() + { + try + { + var userName = User.Identity?.Name; + if (string.IsNullOrEmpty(userName)) + return null; + + using var db = new Database.AppDataConnection(); + var userRet = db.GetUserByName(userName); + if (!userRet.IsSuccessful || !userRet.Value.HasValue) + return null; + + var user = userRet.Value.Value; + if (user.BoardID == Guid.Empty) + return null; + + var boardRet = db.GetBoardByID(user.BoardID); + if (!boardRet.IsSuccessful || !boardRet.Value.HasValue) + return null; + + var board = boardRet.Value.Value; + return new Oscilloscope(board.IpAddr, board.Port); + } + catch (Exception ex) + { + logger.Error(ex, "获取示波器实例时发生异常"); + return null; + } + } + + /// + /// 初始化示波器 + /// + /// 示波器配置 + /// 操作结果 + [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) + { + try + { + if (config == null) + return BadRequest("配置参数不能为空"); + + if (config.HorizontalShift > 1023) + return BadRequest("水平偏移量必须在0-1023之间"); + + if (config.DecimationRate > 1023) + return BadRequest("抽样率必须在0-1023之间"); + + var oscilloscope = GetOscilloscope(); + if (oscilloscope == null) + return BadRequest("用户未绑定有效的实验板"); + + // 首先关闭捕获 + var stopResult = await oscilloscope.SetCaptureEnable(false); + if (!stopResult.IsSuccessful) + { + logger.Error($"关闭捕获失败: {stopResult.Error}"); + return StatusCode(StatusCodes.Status500InternalServerError, "关闭捕获失败"); + } + + // 设置触发电平 + var levelResult = await oscilloscope.SetTriggerLevel(config.TriggerLevel); + if (!levelResult.IsSuccessful) + { + logger.Error($"设置触发电平失败: {levelResult.Error}"); + return StatusCode(StatusCodes.Status500InternalServerError, "设置触发电平失败"); + } + + // 设置触发边沿 + var edgeResult = await oscilloscope.SetTriggerEdge(config.TriggerRisingEdge); + if (!edgeResult.IsSuccessful) + { + logger.Error($"设置触发边沿失败: {edgeResult.Error}"); + return StatusCode(StatusCodes.Status500InternalServerError, "设置触发边沿失败"); + } + + // 设置水平偏移量 + var shiftResult = await oscilloscope.SetHorizontalShift(config.HorizontalShift); + if (!shiftResult.IsSuccessful) + { + logger.Error($"设置水平偏移量失败: {shiftResult.Error}"); + return StatusCode(StatusCodes.Status500InternalServerError, "设置水平偏移量失败"); + } + + // 设置抽样率 + var rateResult = await oscilloscope.SetDecimationRate(config.DecimationRate); + if (!rateResult.IsSuccessful) + { + logger.Error($"设置抽样率失败: {rateResult.Error}"); + return StatusCode(StatusCodes.Status500InternalServerError, "设置抽样率失败"); + } + + // 刷新RAM + if (config.AutoRefreshRAM) + { + var refreshResult = await oscilloscope.RefreshRAM(); + if (!refreshResult.IsSuccessful) + { + logger.Error($"刷新RAM失败: {refreshResult.Error}"); + return StatusCode(StatusCodes.Status500InternalServerError, "刷新RAM失败"); + } + } + + // 设置捕获开关 + var captureResult = await oscilloscope.SetCaptureEnable(config.CaptureEnabled); + if (!captureResult.IsSuccessful) + { + logger.Error($"设置捕获开关失败: {captureResult.Error}"); + return StatusCode(StatusCodes.Status500InternalServerError, "设置捕获开关失败"); + } + + return Ok(true); + } + catch (Exception ex) + { + logger.Error(ex, "初始化示波器时发生异常"); + return StatusCode(StatusCodes.Status500InternalServerError, "操作失败,请稍后重试"); + } + } + + /// + /// 启动捕获 + /// + /// 操作结果 + [HttpPost("StartCapture")] + [EnableCors("Users")] + [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + public async Task StartCapture() + { + try + { + var oscilloscope = GetOscilloscope(); + if (oscilloscope == null) + return BadRequest("用户未绑定有效的实验板"); + + var result = await oscilloscope.SetCaptureEnable(true); + if (!result.IsSuccessful) + { + logger.Error($"启动捕获失败: {result.Error}"); + return StatusCode(StatusCodes.Status500InternalServerError, "启动捕获失败"); + } + + return Ok(result.Value); + } + catch (Exception ex) + { + logger.Error(ex, "启动捕获时发生异常"); + return StatusCode(StatusCodes.Status500InternalServerError, "操作失败,请稍后重试"); + } + } + + /// + /// 停止捕获 + /// + /// 操作结果 + [HttpPost("StopCapture")] + [EnableCors("Users")] + [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + public async Task StopCapture() + { + try + { + var oscilloscope = GetOscilloscope(); + if (oscilloscope == null) + return BadRequest("用户未绑定有效的实验板"); + + var result = await oscilloscope.SetCaptureEnable(false); + if (!result.IsSuccessful) + { + logger.Error($"停止捕获失败: {result.Error}"); + return StatusCode(StatusCodes.Status500InternalServerError, "停止捕获失败"); + } + + return Ok(result.Value); + } + catch (Exception ex) + { + logger.Error(ex, "停止捕获时发生异常"); + return StatusCode(StatusCodes.Status500InternalServerError, "操作失败,请稍后重试"); + } + } + + /// + /// 获取示波器数据和状态 + /// + /// 示波器数据和状态信息 + [HttpGet("GetData")] + [EnableCors("Users")] + [ProducesResponseType(typeof(OscilloscopeDataResponse), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + public async Task GetData() + { + try + { + var oscilloscope = GetOscilloscope(); + if (oscilloscope == null) + return BadRequest("用户未绑定有效的实验板"); + + // 并行获取所有数据 + var freqTask = oscilloscope.GetADFrequency(); + var vppTask = oscilloscope.GetADVpp(); + var maxTask = oscilloscope.GetADMax(); + var minTask = oscilloscope.GetADMin(); + var waveformTask = oscilloscope.GetWaveformData(); + + await Task.WhenAll(freqTask.AsTask(), vppTask.AsTask(), maxTask.AsTask(), + minTask.AsTask(), waveformTask.AsTask()); + + var freqResult = await freqTask; + var vppResult = await vppTask; + var maxResult = await maxTask; + var minResult = await minTask; + var waveformResult = await waveformTask; + + if (!freqResult.IsSuccessful) + { + logger.Error($"获取AD采样频率失败: {freqResult.Error}"); + return StatusCode(StatusCodes.Status500InternalServerError, "获取AD采样频率失败"); + } + + if (!vppResult.IsSuccessful) + { + logger.Error($"获取AD采样幅度失败: {vppResult.Error}"); + return StatusCode(StatusCodes.Status500InternalServerError, "获取AD采样幅度失败"); + } + + if (!maxResult.IsSuccessful) + { + logger.Error($"获取AD采样最大值失败: {maxResult.Error}"); + return StatusCode(StatusCodes.Status500InternalServerError, "获取AD采样最大值失败"); + } + + if (!minResult.IsSuccessful) + { + logger.Error($"获取AD采样最小值失败: {minResult.Error}"); + return StatusCode(StatusCodes.Status500InternalServerError, "获取AD采样最小值失败"); + } + + if (!waveformResult.IsSuccessful) + { + logger.Error($"获取波形数据失败: {waveformResult.Error}"); + return StatusCode(StatusCodes.Status500InternalServerError, "获取波形数据失败"); + } + + var response = new OscilloscopeDataResponse + { + ADFrequency = freqResult.Value, + ADVpp = vppResult.Value, + ADMax = maxResult.Value, + ADMin = minResult.Value, + WaveformData = Convert.ToBase64String(waveformResult.Value) + }; + + return Ok(response); + } + catch (Exception ex) + { + logger.Error(ex, "获取示波器数据时发生异常"); + return StatusCode(StatusCodes.Status500InternalServerError, "操作失败,请稍后重试"); + } + } + + /// + /// 更新触发参数 + /// + /// 触发电平(0-255) + /// 触发边沿(true为上升沿,false为下降沿) + /// 操作结果 + [HttpPost("UpdateTrigger")] + [EnableCors("Users")] + [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + public async Task UpdateTrigger(byte level, bool risingEdge) + { + try + { + var oscilloscope = GetOscilloscope(); + if (oscilloscope == null) + return BadRequest("用户未绑定有效的实验板"); + + // 设置触发电平 + var levelResult = await oscilloscope.SetTriggerLevel(level); + if (!levelResult.IsSuccessful) + { + logger.Error($"设置触发电平失败: {levelResult.Error}"); + return StatusCode(StatusCodes.Status500InternalServerError, "设置触发电平失败"); + } + + // 设置触发边沿 + var edgeResult = await oscilloscope.SetTriggerEdge(risingEdge); + if (!edgeResult.IsSuccessful) + { + logger.Error($"设置触发边沿失败: {edgeResult.Error}"); + return StatusCode(StatusCodes.Status500InternalServerError, "设置触发边沿失败"); + } + + return Ok(true); + } + catch (Exception ex) + { + logger.Error(ex, "更新触发参数时发生异常"); + return StatusCode(StatusCodes.Status500InternalServerError, "操作失败,请稍后重试"); + } + } + + /// + /// 更新采样参数 + /// + /// 水平偏移量(0-1023) + /// 抽样率(0-1023) + /// 操作结果 + [HttpPost("UpdateSampling")] + [EnableCors("Users")] + [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + public async Task UpdateSampling(ushort horizontalShift, ushort decimationRate) + { + try + { + if (horizontalShift > 1023) + return BadRequest("水平偏移量必须在0-1023之间"); + + if (decimationRate > 1023) + return BadRequest("抽样率必须在0-1023之间"); + + var oscilloscope = GetOscilloscope(); + if (oscilloscope == null) + return BadRequest("用户未绑定有效的实验板"); + + // 设置水平偏移量 + var shiftResult = await oscilloscope.SetHorizontalShift(horizontalShift); + if (!shiftResult.IsSuccessful) + { + logger.Error($"设置水平偏移量失败: {shiftResult.Error}"); + return StatusCode(StatusCodes.Status500InternalServerError, "设置水平偏移量失败"); + } + + // 设置抽样率 + var rateResult = await oscilloscope.SetDecimationRate(decimationRate); + if (!rateResult.IsSuccessful) + { + logger.Error($"设置抽样率失败: {rateResult.Error}"); + return StatusCode(StatusCodes.Status500InternalServerError, "设置抽样率失败"); + } + + return Ok(true); + } + catch (Exception ex) + { + logger.Error(ex, "更新采样参数时发生异常"); + return StatusCode(StatusCodes.Status500InternalServerError, "操作失败,请稍后重试"); + } + } + + /// + /// 手动刷新RAM + /// + /// 操作结果 + [HttpPost("RefreshRAM")] + [EnableCors("Users")] + [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + public async Task RefreshRAM() + { + try + { + var oscilloscope = GetOscilloscope(); + if (oscilloscope == null) + return BadRequest("用户未绑定有效的实验板"); + + var result = await oscilloscope.RefreshRAM(); + if (!result.IsSuccessful) + { + logger.Error($"刷新RAM失败: {result.Error}"); + return StatusCode(StatusCodes.Status500InternalServerError, "刷新RAM失败"); + } + + return Ok(result.Value); + } + catch (Exception ex) + { + logger.Error(ex, "刷新RAM时发生异常"); + return StatusCode(StatusCodes.Status500InternalServerError, "操作失败,请稍后重试"); + } + } +} \ No newline at end of file diff --git a/server/src/Peripherals/OscilloscopeClient.cs b/server/src/Peripherals/OscilloscopeClient.cs index 3eeff22..ad04055 100644 --- a/server/src/Peripherals/OscilloscopeClient.cs +++ b/server/src/Peripherals/OscilloscopeClient.cs @@ -1,11 +1,68 @@ using System.Net; +using Common; using DotNext; namespace Peripherals.OscilloscopeClient; static class OscilloscopeAddr { - public const UInt32 Base = 0x0000_0000; + const UInt32 BASE = 0x8000_0000; + + /// + /// 0x0000_0000:R/W[0] wave_run 启动捕获/关闭 + /// + public const UInt32 START_CAPTURE = BASE + 0x0000_0000; + + /// + /// 0x0000_0001: R/W[7:0] trig_level 触发电平 + /// + public const UInt32 TRIG_LEVEL = BASE + 0x0000_0001; + + /// + /// 0x0000_0002:R/W[0] trig_edge 触发边沿,0-下降沿,1-上升沿 + /// + public const UInt32 TRIG_EDGE = BASE + 0x0000_0002; + + /// + /// 0x0000_0003: R/W[9:0] h shift 水平偏移量 + /// + public const UInt32 H_SHIFT = BASE + 0x0000_0003; + + /// + /// 0x0000_0004: R/W[9:0] deci rate 抽样率,0—1023 + /// + public const UInt32 DECI_RATE = BASE + 0x0000_0004; + + /// + /// 0x0000_0005:R/W[0] ram refresh RAM刷新 + /// + public const UInt32 RAM_FRESH = BASE + 0x0000_0005; + + /// + /// 0x0000 0006:R[19: 0] ad_freq AD采样频率 + /// + public const UInt32 AD_FREQ = BASE + 0x0000_0006; + + /// + /// Ox0000_0007: R[7:0] ad_vpp AD采样幅度 + /// + public const UInt32 AD_VPP = BASE + 0x0000_0007; + + /// + /// 0x0000_0008: R[7:0] ad max AD采样最大值 + /// + public const UInt32 AD_MAX = BASE + 0x0000_0008; + + /// + /// 0x0000_0009: R[7:0] ad_min AD采样最小值 + /// + public const UInt32 AD_MIN = BASE + 0x0000_0009; + + /// + /// 0x0000_1000-0x0000_13FF:R[7:0] wave_rd_data 共1024个字节 + /// + public const UInt32 RD_DATA_ADDR = BASE + 0x0000_1000; + public const UInt32 RD_DATA_LENGTH = 0x0000_0400; } class Oscilloscope @@ -13,6 +70,7 @@ class Oscilloscope private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); readonly int timeout = 2000; + readonly int taskID = 1; readonly int port; readonly string address; @@ -33,4 +91,258 @@ class Oscilloscope this.ep = new IPEndPoint(IPAddress.Parse(address), port); this.timeout = timeout; } + + /// + /// 控制示波器的捕获开关 + /// + /// 是否启动捕获 + /// 操作结果,成功返回true,否则返回异常信息 + public async ValueTask> SetCaptureEnable(bool enable) + { + UInt32 value = enable ? 1u : 0u; + var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, OscilloscopeAddr.START_CAPTURE, value, this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to set capture enable: {ret.Error}"); + return new(ret.Error); + } + if (!ret.Value) + { + logger.Error("WriteAddr to START_CAPTURE returned false"); + return new(new Exception("Failed to set capture enable")); + } + return true; + } + + /// + /// 设置触发电平 + /// + /// 触发电平值(0-255) + /// 操作结果,成功返回true,否则返回异常信息 + public async ValueTask> SetTriggerLevel(byte level) + { + var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, OscilloscopeAddr.TRIG_LEVEL, level, this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to set trigger level: {ret.Error}"); + return new(ret.Error); + } + if (!ret.Value) + { + logger.Error("WriteAddr to TRIG_LEVEL returned false"); + return new(new Exception("Failed to set trigger level")); + } + return true; + } + + /// + /// 设置触发边沿 + /// + /// true为上升沿,false为下降沿 + /// 操作结果,成功返回true,否则返回异常信息 + public async ValueTask> SetTriggerEdge(bool risingEdge) + { + UInt32 value = risingEdge ? 1u : 0u; + var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, OscilloscopeAddr.TRIG_EDGE, value, this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to set trigger edge: {ret.Error}"); + return new(ret.Error); + } + if (!ret.Value) + { + logger.Error("WriteAddr to TRIG_EDGE returned false"); + return new(new Exception("Failed to set trigger edge")); + } + return true; + } + + /// + /// 设置水平偏移量 + /// + /// 水平偏移量值(0-1023) + /// 操作结果,成功返回true,否则返回异常信息 + public async ValueTask> SetHorizontalShift(UInt16 shift) + { + if (shift > 1023) + return new(new ArgumentException("Horizontal shift must be 0-1023", nameof(shift))); + + var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, OscilloscopeAddr.H_SHIFT, shift, this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to set horizontal shift: {ret.Error}"); + return new(ret.Error); + } + if (!ret.Value) + { + logger.Error("WriteAddr to H_SHIFT returned false"); + return new(new Exception("Failed to set horizontal shift")); + } + return true; + } + + /// + /// 设置抽样率 + /// + /// 抽样率值(0-1023) + /// 操作结果,成功返回true,否则返回异常信息 + public async ValueTask> SetDecimationRate(UInt16 rate) + { + if (rate > 1023) + return new(new ArgumentException("Decimation rate must be 0-1023", nameof(rate))); + + var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, OscilloscopeAddr.DECI_RATE, rate, this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to set decimation rate: {ret.Error}"); + return new(ret.Error); + } + if (!ret.Value) + { + logger.Error("WriteAddr to DECI_RATE returned false"); + return new(new Exception("Failed to set decimation rate")); + } + return true; + } + + /// + /// 刷新RAM + /// + /// 操作结果,成功返回true,否则返回异常信息 + public async ValueTask> RefreshRAM() + { + var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, OscilloscopeAddr.RAM_FRESH, 1u, this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to refresh RAM: {ret.Error}"); + return new(ret.Error); + } + if (!ret.Value) + { + logger.Error("WriteAddr to RAM_FRESH returned false"); + return new(new Exception("Failed to refresh RAM")); + } + return true; + } + + /// + /// 获取AD采样频率 + /// + /// 操作结果,成功返回采样频率值,否则返回异常信息 + public async ValueTask> GetADFrequency() + { + var ret = await UDPClientPool.ReadAddr(this.ep, this.taskID, OscilloscopeAddr.AD_FREQ, this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to read AD frequency: {ret.Error}"); + return new(ret.Error); + } + if (ret.Value.Options.Data == null || ret.Value.Options.Data.Length < 4) + { + logger.Error("ReadAddr returned invalid data for AD frequency"); + return new(new Exception("Failed to read AD frequency")); + } + UInt32 freq = Number.BytesToUInt32(ret.Value.Options.Data).Value; + // 取低20位 [19:0] + freq &= 0xFFFFF; + return freq; + } + + /// + /// 获取AD采样幅度 + /// + /// 操作结果,成功返回采样幅度值,否则返回异常信息 + public async ValueTask> GetADVpp() + { + var ret = await UDPClientPool.ReadAddr(this.ep, this.taskID, OscilloscopeAddr.AD_VPP, this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to read AD VPP: {ret.Error}"); + return new(ret.Error); + } + if (ret.Value.Options.Data == null || ret.Value.Options.Data.Length < 1) + { + logger.Error("ReadAddr returned invalid data for AD VPP"); + return new(new Exception("Failed to read AD VPP")); + } + return ret.Value.Options.Data[3]; + } + + /// + /// 获取AD采样最大值 + /// + /// 操作结果,成功返回采样最大值,否则返回异常信息 + public async ValueTask> GetADMax() + { + var ret = await UDPClientPool.ReadAddr(this.ep, this.taskID, OscilloscopeAddr.AD_MAX, this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to read AD max: {ret.Error}"); + return new(ret.Error); + } + if (ret.Value.Options.Data == null || ret.Value.Options.Data.Length < 1) + { + logger.Error("ReadAddr returned invalid data for AD max"); + return new(new Exception("Failed to read AD max")); + } + return ret.Value.Options.Data[3]; + } + + /// + /// 获取AD采样最小值 + /// + /// 操作结果,成功返回采样最小值,否则返回异常信息 + public async ValueTask> GetADMin() + { + var ret = await UDPClientPool.ReadAddr(this.ep, this.taskID, OscilloscopeAddr.AD_MIN, this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to read AD min: {ret.Error}"); + return new(ret.Error); + } + if (ret.Value.Options.Data == null || ret.Value.Options.Data.Length < 1) + { + logger.Error("ReadAddr returned invalid data for AD min"); + return new(new Exception("Failed to read AD min")); + } + return ret.Value.Options.Data[3]; + } + + /// + /// 获取波形采样数据 + /// + /// 操作结果,成功返回采样数据数组,否则返回异常信息 + public async ValueTask> GetWaveformData() + { + var ret = await UDPClientPool.ReadAddr4BytesAsync( + this.ep, + this.taskID, + OscilloscopeAddr.RD_DATA_ADDR, + (int)OscilloscopeAddr.RD_DATA_LENGTH / 32, + this.timeout + ); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to read waveform data: {ret.Error}"); + return new(ret.Error); + } + var data = ret.Value; + if (data == null || data.Length != OscilloscopeAddr.RD_DATA_LENGTH) + { + logger.Error($"Waveform data length mismatch: {data?.Length}"); + return new(new Exception("Waveform data length mismatch")); + } + + // 处理波形数据:从每4个字节中提取第4个字节(索引3)作为有效数据 + // 数据格式:低八位有效,即[4*i + 3]才是有效数据 + int sampleCount = data.Length / 4; + byte[] waveformData = new byte[sampleCount]; + + for (int i = 0; i < sampleCount; i++) + { + waveformData[i] = data[4 * i + 3]; + } + + return waveformData; + } } diff --git a/src/APIClient.ts b/src/APIClient.ts index 4f29235..9983da4 100644 --- a/src/APIClient.ts +++ b/src/APIClient.ts @@ -3170,6 +3170,61 @@ export class NetConfigClient { return Promise.resolve(null as any); } + /** + * 设置板卡MAC地址 + * @param boardMac (optional) 板卡MAC地址(格式:AA:BB:CC:DD:EE:FF) + * @return 操作结果 + */ + setBoardMAC(boardMac: string | undefined): Promise { + let url_ = this.baseUrl + "/api/NetConfig/SetBoardMAC?"; + if (boardMac === null) + throw new Error("The parameter 'boardMac' cannot be null."); + else if (boardMac !== undefined) + url_ += "boardMac=" + encodeURIComponent("" + boardMac) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "POST", + headers: { + "Accept": "application/json" + } + }; + + return this.http.fetch(url_, options_).then((_response: Response) => { + return this.processSetBoardMAC(_response); + }); + } + + protected processSetBoardMAC(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result200 = resultData200 !== undefined ? resultData200 : null; + + return result200; + }); + } else if (status === 400) { + return response.text().then((_responseText) => { + let result400: any = null; + let resultData400 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result400 = ProblemDetails.fromJS(resultData400); + return throwException("A server side error occurred.", status, _responseText, _headers, result400); + }); + } else if (status === 500) { + return response.text().then((_responseText) => { + return throwException("A server side error occurred.", status, _responseText, _headers); + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + /** * 设置主机MAC地址 * @param hostMac (optional) 主机MAC地址(格式:AA:BB:CC:DD:EE:FF) @@ -3319,31 +3374,81 @@ export class NetConfigClient { } /** - * 设置板卡MAC地址 - * @param boardMac (optional) 板卡MAC地址(格式:AA:BB:CC:DD:EE:FF) - * @return 操作结果 + * 获取本机网络信息 + * @return 本机网络信息 */ - setBoardMAC(boardMac: string | undefined): Promise { - let url_ = this.baseUrl + "/api/NetConfig/SetBoardMAC?"; - if (boardMac === null) - throw new Error("The parameter 'boardMac' cannot be null."); - else if (boardMac !== undefined) - url_ += "boardMac=" + encodeURIComponent("" + boardMac) + "&"; + getLocalNetworkInfo(): Promise { + let url_ = this.baseUrl + "/api/NetConfig/GetLocalNetworkInfo"; url_ = url_.replace(/[?&]$/, ""); let options_: RequestInit = { - method: "POST", + method: "GET", headers: { "Accept": "application/json" } }; return this.http.fetch(url_, options_).then((_response: Response) => { - return this.processSetBoardMAC(_response); + return this.processGetLocalNetworkInfo(_response); }); } - protected processSetBoardMAC(response: Response): Promise { + protected processGetLocalNetworkInfo(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result200 = resultData200 !== undefined ? resultData200 : null; + + return result200; + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } +} + +export class OscilloscopeApiClient { + private http: { fetch(url: RequestInfo, init?: RequestInit): Promise }; + private baseUrl: string; + protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined; + + constructor(baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise }) { + this.http = http ? http : window as any; + this.baseUrl = baseUrl ?? "http://localhost:5000"; + } + + /** + * 初始化示波器 + * @param config 示波器配置 + * @return 操作结果 + */ + initialize(config: OscilloscopeFullConfig): Promise { + let url_ = this.baseUrl + "/api/OscilloscopeApi/Initialize"; + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(config); + + let options_: RequestInit = { + body: content_, + method: "POST", + headers: { + "Content-Type": "application/json", + "Accept": "application/json" + } + }; + + return this.http.fetch(url_, options_).then((_response: Response) => { + return this.processInitialize(_response); + }); + } + + protected processInitialize(response: Response): Promise { const status = response.status; let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; if (status === 200) { @@ -3365,6 +3470,374 @@ export class NetConfigClient { return response.text().then((_responseText) => { return throwException("A server side error occurred.", status, _responseText, _headers); }); + } else if (status === 401) { + return response.text().then((_responseText) => { + let result401: any = null; + let resultData401 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result401 = ProblemDetails.fromJS(resultData401); + return throwException("A server side error occurred.", status, _responseText, _headers, result401); + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + /** + * 启动捕获 + * @return 操作结果 + */ + startCapture(): Promise { + let url_ = this.baseUrl + "/api/OscilloscopeApi/StartCapture"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "POST", + headers: { + "Accept": "application/json" + } + }; + + return this.http.fetch(url_, options_).then((_response: Response) => { + return this.processStartCapture(_response); + }); + } + + protected processStartCapture(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result200 = resultData200 !== undefined ? resultData200 : null; + + return result200; + }); + } else if (status === 400) { + return response.text().then((_responseText) => { + let result400: any = null; + let resultData400 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result400 = ProblemDetails.fromJS(resultData400); + return throwException("A server side error occurred.", status, _responseText, _headers, result400); + }); + } else if (status === 500) { + return response.text().then((_responseText) => { + return throwException("A server side error occurred.", status, _responseText, _headers); + }); + } else if (status === 401) { + return response.text().then((_responseText) => { + let result401: any = null; + let resultData401 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result401 = ProblemDetails.fromJS(resultData401); + return throwException("A server side error occurred.", status, _responseText, _headers, result401); + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + /** + * 停止捕获 + * @return 操作结果 + */ + stopCapture(): Promise { + let url_ = this.baseUrl + "/api/OscilloscopeApi/StopCapture"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "POST", + headers: { + "Accept": "application/json" + } + }; + + return this.http.fetch(url_, options_).then((_response: Response) => { + return this.processStopCapture(_response); + }); + } + + protected processStopCapture(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result200 = resultData200 !== undefined ? resultData200 : null; + + return result200; + }); + } else if (status === 400) { + return response.text().then((_responseText) => { + let result400: any = null; + let resultData400 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result400 = ProblemDetails.fromJS(resultData400); + return throwException("A server side error occurred.", status, _responseText, _headers, result400); + }); + } else if (status === 500) { + return response.text().then((_responseText) => { + return throwException("A server side error occurred.", status, _responseText, _headers); + }); + } else if (status === 401) { + return response.text().then((_responseText) => { + let result401: any = null; + let resultData401 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result401 = ProblemDetails.fromJS(resultData401); + return throwException("A server side error occurred.", status, _responseText, _headers, result401); + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + /** + * 获取示波器数据和状态 + * @return 示波器数据和状态信息 + */ + getData(): Promise { + let url_ = this.baseUrl + "/api/OscilloscopeApi/GetData"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.http.fetch(url_, options_).then((_response: Response) => { + return this.processGetData(_response); + }); + } + + protected processGetData(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result200 = OscilloscopeDataResponse.fromJS(resultData200); + return result200; + }); + } else if (status === 400) { + return response.text().then((_responseText) => { + let result400: any = null; + let resultData400 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result400 = ProblemDetails.fromJS(resultData400); + return throwException("A server side error occurred.", status, _responseText, _headers, result400); + }); + } else if (status === 500) { + return response.text().then((_responseText) => { + return throwException("A server side error occurred.", status, _responseText, _headers); + }); + } else if (status === 401) { + return response.text().then((_responseText) => { + let result401: any = null; + let resultData401 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result401 = ProblemDetails.fromJS(resultData401); + return throwException("A server side error occurred.", status, _responseText, _headers, result401); + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + /** + * 更新触发参数 + * @param level (optional) 触发电平(0-255) + * @param risingEdge (optional) 触发边沿(true为上升沿,false为下降沿) + * @return 操作结果 + */ + updateTrigger(level: number | undefined, risingEdge: boolean | undefined): Promise { + let url_ = this.baseUrl + "/api/OscilloscopeApi/UpdateTrigger?"; + if (level === null) + throw new Error("The parameter 'level' cannot be null."); + else if (level !== undefined) + url_ += "level=" + encodeURIComponent("" + level) + "&"; + if (risingEdge === null) + throw new Error("The parameter 'risingEdge' cannot be null."); + else if (risingEdge !== undefined) + url_ += "risingEdge=" + encodeURIComponent("" + risingEdge) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "POST", + headers: { + "Accept": "application/json" + } + }; + + return this.http.fetch(url_, options_).then((_response: Response) => { + return this.processUpdateTrigger(_response); + }); + } + + protected processUpdateTrigger(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result200 = resultData200 !== undefined ? resultData200 : null; + + return result200; + }); + } else if (status === 400) { + return response.text().then((_responseText) => { + let result400: any = null; + let resultData400 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result400 = ProblemDetails.fromJS(resultData400); + return throwException("A server side error occurred.", status, _responseText, _headers, result400); + }); + } else if (status === 500) { + return response.text().then((_responseText) => { + return throwException("A server side error occurred.", status, _responseText, _headers); + }); + } else if (status === 401) { + return response.text().then((_responseText) => { + let result401: any = null; + let resultData401 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result401 = ProblemDetails.fromJS(resultData401); + return throwException("A server side error occurred.", status, _responseText, _headers, result401); + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + /** + * 更新采样参数 + * @param horizontalShift (optional) 水平偏移量(0-1023) + * @param decimationRate (optional) 抽样率(0-1023) + * @return 操作结果 + */ + updateSampling(horizontalShift: number | undefined, decimationRate: number | undefined): Promise { + let url_ = this.baseUrl + "/api/OscilloscopeApi/UpdateSampling?"; + if (horizontalShift === null) + throw new Error("The parameter 'horizontalShift' cannot be null."); + else if (horizontalShift !== undefined) + url_ += "horizontalShift=" + encodeURIComponent("" + horizontalShift) + "&"; + if (decimationRate === null) + throw new Error("The parameter 'decimationRate' cannot be null."); + else if (decimationRate !== undefined) + url_ += "decimationRate=" + encodeURIComponent("" + decimationRate) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "POST", + headers: { + "Accept": "application/json" + } + }; + + return this.http.fetch(url_, options_).then((_response: Response) => { + return this.processUpdateSampling(_response); + }); + } + + protected processUpdateSampling(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result200 = resultData200 !== undefined ? resultData200 : null; + + return result200; + }); + } else if (status === 400) { + return response.text().then((_responseText) => { + let result400: any = null; + let resultData400 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result400 = ProblemDetails.fromJS(resultData400); + return throwException("A server side error occurred.", status, _responseText, _headers, result400); + }); + } else if (status === 500) { + return response.text().then((_responseText) => { + return throwException("A server side error occurred.", status, _responseText, _headers); + }); + } else if (status === 401) { + return response.text().then((_responseText) => { + let result401: any = null; + let resultData401 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result401 = ProblemDetails.fromJS(resultData401); + return throwException("A server side error occurred.", status, _responseText, _headers, result401); + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + /** + * 手动刷新RAM + * @return 操作结果 + */ + refreshRAM(): Promise { + let url_ = this.baseUrl + "/api/OscilloscopeApi/RefreshRAM"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "POST", + headers: { + "Accept": "application/json" + } + }; + + return this.http.fetch(url_, options_).then((_response: Response) => { + return this.processRefreshRAM(_response); + }); + } + + protected processRefreshRAM(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result200 = resultData200 !== undefined ? resultData200 : null; + + return result200; + }); + } else if (status === 400) { + return response.text().then((_responseText) => { + let result400: any = null; + let resultData400 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result400 = ProblemDetails.fromJS(resultData400); + return throwException("A server side error occurred.", status, _responseText, _headers, result400); + }); + } else if (status === 500) { + return response.text().then((_responseText) => { + return throwException("A server side error occurred.", status, _responseText, _headers); + }); + } else if (status === 401) { + return response.text().then((_responseText) => { + let result401: any = null; + let resultData401 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result401 = ProblemDetails.fromJS(resultData401); + return throwException("A server side error occurred.", status, _responseText, _headers, result401); + }); } else if (status !== 200 && status !== 204) { return response.text().then((_responseText) => { return throwException("An unexpected server error occurred.", status, _responseText, _headers); @@ -4885,6 +5358,140 @@ export interface INetworkInterfaceDto { macAddress: string; } +/** 示波器完整配置 */ +export class OscilloscopeFullConfig implements IOscilloscopeFullConfig { + /** 是否启动捕获 */ + captureEnabled!: boolean; + /** 触发电平(0-255) */ + triggerLevel!: number; + /** 触发边沿(true为上升沿,false为下降沿) */ + triggerRisingEdge!: boolean; + /** 水平偏移量(0-1023) */ + horizontalShift!: number; + /** 抽样率(0-1023) */ + decimationRate!: number; + /** 是否自动刷新RAM */ + autoRefreshRAM!: boolean; + + constructor(data?: IOscilloscopeFullConfig) { + if (data) { + for (var property in data) { + if (data.hasOwnProperty(property)) + (this)[property] = (data)[property]; + } + } + } + + init(_data?: any) { + if (_data) { + this.captureEnabled = _data["captureEnabled"]; + this.triggerLevel = _data["triggerLevel"]; + this.triggerRisingEdge = _data["triggerRisingEdge"]; + this.horizontalShift = _data["horizontalShift"]; + this.decimationRate = _data["decimationRate"]; + this.autoRefreshRAM = _data["autoRefreshRAM"]; + } + } + + static fromJS(data: any): OscilloscopeFullConfig { + data = typeof data === 'object' ? data : {}; + let result = new OscilloscopeFullConfig(); + result.init(data); + return result; + } + + toJSON(data?: any) { + data = typeof data === 'object' ? data : {}; + data["captureEnabled"] = this.captureEnabled; + data["triggerLevel"] = this.triggerLevel; + data["triggerRisingEdge"] = this.triggerRisingEdge; + data["horizontalShift"] = this.horizontalShift; + data["decimationRate"] = this.decimationRate; + data["autoRefreshRAM"] = this.autoRefreshRAM; + return data; + } +} + +/** 示波器完整配置 */ +export interface IOscilloscopeFullConfig { + /** 是否启动捕获 */ + 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) { + if (data) { + for (var property in data) { + if (data.hasOwnProperty(property)) + (this)[property] = (data)[property]; + } + } + } + + init(_data?: any) { + if (_data) { + this.adFrequency = _data["adFrequency"]; + this.adVpp = _data["adVpp"]; + this.adMax = _data["adMax"]; + this.adMin = _data["adMin"]; + this.waveformData = _data["waveformData"]; + } + } + + static fromJS(data: any): OscilloscopeDataResponse { + data = typeof data === 'object' ? data : {}; + let result = new OscilloscopeDataResponse(); + result.init(data); + return result; + } + + toJSON(data?: any) { + data = typeof data === 'object' ? data : {}; + data["adFrequency"] = this.adFrequency; + data["adVpp"] = this.adVpp; + data["adMax"] = this.adMax; + data["adMin"] = this.adMin; + data["waveformData"] = this.waveformData; + return data; + } +} + +/** 示波器状态和数据 */ +export interface IOscilloscopeDataResponse { + /** AD采样频率 */ + adFrequency: number; + /** AD采样幅度 */ + adVpp: number; + /** AD采样最大值 */ + adMax: number; + /** AD采样最小值 */ + adMin: number; + /** 波形数据(Base64编码) */ + waveformData: string; +} + /** Package options which to send address to read or write */ export class SendAddrPackOptions implements ISendAddrPackOptions { /** 突发类型 */ diff --git a/src/components/Oscilloscope/OscilloscopeManager.ts b/src/components/Oscilloscope/OscilloscopeManager.ts new file mode 100644 index 0000000..aa32fd4 --- /dev/null +++ b/src/components/Oscilloscope/OscilloscopeManager.ts @@ -0,0 +1,264 @@ +import { createInjectionState } from "@vueuse/core"; +import { shallowRef, reactive, ref, computed } from "vue"; +import { Mutex } from "async-mutex"; +import { + OscilloscopeFullConfig, + OscilloscopeDataResponse, +} from "@/APIClient"; +import { AuthManager } from "@/utils/AuthManager"; +import { useAlertStore } from "@/components/Alert"; +import { useRequiredInjection } from "@/utils/Common"; + +export type OscilloscopeDataType = { + x: number[]; + y: number[] | number[][]; + xUnit: "s" | "ms" | "us" | "ns"; + yUnit: "V" | "mV" | "uV"; + adFrequency: number; + adVpp: number; + adMax: number; + adMin: number; +}; + +// 默认配置 +const DEFAULT_CONFIG: OscilloscopeFullConfig = new OscilloscopeFullConfig({ + captureEnabled: false, + triggerLevel: 128, + triggerRisingEdge: true, + horizontalShift: 0, + decimationRate: 0, + autoRefreshRAM: false, +}); + +// 采样频率常量(后端返回) +const [useProvideOscilloscope, useOscilloscopeState] = createInjectionState(() => { + const oscData = shallowRef(); + const alert = useRequiredInjection(useAlertStore); + + // 互斥锁 + const operationMutex = new Mutex(); + + // 状态 + const isApplying = ref(false); + const isCapturing = ref(false); + + // 配置 + const config = reactive(new OscilloscopeFullConfig({ ...DEFAULT_CONFIG })); + + // 采样点数(由后端数据决定) + const sampleCount = ref(0); + + // 采样周期(ns),由adFrequency计算 + const samplePeriodNs = computed(() => + oscData.value?.adFrequency ? 1_000_000_000 / oscData.value.adFrequency : 200 + ); + + // 应用配置 + const applyConfiguration = async () => { + if (operationMutex.isLocked()) { + alert.warn("有其他操作正在进行中,请稍后再试", 3000); + return; + } + const release = await operationMutex.acquire(); + isApplying.value = true; + try { + const client = AuthManager.createAuthenticatedOscilloscopeApiClient(); + const success = await client.initialize({ ...config }); + if (success) { + alert.success("示波器配置已应用", 2000); + } else { + throw new Error("应用失败"); + } + } catch (error) { + alert.error("应用配置失败", 3000); + } finally { + isApplying.value = false; + release(); + } + }; + + // 重置配置 + const resetConfiguration = () => { + Object.assign(config, { ...DEFAULT_CONFIG }); + alert.info("配置已重置", 2000); + }; + + // 获取数据 + const getOscilloscopeData = async () => { + try { + const client = AuthManager.createAuthenticatedOscilloscopeApiClient(); + 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, + }; + } catch (error) { + alert.error("获取示波器数据失败", 3000); + } + }; + + // 启动捕获 + const startCapture = async () => { + if (operationMutex.isLocked()) { + alert.warn("有其他操作正在进行中,请稍后再试", 3000); + return; + } + isCapturing.value = true; + const release = await operationMutex.acquire(); + try { + const client = AuthManager.createAuthenticatedOscilloscopeApiClient(); + const started = await client.startCapture(); + if (!started) throw new Error("无法启动捕获"); + alert.info("开始捕获...", 2000); + + // 简单轮询,直到捕获完成(可根据后端实际情况优化) + await new Promise((resolve) => setTimeout(resolve, 1000)); + await getOscilloscopeData(); + alert.success("捕获完成", 2000); + } catch (error) { + alert.error("捕获失败", 3000); + } finally { + isCapturing.value = false; + release(); + } + }; + + // 停止捕获 + const stopCapture = async () => { + if (!isCapturing.value) { + alert.warn("当前没有正在进行的捕获操作", 2000); + return; + } + isCapturing.value = false; + const release = await operationMutex.acquire(); + try { + const client = AuthManager.createAuthenticatedOscilloscopeApiClient(); + const stopped = await client.stopCapture(); + if (!stopped) throw new Error("无法停止捕获"); + alert.info("捕获已停止", 2000); + } catch (error) { + alert.error("停止捕获失败", 3000); + } finally { + release(); + } + }; + + // 更新触发参数 + const updateTrigger = async (level: number, risingEdge: boolean) => { + const client = AuthManager.createAuthenticatedOscilloscopeApiClient(); + try { + const ok = await client.updateTrigger(level, risingEdge); + if (ok) { + config.triggerLevel = level; + config.triggerRisingEdge = risingEdge; + alert.success("触发参数已更新", 2000); + } else { + throw new Error(); + } + } catch { + alert.error("更新触发参数失败", 2000); + } + }; + + // 更新采样参数 + const updateSampling = async (horizontalShift: number, decimationRate: number) => { + const client = AuthManager.createAuthenticatedOscilloscopeApiClient(); + try { + const ok = await client.updateSampling(horizontalShift, decimationRate); + if (ok) { + config.horizontalShift = horizontalShift; + config.decimationRate = decimationRate; + alert.success("采样参数已更新", 2000); + } else { + throw new Error(); + } + } catch { + alert.error("更新采样参数失败", 2000); + } + }; + + // 手动刷新RAM + const refreshRAM = async () => { + const client = AuthManager.createAuthenticatedOscilloscopeApiClient(); + try { + const ok = await client.refreshRAM(); + if (ok) { + alert.success("RAM已刷新", 2000); + } else { + throw new Error(); + } + } catch { + alert.error("刷新RAM失败", 2000); + } + }; + + // 生成测试数据 + const generateTestData = () => { + const freq = 5_000_000; + const duration = 0.001; // 1ms + const points = Math.floor(freq * duration); + const x = Array.from({ length: points }, (_, i) => (i * 1_000_000_000 / freq) / 1000); + const y = Array.from({ length: points }, (_, i) => + Math.floor(Math.sin(i * 0.01) * 127 + 128) + ); + oscData.value = { + x, + y, + xUnit: "us", + yUnit: "V", + adFrequency: freq, + adVpp: 2.0, + adMax: 255, + adMin: 0, + }; + alert.success("测试数据生成成功", 2000); + }; + + const isOperationInProgress = computed( + () => isApplying.value || isCapturing.value || operationMutex.isLocked() + ); + + return { + oscData, + config, + isApplying, + isCapturing, + isOperationInProgress, + sampleCount, + samplePeriodNs, + + applyConfiguration, + resetConfiguration, + getOscilloscopeData, + startCapture, + stopCapture, + updateTrigger, + updateSampling, + refreshRAM, + generateTestData, + }; +}); + +export { useProvideOscilloscope, useOscilloscopeState, DEFAULT_CONFIG }; \ No newline at end of file diff --git a/src/components/Oscilloscope/WaveformDisplay.vue b/src/components/Oscilloscope/OscilloscopeWaveformDisplay.vue similarity index 65% rename from src/components/Oscilloscope/WaveformDisplay.vue rename to src/components/Oscilloscope/OscilloscopeWaveformDisplay.vue index 868df93..0960d64 100644 --- a/src/components/Oscilloscope/WaveformDisplay.vue +++ b/src/components/Oscilloscope/OscilloscopeWaveformDisplay.vue @@ -1,6 +1,6 @@