Merge branch 'master' of ssh://git.swordlost.top:222/SikongJueluo/FPGA_WebLab into dpp
This commit is contained in:
commit
4af7da6344
|
@ -13,6 +13,7 @@ declare module 'vue' {
|
|||
BaseBoard: typeof import('./src/components/equipments/BaseBoard.vue')['default']
|
||||
BaseInputField: typeof import('./src/components/InputField/BaseInputField.vue')['default']
|
||||
Canvas: typeof import('./src/components/Canvas.vue')['default']
|
||||
ChannelConfig: typeof import('./src/components/LogicAnalyzer/ChannelConfig.vue')['default']
|
||||
CollapsibleSection: typeof import('./src/components/CollapsibleSection.vue')['default']
|
||||
ComponentSelector: typeof import('./src/components/LabCanvas/ComponentSelector.vue')['default']
|
||||
DDR: typeof import('./src/components/equipments/DDR.vue')['default']
|
||||
|
@ -29,6 +30,7 @@ declare module 'vue' {
|
|||
LabCanvasNew: typeof import('./src/components/LabCanvas/LabCanvasNew.vue')['default']
|
||||
LabComponentsDrawer: typeof import('./src/components/LabCanvasNew/LabComponentsDrawer.vue')['default']
|
||||
LabComponentsDrawerNew: typeof import('./src/components/LabCanvas/LabComponentsDrawerNew.vue')['default']
|
||||
LogicalWaveFormDisplay: typeof import('./src/components/LogicAnalyzer/LogicalWaveFormDisplay.vue')['default']
|
||||
LoginCard: typeof import('./src/components/LoginCard.vue')['default']
|
||||
MarkdownRenderer: typeof import('./src/components/MarkdownRenderer.vue')['default']
|
||||
MechanicalButton: typeof import('./src/components/equipments/MechanicalButton.vue')['default']
|
||||
|
@ -66,6 +68,7 @@ declare module 'vue' {
|
|||
TabsTrigger: typeof import('reka-ui')['TabsTrigger']
|
||||
ThemeControlButton: typeof import('./src/components/ThemeControlButton.vue')['default']
|
||||
ThemeControlToggle: typeof import('./src/components/ThemeControlToggle.vue')['default']
|
||||
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']
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
/// </summary>
|
||||
public static class MsgBus
|
||||
{
|
||||
private static readonly UDPServer udpServer = new UDPServer(1234);
|
||||
private static readonly UDPServer udpServer = new UDPServer(1234, 12);
|
||||
/// <summary>
|
||||
/// 获取UDP服务器
|
||||
/// </summary>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System.Net;
|
||||
using DotNext;
|
||||
using Peripherals.PowerClient;
|
||||
|
||||
namespace Peripherals.CameraClient;
|
||||
|
||||
|
@ -27,7 +28,7 @@ class Camera
|
|||
|
||||
const uint CAM_I2C_ADDR = 0x3C;
|
||||
const Peripherals.I2cClient.I2cProtocol CAM_PROTO = Peripherals.I2cClient.I2cProtocol.SCCB;
|
||||
const byte PLL_MUX = 60;
|
||||
const byte PLL_MUX = 105;
|
||||
const UInt32 FrameAddr = 0x00;
|
||||
|
||||
// 动态分辨率参数
|
||||
|
@ -54,6 +55,8 @@ class Camera
|
|||
|
||||
public async ValueTask<Result<bool>> Init()
|
||||
{
|
||||
await PowerHardwareCamera(true);
|
||||
await SleepHardwareCamera(false);
|
||||
// 步骤1: 复位
|
||||
var resetResult = await Reset();
|
||||
if (!resetResult.IsSuccessful) return resetResult;
|
||||
|
@ -139,14 +142,16 @@ class Camera
|
|||
var resolutionResult = await ConfigureResolution1280x720();
|
||||
if (!resolutionResult.IsSuccessful) return resolutionResult;
|
||||
|
||||
// 步骤22: 开始流
|
||||
// // 步骤22: 开始流
|
||||
var startResult = await StartStreaming();
|
||||
if (!startResult.IsSuccessful) return startResult;
|
||||
// var resetResult2 = await Reset();
|
||||
// if (!resetResult2.IsSuccessful) return resetResult2;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async ValueTask<Result<bool>> EnableCamera(bool isEnable)
|
||||
public async ValueTask<Result<bool>> EnableHardwareTrans(bool isEnable)
|
||||
{
|
||||
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, CameraAddr.CAPTURE_ON, Convert.ToUInt32(isEnable));
|
||||
if (!ret.IsSuccessful)
|
||||
|
@ -162,7 +167,7 @@ class Camera
|
|||
return true;
|
||||
}
|
||||
|
||||
public async ValueTask<Result<bool>> EnableCameraHardware(bool isEnable)
|
||||
public async ValueTask<Result<bool>> PowerHardwareCamera(bool isEnable)
|
||||
{
|
||||
{
|
||||
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, CameraAddr.CAMERA_POWER, (isEnable ? 0x00000001u : 0x00000000u));
|
||||
|
@ -180,7 +185,7 @@ class Camera
|
|||
return true;
|
||||
}
|
||||
|
||||
public async ValueTask<Result<bool>> SleepCameraHardware(bool isEnable)
|
||||
public async ValueTask<Result<bool>> SleepHardwareCamera(bool isEnable)
|
||||
{
|
||||
{
|
||||
var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, CameraAddr.CAMERA_POWER, (isEnable ? 0x00000101u : 0x00000001u));
|
||||
|
@ -224,7 +229,7 @@ class Camera
|
|||
// 读取失败时清除缓冲区,为下次读取做准备
|
||||
try
|
||||
{
|
||||
await MsgBus.UDPServer.ClearUDPData(this.address, this.taskID);
|
||||
MsgBus.UDPServer.ClearUDPData(this.address, this.taskID);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -237,6 +242,33 @@ class Camera
|
|||
return result.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取摄像头寄存器
|
||||
/// </summary>
|
||||
/// <param name="registerAddr">寄存器地址</param>
|
||||
/// <returns>读取到的寄存器值</returns>
|
||||
private async ValueTask<Result<byte>> ReadRegister(UInt16 registerAddr)
|
||||
{
|
||||
var i2c = new Peripherals.I2cClient.I2c(this.address, this.port, this.taskID, this.timeout);
|
||||
|
||||
// 地址高低字节
|
||||
var addrBytes = new byte[2];
|
||||
addrBytes[0] = (byte)(registerAddr >> 8);
|
||||
addrBytes[1] = (byte)(registerAddr & 0xFF);
|
||||
|
||||
// 先写寄存器地址
|
||||
var writeResult = await i2c.WriteData(CAM_I2C_ADDR, addrBytes, CAM_PROTO);
|
||||
if (!writeResult.IsSuccessful)
|
||||
return new(writeResult.Error);
|
||||
|
||||
// 再读一个字节
|
||||
var readResult = await i2c.ReadData(CAM_I2C_ADDR, 1, CAM_PROTO);
|
||||
if (!readResult.IsSuccessful)
|
||||
return new(readResult.Error);
|
||||
|
||||
return readResult.Value[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量配置I2C寄存器
|
||||
/// </summary>
|
||||
|
@ -296,7 +328,7 @@ class Camera
|
|||
}
|
||||
else
|
||||
{
|
||||
await Task.Delay(3); // 其他命令延时3ms
|
||||
await Task.Delay(5); // 其他命令延时3ms
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -391,11 +423,6 @@ class Camera
|
|||
// 2. 配置I2C寄存器
|
||||
var resolutionRegisters = new UInt16[][]
|
||||
{
|
||||
// H_OFFSET/V_OFFSET
|
||||
[0x3810, unchecked((byte)((hOffset >> 8) & 0xFF))],
|
||||
[0x3811, unchecked((byte)(hOffset & 0xFF))],
|
||||
[0x3812, unchecked((byte)((vOffset >> 8) & 0xFF))],
|
||||
|
||||
// H_START/V_START
|
||||
[0x3800, unchecked((byte)((hStart >> 8) & 0xFF))],
|
||||
[0x3801, unchecked((byte)(hStart & 0xFF))],
|
||||
|
@ -420,6 +447,10 @@ class Camera
|
|||
[0x380E, unchecked((byte)((vts >> 8) & 0xFF))],
|
||||
[0x380F, unchecked((byte)(vts & 0xFF))],
|
||||
|
||||
// H_OFFSET/V_OFFSET
|
||||
[0x3810, unchecked((byte)((hOffset >> 8) & 0xFF))],
|
||||
[0x3811, unchecked((byte)(hOffset & 0xFF))],
|
||||
[0x3812, unchecked((byte)((vOffset >> 8) & 0xFF))],
|
||||
// Timing Voffset
|
||||
[0x3813, unchecked((byte)(vOffset & 0xFF))]
|
||||
};
|
||||
|
@ -548,10 +579,11 @@ class Camera
|
|||
{
|
||||
var resetRegisters = new UInt16[][]
|
||||
{
|
||||
[0x30, 0x08, 0x82] // 复位命令
|
||||
[0x3103, 0x11],// system clock from pad, bit[1]
|
||||
[0x3008, 0x82] // 复位命令
|
||||
};
|
||||
|
||||
return await ConfigureRegisters(resetRegisters, customDelayMs: 5); // 复位后等待5ms
|
||||
return await ConfigureRegisters(resetRegisters, customDelayMs: 50); // 复位后等待5ms
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -576,7 +608,7 @@ class Camera
|
|||
{
|
||||
var basicRegisters = new UInt16[][]
|
||||
{
|
||||
[0x3103, 0x02],
|
||||
[0x3103, 0x02], // system clock from pad, bit[1]
|
||||
[0x3017, 0xff],
|
||||
[0x3018, 0xff],
|
||||
[0x3037, 0x13],
|
||||
|
@ -633,9 +665,7 @@ class Camera
|
|||
{
|
||||
[0x3905, 0x02],
|
||||
[0x3906, 0x10],
|
||||
[0x3901, 0x0a],
|
||||
[0x3035, 0x11], // 30fps
|
||||
[0x3036, PLL_MUX] // PLL倍频
|
||||
[0x3901, 0x0a]
|
||||
};
|
||||
|
||||
return await ConfigureRegisters(clockRegisters);
|
||||
|
@ -693,7 +723,6 @@ class Camera
|
|||
[0x3c04, 0x28],
|
||||
[0x3c05, 0x98],
|
||||
[0x3c06, 0x00],
|
||||
[0x3c07, 0x08],
|
||||
[0x3c08, 0x00],
|
||||
[0x3c09, 0x1c],
|
||||
[0x3c0a, 0x9c],
|
||||
|
@ -753,10 +782,6 @@ class Camera
|
|||
[0x3a1e, 0x26], // AEC控制;stable range out low
|
||||
[0x3a11, 0x60], // AEC控制; fast zone high
|
||||
[0x3a1f, 0x14], // AEC控制; fast zone low
|
||||
[0x3a02, 0x17], // 60Hz max exposure
|
||||
[0x3a03, 0x10], // 60Hz max exposure
|
||||
[0x3a14, 0x17], // 50Hz max exposure
|
||||
[0x3a15, 0x10], // 50Hz max exposure
|
||||
[0x3b07, 0x0a] // 帧曝光模式
|
||||
};
|
||||
|
||||
|
@ -900,14 +925,13 @@ class Camera
|
|||
{
|
||||
var timingRegisters = new UInt16[][]
|
||||
{
|
||||
[0x3820, 0x46], // vflip
|
||||
[0x3821, 0x01], // mirror
|
||||
[0x3814, 0x31], // timing X inc
|
||||
[0x3815, 0x31], // timing Y inc
|
||||
[0x3618, 0x00],
|
||||
[0x3612, 0x29],
|
||||
[0x3709, 0x52],
|
||||
[0x370c, 0x03]
|
||||
[0x3035, 0x11], // 60fps
|
||||
[0x3036, PLL_MUX],// PLL倍频
|
||||
[0x3c07, 0x08],
|
||||
[0x3820, 0x41], // vflip
|
||||
[0x3821, 0x00], // mirror
|
||||
[0x3814, 0x11], // timing X inc
|
||||
[0x3815, 0x11] // timing Y inc
|
||||
};
|
||||
|
||||
return await ConfigureRegisters(timingRegisters);
|
||||
|
@ -921,14 +945,22 @@ class Camera
|
|||
{
|
||||
var testRegisters = new UInt16[][]
|
||||
{
|
||||
[0x3618, 0x00],
|
||||
[0x3612, 0x29],
|
||||
[0x3709, 0x52],
|
||||
[0x370c, 0x03],
|
||||
[0x4004, 0x02], // BLC(背光) 2 lines
|
||||
[0x4713, 0x03], // JPEG mode 3
|
||||
[0x4407, 0x04], // 量化标度
|
||||
[0x460c, 0x20],
|
||||
[0x3a02, 0x17], // 60Hz max exposure
|
||||
[0x3a03, 0x10], // 60Hz max exposure
|
||||
[0x3a14, 0x17], // 50Hz max exposure
|
||||
[0x3a15, 0x10], // 50Hz max exposure
|
||||
[0x4837, 0x22], // DVP CLK divider
|
||||
[0x3824, 0x02], // DVP CLK divider
|
||||
// 彩条测试禁用
|
||||
[0x503d, 0x00],
|
||||
[0x503d, 0x80],
|
||||
[0x4741, 0x00],
|
||||
// 闪光灯配置
|
||||
[0x3016, 0x02],
|
||||
|
|
|
@ -108,7 +108,7 @@ public class DDS
|
|||
if (waveNum < 0 || waveNum > 3) return new(new ArgumentException(
|
||||
$"Wave number should be 0 ~ 3 instead of {waveNum}", nameof(waveNum)));
|
||||
|
||||
await MsgBus.UDPServer.ClearUDPData(this.address, 1);
|
||||
MsgBus.UDPServer.ClearUDPData(this.address, 1);
|
||||
logger.Trace("Clear udp data finished");
|
||||
|
||||
var ret = await UDPClientPool.WriteAddr(
|
||||
|
@ -132,7 +132,7 @@ public class DDS
|
|||
if (waveNum < 0 || waveNum > 3) return new(new ArgumentException(
|
||||
$"Wave number should be 0 ~ 3 instead of {waveNum}", nameof(waveNum)));
|
||||
|
||||
await MsgBus.UDPServer.ClearUDPData(this.address, 1);
|
||||
MsgBus.UDPServer.ClearUDPData(this.address, 1);
|
||||
logger.Trace("Clear udp data finished");
|
||||
|
||||
var ret = await UDPClientPool.WriteAddr(
|
||||
|
@ -158,7 +158,7 @@ public class DDS
|
|||
if (phase < 0 || phase > 4096) return new(new ArgumentException(
|
||||
$"Phase should be 0 ~ 4096 instead of {phase}", nameof(phase)));
|
||||
|
||||
await MsgBus.UDPServer.ClearUDPData(this.address, 1);
|
||||
MsgBus.UDPServer.ClearUDPData(this.address, 1);
|
||||
logger.Trace("Clear udp data finished");
|
||||
|
||||
var ret = await UDPClientPool.WriteAddr(
|
||||
|
|
|
@ -82,7 +82,7 @@ public class I2c
|
|||
/// <param name="taskID">[TODO:parameter]</param>
|
||||
/// <param name="timeout">[TODO:parameter]</param>
|
||||
/// <returns>[TODO:return]</returns>
|
||||
public I2c(string address, int port, int taskID,int timeout = 2000)
|
||||
public I2c(string address, int port, int taskID, int timeout = 2000)
|
||||
{
|
||||
if (timeout < 0)
|
||||
throw new ArgumentException("Timeout couldn't be negative", nameof(timeout));
|
||||
|
@ -109,7 +109,7 @@ public class I2c
|
|||
}
|
||||
|
||||
// 清除UDP服务器接收缓冲区
|
||||
await MsgBus.UDPServer.ClearUDPData(this.address, this.taskID);
|
||||
MsgBus.UDPServer.ClearUDPData(this.address, this.taskID);
|
||||
|
||||
logger.Trace($"Clear up udp server {this.address} receive data");
|
||||
|
||||
|
@ -214,7 +214,7 @@ public class I2c
|
|||
}
|
||||
|
||||
// 清除UDP服务器接收缓冲区
|
||||
await MsgBus.UDPServer.ClearUDPData(this.address, this.taskID);
|
||||
MsgBus.UDPServer.ClearUDPData(this.address, this.taskID);
|
||||
|
||||
logger.Trace($"Clear up udp server {this.address} receive data");
|
||||
|
||||
|
|
|
@ -627,7 +627,7 @@ public class Jtag
|
|||
public async ValueTask<Result<uint>> ReadIDCode()
|
||||
{
|
||||
// Clear Data
|
||||
await MsgBus.UDPServer.ClearUDPData(this.address, 0);
|
||||
MsgBus.UDPServer.ClearUDPData(this.address, 0);
|
||||
|
||||
logger.Trace($"Clear up udp server {this.address,0} receive data");
|
||||
|
||||
|
@ -665,7 +665,7 @@ public class Jtag
|
|||
public async ValueTask<Result<uint>> ReadStatusReg()
|
||||
{
|
||||
// Clear Data
|
||||
await MsgBus.UDPServer.ClearUDPData(this.address, 0);
|
||||
MsgBus.UDPServer.ClearUDPData(this.address, 0);
|
||||
|
||||
logger.Trace($"Clear up udp server {this.address,0} receive data");
|
||||
|
||||
|
@ -702,7 +702,7 @@ public class Jtag
|
|||
public async ValueTask<Result<bool>> DownloadBitstream(byte[] bitstream)
|
||||
{
|
||||
// Clear Data
|
||||
await MsgBus.UDPServer.ClearUDPData(this.address, 0);
|
||||
MsgBus.UDPServer.ClearUDPData(this.address, 0);
|
||||
|
||||
logger.Trace($"Clear up udp server {this.address,0} receive data");
|
||||
|
||||
|
@ -786,7 +786,7 @@ public class Jtag
|
|||
logger.Debug($"Get boundar scan registers number: {portNum}");
|
||||
|
||||
// Clear Data
|
||||
await MsgBus.UDPServer.ClearUDPData(this.address, 0);
|
||||
MsgBus.UDPServer.ClearUDPData(this.address, 0);
|
||||
|
||||
logger.Trace($"Clear up udp server {this.address,0} receive data");
|
||||
|
||||
|
@ -853,7 +853,7 @@ public class Jtag
|
|||
public async ValueTask<Result<bool>> SetSpeed(UInt32 speed)
|
||||
{
|
||||
// Clear Data
|
||||
await MsgBus.UDPServer.ClearUDPData(this.address, 0);
|
||||
MsgBus.UDPServer.ClearUDPData(this.address, 0);
|
||||
|
||||
logger.Trace($"Clear up udp server {this.address,0} receive data");
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ public class MatrixKey
|
|||
public async ValueTask<Result<bool>> EnableControl()
|
||||
{
|
||||
if (MsgBus.IsRunning)
|
||||
await MsgBus.UDPServer.ClearUDPData(this.address, 1);
|
||||
MsgBus.UDPServer.ClearUDPData(this.address, 1);
|
||||
else return new(new Exception("Message Bus not work!"));
|
||||
|
||||
var ret = await UDPClientPool.WriteAddr(this.ep, 1, MatrixKeyAddr.KEY_ENABLE, 1, this.timeout);
|
||||
|
@ -59,7 +59,7 @@ public class MatrixKey
|
|||
public async ValueTask<Result<bool>> DisableControl()
|
||||
{
|
||||
if (MsgBus.IsRunning)
|
||||
await MsgBus.UDPServer.ClearUDPData(this.address, 1);
|
||||
MsgBus.UDPServer.ClearUDPData(this.address, 1);
|
||||
else return new(new Exception("Message Bus not work!"));
|
||||
|
||||
var ret = await UDPClientPool.WriteAddr(this.ep, 1, MatrixKeyAddr.KEY_ENABLE, 0, this.timeout);
|
||||
|
@ -75,7 +75,7 @@ public class MatrixKey
|
|||
public async ValueTask<Result<bool>> ControlKey(BitArray keyStates)
|
||||
{
|
||||
if (MsgBus.IsRunning)
|
||||
await MsgBus.UDPServer.ClearUDPData(this.address, 1);
|
||||
MsgBus.UDPServer.ClearUDPData(this.address, 1);
|
||||
else return new(new Exception("Message Bus not work!"));
|
||||
|
||||
if (keyStates.Length != 16) return new(new ArgumentException(
|
||||
|
|
|
@ -45,7 +45,7 @@ public class Power
|
|||
public async ValueTask<Result<bool>> SetPowerOnOff(bool enable)
|
||||
{
|
||||
if (MsgBus.IsRunning)
|
||||
await MsgBus.UDPServer.ClearUDPData(this.address, 1);
|
||||
MsgBus.UDPServer.ClearUDPData(this.address, 1);
|
||||
else return new(new Exception("Message Bus not work!"));
|
||||
|
||||
var ret = await UDPClientPool.WriteAddr(this.ep, 1, PowerAddr.PowerCtrl, Convert.ToUInt32(enable), this.timeout);
|
||||
|
|
|
@ -382,7 +382,7 @@ public class RemoteUpdater
|
|||
/// <returns>[TODO:return]</returns>
|
||||
public async ValueTask<Result<bool>> HotResetBitstream(int bitstreamNum)
|
||||
{
|
||||
await MsgBus.UDPServer.ClearUDPData(this.address, 0);
|
||||
MsgBus.UDPServer.ClearUDPData(this.address, 0);
|
||||
logger.Trace("Clear udp data finished");
|
||||
|
||||
{
|
||||
|
@ -412,7 +412,7 @@ public class RemoteUpdater
|
|||
byte[]? bitstream2,
|
||||
byte[]? bitstream3)
|
||||
{
|
||||
await MsgBus.UDPServer.ClearUDPData(this.address, 0);
|
||||
MsgBus.UDPServer.ClearUDPData(this.address, 0);
|
||||
logger.Trace("Clear udp data finished");
|
||||
|
||||
for (int bitstreamNum = 0; bitstreamNum < 4; bitstreamNum++)
|
||||
|
@ -463,7 +463,7 @@ public class RemoteUpdater
|
|||
$"The length of data should be divided by 4096, bug given {bytesData.Length}", nameof(bytesData)));
|
||||
var bitstreamBlockNum = bytesData.Length / (4 * 1024);
|
||||
|
||||
await MsgBus.UDPServer.ClearUDPData(this.address, 0);
|
||||
MsgBus.UDPServer.ClearUDPData(this.address, 0);
|
||||
logger.Trace("Clear udp data finished");
|
||||
|
||||
{
|
||||
|
@ -539,7 +539,7 @@ public class RemoteUpdater
|
|||
/// <returns>[TODO:return]</returns>
|
||||
public async ValueTask<Result<UInt32>> GetVersion()
|
||||
{
|
||||
await MsgBus.UDPServer.ClearUDPData(this.address, 0);
|
||||
MsgBus.UDPServer.ClearUDPData(this.address, 0);
|
||||
logger.Trace("Clear udp data finished");
|
||||
|
||||
{
|
||||
|
|
|
@ -156,8 +156,7 @@ public class HttpVideoStreamService : BackgroundService
|
|||
throw new Exception("Please config camera first");
|
||||
}
|
||||
_cameraEnable = isEnabled;
|
||||
await _camera.EnableCamera(_cameraEnable);
|
||||
await _camera.SleepCameraHardware(!_cameraEnable);
|
||||
await _camera.EnableHardwareTrans(_cameraEnable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -477,7 +477,7 @@ public class UDPClientPool
|
|||
int outstanding = sentCount - (found.HasValue ? found.Value : 0);
|
||||
|
||||
// If outstanding >= 512 - batchSize, wait for some data to be received
|
||||
if (outstanding >= 512 - batchSize)
|
||||
if (outstanding >= 256 - batchSize)
|
||||
continue;
|
||||
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using DotNext;
|
||||
using DotNext.Threading;
|
||||
using Newtonsoft.Json;
|
||||
using WebProtocol;
|
||||
|
||||
|
@ -72,12 +72,10 @@ public class UDPServer
|
|||
{
|
||||
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
|
||||
private Dictionary<string, Queue<UDPData>> udpData = new Dictionary<string, Queue<UDPData>>();
|
||||
|
||||
private Semaphore taskPool = new Semaphore(3, 3);
|
||||
private ConcurrentDictionary<string, ConcurrentQueue<UDPData>> udpData = new ConcurrentDictionary<string, ConcurrentQueue<UDPData>>();
|
||||
|
||||
private int listenPort;
|
||||
private UdpClient listener;
|
||||
private List<UdpClient> listeners = new List<UdpClient>();
|
||||
private IPEndPoint groupEP;
|
||||
|
||||
private bool isRunning = false;
|
||||
|
@ -103,15 +101,19 @@ public class UDPServer
|
|||
/// Construct a udp server with fixed port
|
||||
/// </summary>
|
||||
/// <param name="port"> Device UDP Port </param>
|
||||
/// <param name="num"> UDP Client Num </param>
|
||||
/// <returns> UDPServer class </returns>
|
||||
public UDPServer(int port)
|
||||
public UDPServer(int port, int num)
|
||||
{
|
||||
// Construction
|
||||
listenPort = port;
|
||||
this.listenPort = port;
|
||||
try
|
||||
{
|
||||
listener = new UdpClient(listenPort);
|
||||
groupEP = new IPEndPoint(IPAddress.Any, listenPort);
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
listeners.Add(new UdpClient(this.listenPort + i));
|
||||
}
|
||||
this.groupEP = new IPEndPoint(IPAddress.Any, listenPort);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -145,25 +147,19 @@ public class UDPServer
|
|||
{
|
||||
UDPData? data = null;
|
||||
|
||||
// logger.Debug($"Caller \"{callerName}|{callerLineNum}\": Try to find {ipAddr}-{taskID} UDP Data");
|
||||
|
||||
var startTime = DateTime.Now;
|
||||
var isTimeout = false;
|
||||
var timeleft = TimeSpan.FromMilliseconds(timeout);
|
||||
while (!isTimeout)
|
||||
{
|
||||
var elapsed = DateTime.Now - startTime;
|
||||
isTimeout = elapsed >= TimeSpan.FromMilliseconds(timeout);
|
||||
if (isTimeout) break;
|
||||
|
||||
timeleft = TimeSpan.FromMilliseconds(timeout) - elapsed;
|
||||
using (await udpData.AcquireWriteLockAsync(timeleft))
|
||||
lock (udpData)
|
||||
{
|
||||
if (udpData.ContainsKey($"{ipAddr}-{taskID}") &&
|
||||
udpData.TryGetValue($"{ipAddr}-{taskID}", out var dataQueue) &&
|
||||
dataQueue.Count > 0)
|
||||
if (udpData.TryGetValue($"{ipAddr}-{taskID}", out var dataQueue) &&
|
||||
dataQueue.TryDequeue(out data))
|
||||
{
|
||||
data = dataQueue.Dequeue();
|
||||
// logger.Debug($"Find UDP Data: {data.ToString()}");
|
||||
break;
|
||||
}
|
||||
|
@ -196,22 +192,22 @@ public class UDPServer
|
|||
|
||||
var startTime = DateTime.Now;
|
||||
var isTimeout = false;
|
||||
var timeleft = TimeSpan.FromMilliseconds(timeout);
|
||||
while (!isTimeout)
|
||||
{
|
||||
var elapsed = DateTime.Now - startTime;
|
||||
isTimeout = elapsed >= TimeSpan.FromMilliseconds(timeout);
|
||||
if (isTimeout) break;
|
||||
|
||||
timeleft = TimeSpan.FromMilliseconds(timeout) - elapsed;
|
||||
using (await udpData.AcquireWriteLockAsync(timeleft))
|
||||
lock (udpData)
|
||||
{
|
||||
if (udpData.ContainsKey($"{ipAddr}-{taskID}") &&
|
||||
udpData.TryGetValue($"{ipAddr}-{taskID}", out var dataQueue) &&
|
||||
dataQueue.Count > 0)
|
||||
if (udpData.TryGetValue($"{ipAddr}-{taskID}", out var dataQueue) &&
|
||||
!dataQueue.IsEmpty)
|
||||
{
|
||||
data = dataQueue.ToList();
|
||||
dataQueue.Clear();
|
||||
data = new List<UDPData>();
|
||||
while (dataQueue.TryDequeue(out var item))
|
||||
{
|
||||
data.Add(item);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -241,26 +237,20 @@ public class UDPServer
|
|||
|
||||
var startTime = DateTime.Now;
|
||||
var isTimeout = false;
|
||||
var timeleft = TimeSpan.FromMilliseconds(timeout);
|
||||
while (!isTimeout)
|
||||
{
|
||||
var elapsed = DateTime.Now - startTime;
|
||||
isTimeout = elapsed >= TimeSpan.FromMilliseconds(timeout);
|
||||
if (isTimeout) break;
|
||||
|
||||
timeleft = TimeSpan.FromMilliseconds(timeout) - elapsed;
|
||||
using (await udpData.AcquireReadLockAsync(timeleft))
|
||||
if (udpData.TryGetValue($"{ipAddr}-{taskID}", out var dataQueue) &&
|
||||
!dataQueue.IsEmpty)
|
||||
{
|
||||
if (udpData.ContainsKey($"{ipAddr}-{taskID}") &&
|
||||
udpData.TryGetValue($"{ipAddr}-{taskID}", out var dataQueue) &&
|
||||
dataQueue.Count > 0)
|
||||
{
|
||||
data = dataQueue.ToList();
|
||||
data = dataQueue.ToArray().ToList();
|
||||
// logger.Debug($"Find UDP Data Array: {JsonConvert.SerializeObject(data)}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data is null)
|
||||
{
|
||||
|
@ -286,24 +276,18 @@ public class UDPServer
|
|||
|
||||
var startTime = DateTime.Now;
|
||||
var isTimeout = false;
|
||||
var timeleft = TimeSpan.FromMilliseconds(timeout);
|
||||
while (!isTimeout)
|
||||
{
|
||||
var elapsed = DateTime.Now - startTime;
|
||||
isTimeout = elapsed >= TimeSpan.FromMilliseconds(timeout);
|
||||
if (isTimeout) break;
|
||||
|
||||
timeleft = TimeSpan.FromMilliseconds(timeout) - elapsed;
|
||||
using (await udpData.AcquireReadLockAsync(timeleft))
|
||||
{
|
||||
if (udpData.ContainsKey($"{ipAddr}-{taskID}") &&
|
||||
udpData.TryGetValue($"{ipAddr}-{taskID}", out var dataQueue))
|
||||
if (udpData.TryGetValue($"{ipAddr}-{taskID}", out var dataQueue))
|
||||
{
|
||||
count = dataQueue.Count;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count is null)
|
||||
{
|
||||
|
@ -367,52 +351,26 @@ public class UDPServer
|
|||
|
||||
return retPack.Value;
|
||||
}
|
||||
static int ReceiveHandleCcount = 0;
|
||||
|
||||
private void ReceiveHandler(IAsyncResult res)
|
||||
private async Task ReceiveHandler(byte[] data, IPEndPoint endPoint, DateTime time)
|
||||
{
|
||||
// 异步锁保护 udpData
|
||||
await Task.Run(() =>
|
||||
{
|
||||
var remoteEP = new IPEndPoint(IPAddress.Any, listenPort);
|
||||
byte[] bytes = listener.EndReceive(res, ref remoteEP);
|
||||
|
||||
// 提前开始接收下一个包
|
||||
listener.BeginReceive(new AsyncCallback(ReceiveHandler), null);
|
||||
logger.Debug($"Test ReceiveHandler Count = {ReceiveHandleCcount}");
|
||||
ReceiveHandleCcount++;
|
||||
|
||||
// Handle RemoteEP
|
||||
if (remoteEP is null)
|
||||
if (endPoint is null)
|
||||
{
|
||||
logger.Debug($"Receive Data from Unknown at {DateTime.Now.ToString()}:");
|
||||
logger.Debug($" Original Data : {BitConverter.ToString(bytes).Replace("-", " ")}");
|
||||
logger.Debug($" Original Data : {BitConverter.ToString(data).Replace("-", " ")}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 异步处理数据包
|
||||
Task.Run(() =>
|
||||
{
|
||||
var udpData = RecordUDPData(bytes, remoteEP, Convert.ToInt32(bytes[1]));
|
||||
PrintData(udpData);
|
||||
var udpDataObj = RecordUDPData(data, endPoint, time, Convert.ToInt32(data[1]));
|
||||
// PrintData(udpDataObj);
|
||||
});
|
||||
}
|
||||
|
||||
private bool SendBytes(IPEndPoint endPoint, byte[] buf)
|
||||
{
|
||||
var sendLen = listener.Send(buf, endPoint);
|
||||
|
||||
if (sendLen == buf.Length) { return true; }
|
||||
else { return false; }
|
||||
}
|
||||
|
||||
private bool SendString(IPEndPoint endPoint, string text)
|
||||
{
|
||||
byte[] buf = Encoding.ASCII.GetBytes(text);
|
||||
var sendLen = listener.Send(buf, endPoint);
|
||||
|
||||
if (sendLen == buf.Length) { return true; }
|
||||
else { return false; }
|
||||
}
|
||||
|
||||
private UDPData RecordUDPData(byte[] bytes, IPEndPoint remoteEP, int taskID)
|
||||
private UDPData RecordUDPData(byte[] bytes, IPEndPoint remoteEP, DateTime time, int taskID)
|
||||
{
|
||||
var remoteAddress = remoteEP.Address.ToString();
|
||||
var remotePort = remoteEP.Port;
|
||||
|
@ -422,26 +380,25 @@ public class UDPServer
|
|||
Port = remotePort,
|
||||
TaskID = taskID,
|
||||
Data = bytes,
|
||||
DateTime = DateTime.Now,
|
||||
DateTime = time,
|
||||
HasRead = false,
|
||||
};
|
||||
|
||||
using (udpData.AcquireWriteLock())
|
||||
{
|
||||
// Record UDP Receive Data
|
||||
if (udpData.ContainsKey($"{remoteAddress}-{taskID}") &&
|
||||
udpData.TryGetValue($"{remoteAddress}-{taskID}", out var dataQueue))
|
||||
|
||||
lock (udpData)
|
||||
{
|
||||
var key = $"{remoteAddress}-{taskID}";
|
||||
var dataQueue = udpData.GetOrAdd(key, _ => new ConcurrentQueue<UDPData>());
|
||||
dataQueue.Enqueue(data);
|
||||
logger.Debug($"Test Lock dataQueue.Count = {dataQueue.Count}");
|
||||
}
|
||||
else
|
||||
|
||||
// 对队列进行一次按时间排序
|
||||
if (dataQueue.Count > 0)
|
||||
{
|
||||
var queue = new Queue<UDPData>();
|
||||
queue.Enqueue(data);
|
||||
udpData.Add($"{remoteAddress}-{taskID}", queue);
|
||||
logger.Trace("Receive data from new client");
|
||||
var sorted = dataQueue.OrderBy(d => d.DateTime).ToList();
|
||||
udpData.TryUpdate(key, new ConcurrentQueue<UDPData>(sorted), dataQueue);
|
||||
}
|
||||
|
||||
PrintAllData();
|
||||
}
|
||||
|
||||
return data;
|
||||
|
@ -451,7 +408,7 @@ public class UDPServer
|
|||
/// 输出UDP Data到log中
|
||||
/// </summary>
|
||||
/// <param name="data">UDP数据</param>
|
||||
public void PrintData(UDPData data)
|
||||
public string PrintData(UDPData data)
|
||||
{
|
||||
var bytes = data.Data;
|
||||
var sign = bytes[0];
|
||||
|
@ -489,6 +446,11 @@ public class UDPServer
|
|||
// logger.Debug($"Receive Data from {data.Address}:{data.Port} at {data.DateTime.ToString()}:");
|
||||
// logger.Debug($" Original Data : {BitConverter.ToString(bytes).Replace("-", " ")}");
|
||||
// if (recvData.Length != 0) logger.Debug($" Decoded Data : {recvData}");
|
||||
return $@"
|
||||
Receive Data from {data.Address}:{data.Port} at {data.DateTime.ToString()}:
|
||||
Original Data : {BitConverter.ToString(bytes).Replace("-", " ")}
|
||||
Decoded Data : {recvData}
|
||||
";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -497,16 +459,13 @@ public class UDPServer
|
|||
/// <returns> void </returns>
|
||||
public void PrintAllData()
|
||||
{
|
||||
using (udpData.AcquireReadLock())
|
||||
{
|
||||
// logger.Debug("Ready Data:");
|
||||
logger.Debug("Ready Data:");
|
||||
|
||||
foreach (var ip in udpData)
|
||||
foreach (var kvp in udpData)
|
||||
{
|
||||
foreach (var data in ip.Value)
|
||||
foreach (var data in kvp.Value)
|
||||
{
|
||||
// logger.Debug(data.ToString());
|
||||
}
|
||||
logger.Debug(PrintData(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -517,19 +476,15 @@ public class UDPServer
|
|||
/// <param name="ipAddr">IP地址</param>
|
||||
/// <param name="taskID">[TODO:parameter]</param>
|
||||
/// <returns>无</returns>
|
||||
public async Task ClearUDPData(string ipAddr, int taskID)
|
||||
public void ClearUDPData(string ipAddr, int taskID)
|
||||
{
|
||||
using (await udpData.AcquireWriteLockAsync())
|
||||
var key = $"{ipAddr}-{taskID}";
|
||||
if (udpData.TryGetValue(key, out var dataQueue))
|
||||
{
|
||||
if (udpData.ContainsKey($"{ipAddr}-{taskID}") &&
|
||||
udpData.TryGetValue($"{ipAddr}-{taskID}", out var dataQueue) &&
|
||||
dataQueue.Count > 0)
|
||||
{
|
||||
dataQueue.Clear();
|
||||
// 清空队列的最有效方式是替换为新的队列
|
||||
udpData.TryUpdate(key, new ConcurrentQueue<UDPData>(), dataQueue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Start UDP Server
|
||||
|
@ -537,17 +492,32 @@ public class UDPServer
|
|||
/// <returns>None</returns>
|
||||
public void Start()
|
||||
{
|
||||
this.isRunning = true;
|
||||
try
|
||||
{
|
||||
this.listener.BeginReceive(new AsyncCallback(ReceiveHandler), null);
|
||||
foreach (var client in listeners)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (this.isRunning)
|
||||
{
|
||||
try
|
||||
{
|
||||
UdpReceiveResult result = await client.ReceiveAsync();
|
||||
ReceiveHandler(result.Buffer, result.RemoteEndPoint, DateTime.Now);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.ToString());
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.isRunning = true;
|
||||
this.isRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -557,8 +527,12 @@ public class UDPServer
|
|||
/// <returns>None</returns>
|
||||
public void Stop()
|
||||
{
|
||||
this.listener.Close();
|
||||
foreach (var item in listeners)
|
||||
{
|
||||
item.Close();
|
||||
}
|
||||
this.isRunning = false;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
677
src/APIClient.ts
677
src/APIClient.ts
|
@ -465,6 +465,90 @@ export class VideoStreamClient {
|
|||
}
|
||||
return Promise.resolve<any>(null as any);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化摄像头自动对焦功能
|
||||
* @return 初始化结果
|
||||
*/
|
||||
initAutoFocus(): Promise<FileResponse | null> {
|
||||
let url_ = this.baseUrl + "/api/VideoStream/InitAutoFocus";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: RequestInit = {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Accept": "application/octet-stream"
|
||||
}
|
||||
};
|
||||
|
||||
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||
return this.processInitAutoFocus(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processInitAutoFocus(response: Response): Promise<FileResponse | null> {
|
||||
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 || status === 206) {
|
||||
const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined;
|
||||
let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined;
|
||||
let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined;
|
||||
if (fileName) {
|
||||
fileName = decodeURIComponent(fileName);
|
||||
} else {
|
||||
fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined;
|
||||
fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined;
|
||||
}
|
||||
return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _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<FileResponse | null>(null as any);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行自动对焦
|
||||
* @return 对焦结果
|
||||
*/
|
||||
autoFocus(): Promise<FileResponse | null> {
|
||||
let url_ = this.baseUrl + "/api/VideoStream/AutoFocus";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: RequestInit = {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Accept": "application/octet-stream"
|
||||
}
|
||||
};
|
||||
|
||||
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||
return this.processAutoFocus(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processAutoFocus(response: Response): Promise<FileResponse | null> {
|
||||
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 || status === 206) {
|
||||
const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined;
|
||||
let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined;
|
||||
let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined;
|
||||
if (fileName) {
|
||||
fileName = decodeURIComponent(fileName);
|
||||
} else {
|
||||
fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined;
|
||||
fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined;
|
||||
}
|
||||
return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _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<FileResponse | null>(null as any);
|
||||
}
|
||||
}
|
||||
|
||||
export class BsdlParserClient {
|
||||
|
@ -1885,6 +1969,451 @@ export class JtagClient {
|
|||
}
|
||||
}
|
||||
|
||||
export class LogicAnalyzerClient {
|
||||
private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
|
||||
private baseUrl: string;
|
||||
protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
|
||||
|
||||
constructor(baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> }) {
|
||||
this.http = http ? http : window as any;
|
||||
this.baseUrl = baseUrl ?? "http://localhost:5000";
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置捕获模式
|
||||
* @param captureOn (optional) 是否开始捕获
|
||||
* @param force (optional) 是否强制捕获
|
||||
* @return 操作结果
|
||||
*/
|
||||
setCaptureMode(captureOn: boolean | undefined, force: boolean | undefined): Promise<boolean> {
|
||||
let url_ = this.baseUrl + "/api/LogicAnalyzer/SetCaptureMode?";
|
||||
if (captureOn === null)
|
||||
throw new Error("The parameter 'captureOn' cannot be null.");
|
||||
else if (captureOn !== undefined)
|
||||
url_ += "captureOn=" + encodeURIComponent("" + captureOn) + "&";
|
||||
if (force === null)
|
||||
throw new Error("The parameter 'force' cannot be null.");
|
||||
else if (force !== undefined)
|
||||
url_ += "force=" + encodeURIComponent("" + force) + "&";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: RequestInit = {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||
return this.processSetCaptureMode(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processSetCaptureMode(response: Response): Promise<boolean> {
|
||||
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 : <any>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<boolean>(null as any);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取捕获状态
|
||||
* @return 捕获状态
|
||||
*/
|
||||
getCaptureStatus(): Promise<CaptureStatus> {
|
||||
let url_ = this.baseUrl + "/api/LogicAnalyzer/GetCaptureStatus";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: RequestInit = {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||
return this.processGetCaptureStatus(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processGetCaptureStatus(response: Response): Promise<CaptureStatus> {
|
||||
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 : <any>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<CaptureStatus>(null as any);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置全局触发模式
|
||||
* @param mode (optional) 全局触发模式
|
||||
* @return 操作结果
|
||||
*/
|
||||
setGlobalTrigMode(mode: GlobalCaptureMode | undefined): Promise<boolean> {
|
||||
let url_ = this.baseUrl + "/api/LogicAnalyzer/SetGlobalTrigMode?";
|
||||
if (mode === null)
|
||||
throw new Error("The parameter 'mode' cannot be null.");
|
||||
else if (mode !== undefined)
|
||||
url_ += "mode=" + encodeURIComponent("" + mode) + "&";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: RequestInit = {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||
return this.processSetGlobalTrigMode(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processSetGlobalTrigMode(response: Response): Promise<boolean> {
|
||||
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 : <any>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<boolean>(null as any);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置信号触发模式
|
||||
* @param signalIndex (optional) 信号索引 (0-7)
|
||||
* @param op (optional) 操作符
|
||||
* @param val (optional) 信号值
|
||||
* @return 操作结果
|
||||
*/
|
||||
setSignalTrigMode(signalIndex: number | undefined, op: SignalOperator | undefined, val: SignalValue | undefined): Promise<boolean> {
|
||||
let url_ = this.baseUrl + "/api/LogicAnalyzer/SetSignalTrigMode?";
|
||||
if (signalIndex === null)
|
||||
throw new Error("The parameter 'signalIndex' cannot be null.");
|
||||
else if (signalIndex !== undefined)
|
||||
url_ += "signalIndex=" + encodeURIComponent("" + signalIndex) + "&";
|
||||
if (op === null)
|
||||
throw new Error("The parameter 'op' cannot be null.");
|
||||
else if (op !== undefined)
|
||||
url_ += "op=" + encodeURIComponent("" + op) + "&";
|
||||
if (val === null)
|
||||
throw new Error("The parameter 'val' cannot be null.");
|
||||
else if (val !== undefined)
|
||||
url_ += "val=" + encodeURIComponent("" + val) + "&";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: RequestInit = {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||
return this.processSetSignalTrigMode(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processSetSignalTrigMode(response: Response): Promise<boolean> {
|
||||
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 : <any>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<boolean>(null as any);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量配置捕获参数
|
||||
* @param config 捕获配置
|
||||
* @return 操作结果
|
||||
*/
|
||||
configureCapture(config: CaptureConfig): Promise<boolean> {
|
||||
let url_ = this.baseUrl + "/api/LogicAnalyzer/ConfigureCapture";
|
||||
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.processConfigureCapture(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processConfigureCapture(response: Response): Promise<boolean> {
|
||||
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 : <any>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<boolean>(null as any);
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制捕获
|
||||
* @return 操作结果
|
||||
*/
|
||||
forceCapture(): Promise<boolean> {
|
||||
let url_ = this.baseUrl + "/api/LogicAnalyzer/ForceCapture";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: RequestInit = {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||
return this.processForceCapture(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processForceCapture(response: Response): Promise<boolean> {
|
||||
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 : <any>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<boolean>(null as any);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取捕获数据
|
||||
* @return 捕获的波形数据(Base64编码)
|
||||
*/
|
||||
getCaptureData(): Promise<string> {
|
||||
let url_ = this.baseUrl + "/api/LogicAnalyzer/GetCaptureData";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: RequestInit = {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
return this.http.fetch(url_, options_).then((_response: Response) => {
|
||||
return this.processGetCaptureData(_response);
|
||||
});
|
||||
}
|
||||
|
||||
protected processGetCaptureData(response: Response): Promise<string> {
|
||||
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 : <any>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<string>(null as any);
|
||||
}
|
||||
}
|
||||
|
||||
export class MatrixKeyClient {
|
||||
private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
|
||||
private baseUrl: string;
|
||||
|
@ -3286,6 +3815,154 @@ export interface IArgumentException extends ISystemException {
|
|||
paramName?: string | undefined;
|
||||
}
|
||||
|
||||
/** 逻辑分析仪运行状态枚举 */
|
||||
export enum CaptureStatus {
|
||||
None = 0,
|
||||
CaptureOn = 1,
|
||||
CaptureForce = 256,
|
||||
CaptureBusy = 65536,
|
||||
CaptureDone = 16777216,
|
||||
}
|
||||
|
||||
/** 全局触发模式枚举,定义多路信号触发条件的逻辑组合方式 */
|
||||
export enum GlobalCaptureMode {
|
||||
AND = 0,
|
||||
OR = 1,
|
||||
NAND = 2,
|
||||
NOR = 3,
|
||||
}
|
||||
|
||||
/** 信号M的操作符枚举 */
|
||||
export enum SignalOperator {
|
||||
Equal = 0,
|
||||
NotEqual = 1,
|
||||
LessThan = 2,
|
||||
LessThanOrEqual = 3,
|
||||
GreaterThan = 4,
|
||||
GreaterThanOrEqual = 5,
|
||||
}
|
||||
|
||||
/** 信号M的值枚举 */
|
||||
export enum SignalValue {
|
||||
Logic0 = 0,
|
||||
Logic1 = 1,
|
||||
NotCare = 2,
|
||||
Rise = 3,
|
||||
Fall = 4,
|
||||
RiseOrFall = 5,
|
||||
NoChange = 6,
|
||||
SomeNumber = 7,
|
||||
}
|
||||
|
||||
/** 捕获配置 */
|
||||
export class CaptureConfig implements ICaptureConfig {
|
||||
/** 全局触发模式 */
|
||||
globalMode!: GlobalCaptureMode;
|
||||
/** 信号触发配置列表 */
|
||||
signalConfigs!: SignalTriggerConfig[];
|
||||
|
||||
constructor(data?: ICaptureConfig) {
|
||||
if (data) {
|
||||
for (var property in data) {
|
||||
if (data.hasOwnProperty(property))
|
||||
(<any>this)[property] = (<any>data)[property];
|
||||
}
|
||||
}
|
||||
if (!data) {
|
||||
this.signalConfigs = [];
|
||||
}
|
||||
}
|
||||
|
||||
init(_data?: any) {
|
||||
if (_data) {
|
||||
this.globalMode = _data["globalMode"];
|
||||
if (Array.isArray(_data["signalConfigs"])) {
|
||||
this.signalConfigs = [] as any;
|
||||
for (let item of _data["signalConfigs"])
|
||||
this.signalConfigs!.push(SignalTriggerConfig.fromJS(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static fromJS(data: any): CaptureConfig {
|
||||
data = typeof data === 'object' ? data : {};
|
||||
let result = new CaptureConfig();
|
||||
result.init(data);
|
||||
return result;
|
||||
}
|
||||
|
||||
toJSON(data?: any) {
|
||||
data = typeof data === 'object' ? data : {};
|
||||
data["globalMode"] = this.globalMode;
|
||||
if (Array.isArray(this.signalConfigs)) {
|
||||
data["signalConfigs"] = [];
|
||||
for (let item of this.signalConfigs)
|
||||
data["signalConfigs"].push(item.toJSON());
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
/** 捕获配置 */
|
||||
export interface ICaptureConfig {
|
||||
/** 全局触发模式 */
|
||||
globalMode: GlobalCaptureMode;
|
||||
/** 信号触发配置列表 */
|
||||
signalConfigs: SignalTriggerConfig[];
|
||||
}
|
||||
|
||||
/** 信号触发配置 */
|
||||
export class SignalTriggerConfig implements ISignalTriggerConfig {
|
||||
/** 信号索引 (0-7) */
|
||||
signalIndex!: number;
|
||||
/** 操作符 */
|
||||
operator!: SignalOperator;
|
||||
/** 信号值 */
|
||||
value!: SignalValue;
|
||||
|
||||
constructor(data?: ISignalTriggerConfig) {
|
||||
if (data) {
|
||||
for (var property in data) {
|
||||
if (data.hasOwnProperty(property))
|
||||
(<any>this)[property] = (<any>data)[property];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(_data?: any) {
|
||||
if (_data) {
|
||||
this.signalIndex = _data["signalIndex"];
|
||||
this.operator = _data["operator"];
|
||||
this.value = _data["value"];
|
||||
}
|
||||
}
|
||||
|
||||
static fromJS(data: any): SignalTriggerConfig {
|
||||
data = typeof data === 'object' ? data : {};
|
||||
let result = new SignalTriggerConfig();
|
||||
result.init(data);
|
||||
return result;
|
||||
}
|
||||
|
||||
toJSON(data?: any) {
|
||||
data = typeof data === 'object' ? data : {};
|
||||
data["signalIndex"] = this.signalIndex;
|
||||
data["operator"] = this.operator;
|
||||
data["value"] = this.value;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
/** 信号触发配置 */
|
||||
export interface ISignalTriggerConfig {
|
||||
/** 信号索引 (0-7) */
|
||||
signalIndex: number;
|
||||
/** 操作符 */
|
||||
operator: SignalOperator;
|
||||
/** 信号值 */
|
||||
value: SignalValue;
|
||||
}
|
||||
|
||||
/** Package options which to send address to read or write */
|
||||
export class SendAddrPackOptions implements ISendAddrPackOptions {
|
||||
/** 突发类型 */
|
||||
|
|
|
@ -48,14 +48,15 @@
|
|||
import { computed } from "vue";
|
||||
import { CheckCircle, XCircle, AlertTriangle, Info, X } from "lucide-vue-next";
|
||||
import { useAlertStore } from ".";
|
||||
import { useRequiredInjection } from "@/utils/Common";
|
||||
|
||||
const alertStore = useAlertStore();
|
||||
const alertStore = useRequiredInjection(useAlertStore);
|
||||
|
||||
// Computed classes for different alert types
|
||||
const alertClasses = computed(() => {
|
||||
const baseClasses = "shadow-lg max-w-sm";
|
||||
|
||||
switch (alertStore?.alertState.value.type) {
|
||||
switch (alertStore.alertState.value.type) {
|
||||
case "success":
|
||||
return `${baseClasses} alert-success`;
|
||||
case "error":
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
<template>
|
||||
<div class="space-y-4">
|
||||
<!-- 通道状态概览 -->
|
||||
<div class="stats stats-horizontal bg-base-100 shadow flex justify-between">
|
||||
<div class="stat">
|
||||
<div class="stat-title">总通道数</div>
|
||||
<div class="stat-value text-primary">8</div>
|
||||
<div class="stat-desc">逻辑分析仪通道</div>
|
||||
</div>
|
||||
|
||||
<div class="stat">
|
||||
<div class="stat-title">启用通道</div>
|
||||
<div class="stat-value text-success">{{ enabledChannelCount }}</div>
|
||||
<div class="stat-desc">当前激活通道</div>
|
||||
</div>
|
||||
|
||||
<div class="stat">
|
||||
<div class="stat-title">采样率</div>
|
||||
<div class="stat-value text-info">100MHz</div>
|
||||
<div class="stat-desc">最大采样频率</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 通道配置列表 -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center justify-between p-2 bg-base-300 rounded-lg">
|
||||
<span class="font-medium">通道</span>
|
||||
<span class="font-medium">启用</span>
|
||||
<span class="font-medium">标签</span>
|
||||
<span class="font-medium">颜色</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="(channel, index) in channels"
|
||||
:key="index"
|
||||
class="flex items-center justify-between p-3 bg-base-200 rounded-lg hover:bg-base-300 transition-colors"
|
||||
>
|
||||
<!-- 通道编号 -->
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-mono font-medium w-12">CH{{ index }}</span>
|
||||
<div
|
||||
class="w-3 h-3 rounded-full border-2 border-white shadow-sm"
|
||||
:style="{ backgroundColor: channel.color }"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<!-- 启用开关 -->
|
||||
<div class="form-control">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="channel.enabled"
|
||||
class="toggle toggle-sm toggle-primary"
|
||||
@change="updateChannelStatus(index)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 通道标签 -->
|
||||
<div class="form-control w-32">
|
||||
<input
|
||||
type="text"
|
||||
v-model="channel.label"
|
||||
:placeholder="`通道 ${index}`"
|
||||
class="input input-sm input-bordered w-full"
|
||||
:disabled="!channel.enabled"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 颜色选择 -->
|
||||
<div class="form-control">
|
||||
<input
|
||||
type="color"
|
||||
v-model="channel.color"
|
||||
class="w-8 h-8 rounded border-2 border-base-300 cursor-pointer"
|
||||
:disabled="!channel.enabled"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 批量操作 -->
|
||||
<div class="flex gap-2 pt-4">
|
||||
<button @click="enableAllChannels" class="btn btn-primary btn-sm">
|
||||
全部启用
|
||||
</button>
|
||||
|
||||
<button @click="disableAllChannels" class="btn btn-outline btn-sm">
|
||||
全部禁用
|
||||
</button>
|
||||
|
||||
<button @click="resetChannelLabels" class="btn btn-outline btn-sm">
|
||||
重置标签
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, reactive } from "vue";
|
||||
|
||||
// 通道接口定义
|
||||
interface Channel {
|
||||
enabled: boolean;
|
||||
label: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
// 预设配置接口
|
||||
interface Preset {
|
||||
name: string;
|
||||
description: string;
|
||||
channels: Partial<Channel>[];
|
||||
}
|
||||
|
||||
// 默认颜色数组
|
||||
const defaultColors = [
|
||||
"#FF5733",
|
||||
"#33FF57",
|
||||
"#3357FF",
|
||||
"#FF33F5",
|
||||
"#F5FF33",
|
||||
"#33FFF5",
|
||||
"#FF8C33",
|
||||
"#8C33FF",
|
||||
];
|
||||
|
||||
// 通道配置
|
||||
const channels = reactive<Channel[]>(
|
||||
Array.from({ length: 8 }, (_, index) => ({
|
||||
enabled: false,
|
||||
label: `CH${index}`,
|
||||
color: defaultColors[index],
|
||||
})),
|
||||
);
|
||||
|
||||
// 计算启用的通道数量
|
||||
const enabledChannelCount = computed(
|
||||
() => channels.filter((channel) => channel.enabled).length,
|
||||
);
|
||||
|
||||
// 更新通道状态
|
||||
const updateChannelStatus = (index: number) => {
|
||||
console.log(`通道 ${index} 状态更新:`, channels[index].enabled);
|
||||
};
|
||||
|
||||
// 启用所有通道
|
||||
const enableAllChannels = () => {
|
||||
channels.forEach((channel) => {
|
||||
channel.enabled = true;
|
||||
});
|
||||
};
|
||||
|
||||
// 禁用所有通道
|
||||
const disableAllChannels = () => {
|
||||
channels.forEach((channel) => {
|
||||
channel.enabled = false;
|
||||
});
|
||||
};
|
||||
|
||||
// 重置通道标签
|
||||
const resetChannelLabels = () => {
|
||||
channels.forEach((channel, index) => {
|
||||
channel.label = `CH${index}`;
|
||||
});
|
||||
};
|
||||
|
||||
// 应用预设配置
|
||||
const applyPreset = (preset: Preset) => {
|
||||
preset.channels.forEach((presetChannel, index) => {
|
||||
if (index < channels.length && presetChannel) {
|
||||
Object.assign(channels[index], presetChannel);
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,210 @@
|
|||
<template>
|
||||
<div class="w-full h-150">
|
||||
<v-chart
|
||||
v-if="data"
|
||||
class="w-full h-full"
|
||||
:option="option"
|
||||
autoresize
|
||||
:update-options="updateOptions"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="w-full h-full flex items-center justify-center text-gray-500"
|
||||
>
|
||||
暂无数据
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, shallowRef } from "vue";
|
||||
import VChart from "vue-echarts";
|
||||
import type { LogicDataType } from "./index";
|
||||
|
||||
// Echarts
|
||||
import { use } from "echarts/core";
|
||||
import { LineChart } from "echarts/charts";
|
||||
import {
|
||||
TooltipComponent,
|
||||
GridComponent,
|
||||
DataZoomComponent,
|
||||
AxisPointerComponent,
|
||||
ToolboxComponent,
|
||||
} from "echarts/components";
|
||||
import { CanvasRenderer } from "echarts/renderers";
|
||||
import type { ComposeOption } from "echarts/core";
|
||||
import type { LineSeriesOption } from "echarts/charts";
|
||||
import type {
|
||||
AxisPointerComponentOption,
|
||||
TooltipComponentOption,
|
||||
GridComponentOption,
|
||||
DataZoomComponentOption,
|
||||
} from "echarts/components";
|
||||
import type {
|
||||
ToolboxComponentOption,
|
||||
XAXisOption,
|
||||
YAXisOption,
|
||||
} from "echarts/types/dist/shared";
|
||||
|
||||
use([
|
||||
TooltipComponent,
|
||||
ToolboxComponent,
|
||||
GridComponent,
|
||||
AxisPointerComponent,
|
||||
DataZoomComponent,
|
||||
LineChart,
|
||||
CanvasRenderer,
|
||||
]);
|
||||
|
||||
type EChartsOption = ComposeOption<
|
||||
| AxisPointerComponentOption
|
||||
| TooltipComponentOption
|
||||
| ToolboxComponentOption
|
||||
| GridComponentOption
|
||||
| DataZoomComponentOption
|
||||
| LineSeriesOption
|
||||
>;
|
||||
|
||||
// Define props
|
||||
interface Props {
|
||||
data?: LogicDataType;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
// 添加更新选项来减少重绘
|
||||
const updateOptions = shallowRef({
|
||||
notMerge: false,
|
||||
lazyUpdate: true,
|
||||
silent: false
|
||||
});
|
||||
|
||||
const option = computed((): EChartsOption => {
|
||||
if (!props.data) return {};
|
||||
|
||||
const channelCount = props.data.y.length;
|
||||
const channelSpacing = 2; // 每个通道之间的间距
|
||||
|
||||
// 使用单个网格
|
||||
const grids: GridComponentOption[] = [
|
||||
{
|
||||
left: "5%",
|
||||
right: "5%",
|
||||
top: "5%",
|
||||
bottom: "15%",
|
||||
},
|
||||
];
|
||||
|
||||
// 单个X轴
|
||||
const xAxis: XAXisOption[] = [
|
||||
{
|
||||
type: "category",
|
||||
boundaryGap: false,
|
||||
data: props.data!.x.map((x) => x.toFixed(3)),
|
||||
axisLabel: {
|
||||
formatter: (value: string) => `${value}${props.data!.xUnit}`,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// 单个Y轴,范围根据通道数量调整
|
||||
const yAxis: YAXisOption[] = [
|
||||
{
|
||||
type: "value",
|
||||
min: -0.5,
|
||||
max: channelCount * channelSpacing - 0.5,
|
||||
interval: channelSpacing,
|
||||
axisLabel: {
|
||||
formatter: (value: number) => {
|
||||
const channelIndex = Math.round(value / channelSpacing);
|
||||
return channelIndex < channelCount
|
||||
? props.data!.channelNames[channelIndex]
|
||||
: "";
|
||||
},
|
||||
},
|
||||
splitLine: { show: false },
|
||||
},
|
||||
];
|
||||
|
||||
// 创建系列数据,每个通道有不同的Y偏移
|
||||
const series: LineSeriesOption[] = props.data.y.map((channelData, index) => ({
|
||||
name: props.data!.channelNames[index],
|
||||
type: "line",
|
||||
data: channelData.map((value) => value + index * channelSpacing + 0.2),
|
||||
step: "end",
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0.3,
|
||||
origin: index * channelSpacing,
|
||||
},
|
||||
symbol: "none",
|
||||
// 优化性能配置
|
||||
sampling: "lttb",
|
||||
// large: true,
|
||||
// largeThreshold: 2000,
|
||||
// progressive: 2000,
|
||||
// 减少动画以避免闪烁
|
||||
animation: false,
|
||||
}));
|
||||
|
||||
return {
|
||||
// 全局动画配置
|
||||
animation: false,
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "line",
|
||||
label: {
|
||||
backgroundColor: "#6a7985",
|
||||
},
|
||||
// 减少axisPointer的动画
|
||||
animation: false,
|
||||
},
|
||||
formatter: (params: any) => {
|
||||
if (Array.isArray(params) && params.length > 0) {
|
||||
const timeValue = props.data!.x[params[0].dataIndex];
|
||||
const dataIndex = params[0].dataIndex;
|
||||
|
||||
let tooltip = `Time: ${timeValue.toFixed(3)}${props.data!.xUnit}<br/>`;
|
||||
|
||||
// 显示所有通道在当前时间点的原始数值(0或1)
|
||||
props.data!.channelNames.forEach((channelName, index) => {
|
||||
const originalValue = props.data!.y[index][dataIndex];
|
||||
tooltip += `${channelName}: ${originalValue}<br/>`;
|
||||
});
|
||||
|
||||
return tooltip;
|
||||
}
|
||||
return "";
|
||||
},
|
||||
// 优化tooltip性能
|
||||
hideDelay: 100,
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
restore: {},
|
||||
},
|
||||
},
|
||||
grid: grids,
|
||||
xAxis: xAxis,
|
||||
yAxis: yAxis,
|
||||
dataZoom: [
|
||||
{
|
||||
show: true,
|
||||
realtime: true,
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
type: "inside",
|
||||
realtime: true,
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: series,
|
||||
};
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,266 @@
|
|||
<template>
|
||||
<div class="space-y-4">
|
||||
<!-- 全局触发模式配置 -->
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text font-medium">全局触发模式</span>
|
||||
</label>
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-2">
|
||||
<button
|
||||
v-for="mode in globalModes"
|
||||
:key="mode.value"
|
||||
@click="setGlobalMode(mode.value)"
|
||||
:class="[
|
||||
'btn btn-sm',
|
||||
currentGlobalMode === mode.value ? 'btn-primary' : 'btn-outline',
|
||||
]"
|
||||
>
|
||||
{{ mode.label }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="label">
|
||||
<span class="label-text-alt text-gray-500">
|
||||
选择多路信号触发条件的逻辑组合方式
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 信号触发配置 -->
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text font-medium">信号触发配置</span>
|
||||
</label>
|
||||
<div class="space-y-2">
|
||||
<div
|
||||
v-for="(signal, index) in signalConfigs"
|
||||
:key="index"
|
||||
class="flex items-center gap-2 p-3 bg-base-200 rounded-lg"
|
||||
>
|
||||
<span class="text-sm font-medium w-16">CH{{ index }}</span>
|
||||
|
||||
<!-- 操作符选择 -->
|
||||
<select
|
||||
v-model="signal.operator"
|
||||
class="select select-sm select-bordered w-32"
|
||||
>
|
||||
<option v-for="op in operators" :key="op.value" :value="op.value">
|
||||
{{ op.label }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<!-- 信号值选择 -->
|
||||
<select
|
||||
v-model="signal.value"
|
||||
class="select select-sm select-bordered w-32"
|
||||
>
|
||||
<option
|
||||
v-for="val in signalValues"
|
||||
:key="val.value"
|
||||
:value="val.value"
|
||||
>
|
||||
{{ val.label }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<!-- 启用/禁用开关 -->
|
||||
<div class="form-control">
|
||||
<label class="label cursor-pointer gap-2">
|
||||
<span class="label-text text-sm">启用</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="signal.enabled"
|
||||
class="toggle toggle-sm toggle-primary"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="flex gap-2 pt-4">
|
||||
<button
|
||||
@click="applyConfiguration"
|
||||
:disabled="isApplying"
|
||||
class="btn btn-primary btn-sm"
|
||||
>
|
||||
<span
|
||||
v-if="isApplying"
|
||||
class="loading loading-spinner loading-sm"
|
||||
></span>
|
||||
应用配置
|
||||
</button>
|
||||
|
||||
<button @click="resetConfiguration" class="btn btn-outline btn-sm">
|
||||
重置
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 状态指示 -->
|
||||
<div
|
||||
v-if="lastApplyResult"
|
||||
class="alert alert-sm"
|
||||
:class="lastApplyResult.success ? 'alert-success' : 'alert-error'"
|
||||
>
|
||||
<span>{{ lastApplyResult.message }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed } from "vue";
|
||||
import { useEquipments } from "@/stores/equipments";
|
||||
import {
|
||||
CaptureConfig,
|
||||
LogicAnalyzerClient,
|
||||
GlobalCaptureMode,
|
||||
SignalOperator,
|
||||
SignalValue,
|
||||
type SignalTriggerConfig,
|
||||
} from "@/APIClient";
|
||||
import { AuthManager } from "@/utils/AuthManager";
|
||||
|
||||
// 使用设备配置
|
||||
const equipments = useEquipments();
|
||||
|
||||
// 当前全局模式
|
||||
const currentGlobalMode = ref<GlobalCaptureMode>(GlobalCaptureMode.AND);
|
||||
|
||||
// 加载状态
|
||||
const isApplying = ref(false);
|
||||
const lastApplyResult = ref<{ success: boolean; message: string } | null>(null);
|
||||
|
||||
// 全局模式选项
|
||||
const globalModes = [
|
||||
{
|
||||
value: GlobalCaptureMode.AND,
|
||||
label: "AND",
|
||||
description: "所有条件都满足时触发",
|
||||
},
|
||||
{
|
||||
value: GlobalCaptureMode.OR,
|
||||
label: "OR",
|
||||
description: "任一条件满足时触发",
|
||||
},
|
||||
{ value: GlobalCaptureMode.NAND, label: "NAND", description: "AND的非" },
|
||||
{ value: GlobalCaptureMode.NOR, label: "NOR", description: "OR的非" },
|
||||
];
|
||||
|
||||
// 操作符选项
|
||||
const operators = [
|
||||
{ value: SignalOperator.Equal, label: "=" },
|
||||
{ value: SignalOperator.NotEqual, label: "≠" },
|
||||
{ value: SignalOperator.LessThan, label: "<" },
|
||||
{ value: SignalOperator.LessThanOrEqual, label: "≤" },
|
||||
{ value: SignalOperator.GreaterThan, label: ">" },
|
||||
{ value: SignalOperator.GreaterThanOrEqual, label: "≥" },
|
||||
];
|
||||
|
||||
// 信号值选项
|
||||
const signalValues = [
|
||||
{ value: SignalValue.Logic0, label: "0" },
|
||||
{ value: SignalValue.Logic1, label: "1" },
|
||||
{ value: SignalValue.NotCare, label: "X" },
|
||||
{ value: SignalValue.Rise, label: "↑" },
|
||||
{ value: SignalValue.Fall, label: "↓" },
|
||||
{ value: SignalValue.RiseOrFall, label: "↕" },
|
||||
{ value: SignalValue.NoChange, label: "—" },
|
||||
{ value: SignalValue.SomeNumber, label: "#" },
|
||||
];
|
||||
|
||||
// 8个信号通道的配置
|
||||
const signalConfigs = reactive(
|
||||
Array.from({ length: 8 }, (_, index) => ({
|
||||
signalIndex: index,
|
||||
operator: SignalOperator.Equal,
|
||||
value: SignalValue.Logic1,
|
||||
enabled: false,
|
||||
})),
|
||||
);
|
||||
|
||||
// 设置全局触发模式
|
||||
const setGlobalMode = async (mode: GlobalCaptureMode) => {
|
||||
try {
|
||||
const client = AuthManager.createAuthenticatedLogicAnalyzerClient();
|
||||
const success = await client.setGlobalTrigMode(mode);
|
||||
|
||||
if (success) {
|
||||
currentGlobalMode.value = mode;
|
||||
lastApplyResult.value = {
|
||||
success: true,
|
||||
message: `全局触发模式已设置为 ${globalModes.find((m) => m.value === mode)?.label}`,
|
||||
};
|
||||
} else {
|
||||
throw new Error("设置失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("设置全局触发模式失败:", error);
|
||||
lastApplyResult.value = {
|
||||
success: false,
|
||||
message: "设置全局触发模式失败",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// 应用完整配置
|
||||
const applyConfiguration = async () => {
|
||||
isApplying.value = true;
|
||||
lastApplyResult.value = null;
|
||||
|
||||
try {
|
||||
const client = AuthManager.createAuthenticatedLogicAnalyzerClient();
|
||||
|
||||
// 准备配置数据
|
||||
const enabledSignals = signalConfigs.filter((signal) => signal.enabled);
|
||||
const config = new CaptureConfig({
|
||||
globalMode: currentGlobalMode.value,
|
||||
signalConfigs: enabledSignals.map(
|
||||
(signal) =>
|
||||
({
|
||||
signalIndex: signal.signalIndex,
|
||||
operator: signal.operator,
|
||||
value: signal.value,
|
||||
}) as SignalTriggerConfig,
|
||||
),
|
||||
});
|
||||
|
||||
// 发送配置
|
||||
const success = await client.configureCapture(config);
|
||||
|
||||
if (success) {
|
||||
lastApplyResult.value = {
|
||||
success: true,
|
||||
message: `配置已成功应用,启用了 ${enabledSignals.length} 个通道`,
|
||||
};
|
||||
} else {
|
||||
throw new Error("应用配置失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("应用配置失败:", error);
|
||||
lastApplyResult.value = {
|
||||
success: false,
|
||||
message: "应用配置失败,请检查设备连接",
|
||||
};
|
||||
} finally {
|
||||
isApplying.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 重置配置
|
||||
const resetConfiguration = () => {
|
||||
currentGlobalMode.value = GlobalCaptureMode.AND;
|
||||
signalConfigs.forEach((signal) => {
|
||||
signal.operator = SignalOperator.Equal;
|
||||
signal.value = SignalValue.Logic1;
|
||||
signal.enabled = false;
|
||||
});
|
||||
lastApplyResult.value = null;
|
||||
};
|
||||
|
||||
// 清除状态消息
|
||||
setTimeout(() => {
|
||||
if (lastApplyResult.value) {
|
||||
lastApplyResult.value = null;
|
||||
}
|
||||
}, 5000);
|
||||
</script>
|
|
@ -0,0 +1,78 @@
|
|||
import LogicalWaveFormDisplay from "./LogicalWaveFormDisplay.vue";
|
||||
|
||||
type LogicDataType = {
|
||||
x: number[];
|
||||
y: number[][]; // 8 channels of digital data (0 or 1)
|
||||
xUnit: "s" | "ms" | "us" | "ns";
|
||||
channelNames: string[];
|
||||
};
|
||||
|
||||
// Test data generator for 8-channel digital signals
|
||||
function generateTestLogicData(): LogicDataType {
|
||||
const sampleRate = 10000; // 10kHz sampling
|
||||
const duration = 1;
|
||||
const points = Math.floor(sampleRate * duration);
|
||||
|
||||
const x = Array.from({ length: points }, (_, i) => (i / sampleRate) * 1000); // time in ms
|
||||
|
||||
// Generate 8 channels with different digital patterns
|
||||
const y = [
|
||||
// Channel 0: Clock signal 100Hz
|
||||
Array.from(
|
||||
{ length: points },
|
||||
(_, i) => Math.floor((100 * i) / sampleRate) % 2,
|
||||
),
|
||||
// Channel 1: Clock/2 signal 50Hz
|
||||
Array.from(
|
||||
{ length: points },
|
||||
(_, i) => Math.floor((50 * i) / sampleRate) % 2,
|
||||
),
|
||||
// Channel 2: Clock/4 signal 25Hz
|
||||
Array.from(
|
||||
{ length: points },
|
||||
(_, i) => Math.floor((25 * i) / sampleRate) % 2,
|
||||
),
|
||||
// Channel 3: Clock/8 signal 12.5Hz
|
||||
Array.from(
|
||||
{ length: points },
|
||||
(_, i) => Math.floor((12.5 * i) / sampleRate) % 2,
|
||||
),
|
||||
// Channel 4: Data signal (pseudo-random pattern)
|
||||
Array.from(
|
||||
{ length: points },
|
||||
(_, i) => Math.abs( Math.floor(Math.sin(i * 0.01) * 10) % 2 ),
|
||||
),
|
||||
// Channel 5: Enable signal (periodic pulse)
|
||||
Array.from(
|
||||
{ length: points },
|
||||
(_, i) => (Math.floor(i / 50) % 10) < 3 ? 1 : 0,
|
||||
),
|
||||
// Channel 6: Reset signal (occasional pulse)
|
||||
Array.from(
|
||||
{ length: points },
|
||||
(_, i) => (Math.floor(i / 200) % 20) === 0 ? 1 : 0,
|
||||
),
|
||||
// Channel 7: Status signal (slow changing)
|
||||
Array.from(
|
||||
{ length: points },
|
||||
(_, i) => Math.floor(i / 1000) % 2,
|
||||
),
|
||||
];
|
||||
|
||||
const channelNames = [
|
||||
"CLK",
|
||||
"CLK/2",
|
||||
"CLK/4",
|
||||
"CLK/8",
|
||||
"PWM",
|
||||
"ENABLE",
|
||||
"RESET",
|
||||
"STATUS"
|
||||
];
|
||||
|
||||
return { x, y, xUnit: "ms", channelNames };
|
||||
}
|
||||
|
||||
export { LogicalWaveFormDisplay, generateTestLogicData, type LogicDataType };
|
||||
export { default as TriggerSettings } from './TriggerSettings.vue'
|
||||
export { default as ChannelConfig } from './ChannelConfig.vue'
|
|
@ -31,7 +31,6 @@ import { CanvasRenderer } from "echarts/renderers";
|
|||
import type { ComposeOption } from "echarts/core";
|
||||
import type { LineSeriesOption } from "echarts/charts";
|
||||
import type {
|
||||
TitleComponentOption,
|
||||
TooltipComponentOption,
|
||||
LegendComponentOption,
|
||||
ToolboxComponentOption,
|
||||
|
@ -40,7 +39,6 @@ import type {
|
|||
} from "echarts/components";
|
||||
|
||||
use([
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent,
|
||||
ToolboxComponent,
|
||||
|
@ -51,7 +49,6 @@ use([
|
|||
]);
|
||||
|
||||
type EChartsOption = ComposeOption<
|
||||
| TitleComponentOption
|
||||
| TooltipComponentOption
|
||||
| LegendComponentOption
|
||||
| ToolboxComponentOption
|
||||
|
|
|
@ -7,7 +7,12 @@
|
|||
<div class="waveform-display">
|
||||
<svg width="100%" height="120" viewBox="0 0 300 120">
|
||||
<rect width="300" height="120" fill="#1a1f25" />
|
||||
<path :d="currentWaveformPath" stroke="lime" stroke-width="2" fill="none" />
|
||||
<path
|
||||
:d="currentWaveformPath"
|
||||
stroke="lime"
|
||||
stroke-width="2"
|
||||
fill="none"
|
||||
/>
|
||||
|
||||
<!-- 频率和相位显示 -->
|
||||
<text x="20" y="25" fill="#0f0" font-size="14">
|
||||
|
@ -16,7 +21,13 @@
|
|||
<text x="200" y="25" fill="#0f0" font-size="14">
|
||||
φ: {{ phase }}°
|
||||
</text>
|
||||
<text x="150" y="110" fill="#0f0" font-size="14" text-anchor="middle">
|
||||
<text
|
||||
x="150"
|
||||
y="110"
|
||||
fill="#0f0"
|
||||
font-size="14"
|
||||
text-anchor="middle"
|
||||
>
|
||||
{{ displayTimebase }}
|
||||
</text>
|
||||
</svg>
|
||||
|
@ -35,10 +46,15 @@
|
|||
|
||||
<!-- 波形选择区 -->
|
||||
<div class="waveform-selector">
|
||||
<div v-for="(name, index) in waveformNames" :key="`wave-${index}`" :class="[
|
||||
<div
|
||||
v-for="(name, index) in waveformNames"
|
||||
:key="`wave-${index}`"
|
||||
:class="[
|
||||
'waveform-option',
|
||||
{ active: currentWaveformIndex === index },
|
||||
]" @click="selectWaveform(index)">
|
||||
]"
|
||||
@click="selectWaveform(index)"
|
||||
>
|
||||
{{ name }}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -51,8 +67,13 @@
|
|||
<button class="control-button" @click="decreaseFrequency">
|
||||
-
|
||||
</button>
|
||||
<input v-model="frequencyInput" @blur="applyFrequencyInput" @keyup.enter="applyFrequencyInput"
|
||||
class="control-input" type="text" />
|
||||
<input
|
||||
v-model="frequencyInput"
|
||||
@blur="applyFrequencyInput"
|
||||
@keyup.enter="applyFrequencyInput"
|
||||
class="control-input"
|
||||
type="text"
|
||||
/>
|
||||
<button class="control-button" @click="increaseFrequency">
|
||||
+
|
||||
</button>
|
||||
|
@ -63,8 +84,13 @@
|
|||
<span class="control-label">相位:</span>
|
||||
<div class="control-buttons">
|
||||
<button class="control-button" @click="decreasePhase">-</button>
|
||||
<input v-model="phaseInput" @blur="applyPhaseInput" @keyup.enter="applyPhaseInput" class="control-input"
|
||||
type="text" />
|
||||
<input
|
||||
v-model="phaseInput"
|
||||
@blur="applyPhaseInput"
|
||||
@keyup.enter="applyPhaseInput"
|
||||
class="control-input"
|
||||
type="text"
|
||||
/>
|
||||
<button class="control-button" @click="increasePhase">+</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -75,9 +101,12 @@
|
|||
<div class="section-heading">自定义波形</div>
|
||||
<div class="input-group">
|
||||
<label class="input-label">函数表达式:</label>
|
||||
<input v-model="customWaveformExpression" class="function-input"
|
||||
<input
|
||||
v-model="customWaveformExpression"
|
||||
class="function-input"
|
||||
placeholder="例如: sin(t) 或 x^(2/3)+0.9*sqrt(3.3-x^2)*sin(a*PI*x) [a=7.8]"
|
||||
@keyup.enter="applyCustomWaveform" />
|
||||
@keyup.enter="applyCustomWaveform"
|
||||
/>
|
||||
<button class="apply-button" @click="applyCustomWaveform">
|
||||
应用
|
||||
</button>
|
||||
|
@ -86,17 +115,26 @@
|
|||
<div class="example-functions">
|
||||
<div class="example-label">示例函数:</div>
|
||||
<div class="example-buttons">
|
||||
<button class="example-button" @click="applyExampleFunction('sin(t)')">
|
||||
<button
|
||||
class="example-button"
|
||||
@click="applyExampleFunction('sin(t)')"
|
||||
>
|
||||
正弦波
|
||||
</button>
|
||||
<button class="example-button" @click="applyExampleFunction('sin(t)^3')">
|
||||
<button
|
||||
class="example-button"
|
||||
@click="applyExampleFunction('sin(t)^3')"
|
||||
>
|
||||
立方正弦
|
||||
</button>
|
||||
<button class="example-button" @click="
|
||||
<button
|
||||
class="example-button"
|
||||
@click="
|
||||
applyExampleFunction(
|
||||
'((x)^(2/3)+0.9*sqrt(3.3-(x)^2)*sin(10*PI*(x)))*0.75',
|
||||
)
|
||||
">
|
||||
"
|
||||
>
|
||||
心形函数
|
||||
</button>
|
||||
</div>
|
||||
|
@ -105,8 +143,16 @@
|
|||
<div class="drawing-area">
|
||||
<div class="section-heading">波形绘制</div>
|
||||
<div class="waveform-canvas-container" ref="canvasContainer">
|
||||
<canvas ref="drawingCanvas" class="drawing-canvas" width="280" height="100" @mousedown="startDrawing"
|
||||
@mousemove="draw" @mouseup="stopDrawing" @mouseleave="stopDrawing"></canvas>
|
||||
<canvas
|
||||
ref="drawingCanvas"
|
||||
class="drawing-canvas"
|
||||
width="280"
|
||||
height="100"
|
||||
@mousedown="startDrawing"
|
||||
@mousemove="draw"
|
||||
@mouseup="stopDrawing"
|
||||
@mouseleave="stopDrawing"
|
||||
></canvas>
|
||||
<div class="canvas-actions">
|
||||
<button class="canvas-button" @click="clearCanvas">
|
||||
清除
|
||||
|
@ -123,19 +169,30 @@
|
|||
<div class="saved-waveforms">
|
||||
<div class="section-heading">波形存储槽</div>
|
||||
<div class="slot-container">
|
||||
<div v-for="(slot, index) in waveformSlots" :key="`slot-${index}`"
|
||||
:class="['waveform-slot', { empty: !slot.name }]" @click="loadWaveformSlot(index)">
|
||||
<div
|
||||
v-for="(slot, index) in waveformSlots"
|
||||
:key="`slot-${index}`"
|
||||
:class="['waveform-slot', { empty: !slot.name }]"
|
||||
@click="loadWaveformSlot(index)"
|
||||
>
|
||||
<span class="slot-name">{{
|
||||
slot.name || `槽 ${index + 1}`
|
||||
}}</span>
|
||||
<button class="save-button" @click.stop="saveCurrentToSlot(index)">
|
||||
<button
|
||||
class="save-button"
|
||||
@click.stop="saveCurrentToSlot(index)"
|
||||
>
|
||||
保存
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary text-primary-content w-full" :disabled="isApplying" @click="applyOutputWave">
|
||||
<button
|
||||
class="btn btn-primary text-primary-content w-full"
|
||||
:disabled="isApplying"
|
||||
@click="applyOutputWave"
|
||||
>
|
||||
<div v-if="isApplying">
|
||||
<span class="loading loading-spinner"></span>
|
||||
应用中...
|
||||
|
@ -151,10 +208,10 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onMounted } from "vue";
|
||||
import CollapsibleSection from "../CollapsibleSection.vue";
|
||||
import { DDSClient } from "@/APIClient";
|
||||
import { useEquipments } from "@/stores/equipments";
|
||||
import { useDialogStore } from "@/stores/dialog";
|
||||
import { toInteger } from "lodash";
|
||||
import { AuthManager } from "@/utils/AuthManager";
|
||||
|
||||
// Component Attributes
|
||||
const props = defineProps<{
|
||||
|
@ -164,7 +221,7 @@ const props = defineProps<{
|
|||
const emit = defineEmits(["update:modelValue"]);
|
||||
|
||||
// Global varibles
|
||||
const dds = new DDSClient();
|
||||
const dds = AuthManager.createAuthenticatedDDSClient();
|
||||
const eqps = useEquipments();
|
||||
const dialog = useDialogStore();
|
||||
|
||||
|
|
|
@ -1,42 +1,55 @@
|
|||
import { ref, reactive, watchPostEffect } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import { useLocalStorage } from '@vueuse/core'
|
||||
import { isString, toNumber } from 'lodash';
|
||||
import { Common } from '@/utils/Common';
|
||||
import z from "zod"
|
||||
import { isNumber } from 'mathjs';
|
||||
import { ref, reactive, watchPostEffect } from "vue";
|
||||
import { defineStore } from "pinia";
|
||||
import { useLocalStorage } from "@vueuse/core";
|
||||
import { isString, toNumber } from "lodash";
|
||||
import z from "zod";
|
||||
import { isNumber } from "mathjs";
|
||||
import { JtagClient, MatrixKeyClient, PowerClient } from "@/APIClient";
|
||||
import { Mutex, withTimeout } from 'async-mutex';
|
||||
import { Mutex, withTimeout } from "async-mutex";
|
||||
import { useConstraintsStore } from "@/stores/constraints";
|
||||
import { useDialogStore } from './dialog';
|
||||
import { useDialogStore } from "./dialog";
|
||||
import { toFileParameterOrUndefined } from "@/utils/Common";
|
||||
import { AuthManager } from "@/utils/AuthManager";
|
||||
|
||||
export const useEquipments = defineStore('equipments', () => {
|
||||
export const useEquipments = defineStore("equipments", () => {
|
||||
// Global Stores
|
||||
const constrainsts = useConstraintsStore();
|
||||
const dialog = useDialogStore();
|
||||
|
||||
const boardAddr = useLocalStorage('fpga-board-addr', "127.0.0.1");
|
||||
const boardPort = useLocalStorage('fpga-board-port', 1234);
|
||||
const boardAddr = useLocalStorage("fpga-board-addr", "127.0.0.1");
|
||||
const boardPort = useLocalStorage("fpga-board-port", 1234);
|
||||
|
||||
// Jtag
|
||||
const jtagBitstream = ref<File>();
|
||||
const jtagBoundaryScanFreq = ref(100);
|
||||
const jtagClientMutex = withTimeout(new Mutex(), 1000, new Error("JtagClient Mutex Timeout!"))
|
||||
const jtagClient = new JtagClient();
|
||||
const jtagClientMutex = withTimeout(
|
||||
new Mutex(),
|
||||
1000,
|
||||
new Error("JtagClient Mutex Timeout!"),
|
||||
);
|
||||
const jtagClient = AuthManager.createAuthenticatedJtagClient();
|
||||
|
||||
// Matrix Key
|
||||
const matrixKeyStates = reactive(new Array<boolean>(16).fill(false))
|
||||
const matrixKeypadClientMutex = withTimeout(new Mutex(), 1000, new Error("Matrixkeyclient Mutex Timeout!"));
|
||||
const matrixKeypadClient = new MatrixKeyClient();
|
||||
const matrixKeyStates = reactive(new Array<boolean>(16).fill(false));
|
||||
const matrixKeypadClientMutex = withTimeout(
|
||||
new Mutex(),
|
||||
1000,
|
||||
new Error("Matrixkeyclient Mutex Timeout!"),
|
||||
);
|
||||
const matrixKeypadClient = AuthManager.createAuthenticatedMatrixKeyClient();
|
||||
|
||||
// Power
|
||||
const powerClientMutex = withTimeout(new Mutex(), 1000, new Error("Matrixkeyclient Mutex Timeout!"));
|
||||
const powerClient = new PowerClient();
|
||||
const powerClientMutex = withTimeout(
|
||||
new Mutex(),
|
||||
1000,
|
||||
new Error("Matrixkeyclient Mutex Timeout!"),
|
||||
);
|
||||
const powerClient = AuthManager.createAuthenticatedPowerClient();
|
||||
|
||||
// Enable Setting
|
||||
const enableJtagBoundaryScan = ref(false);
|
||||
const enableMatrixKey = ref(false);
|
||||
const enablePower = ref(false)
|
||||
const enablePower = ref(false);
|
||||
|
||||
// Watch
|
||||
watchPostEffect(async () => {
|
||||
|
@ -60,8 +73,7 @@ export const useEquipments = defineStore('equipments', () => {
|
|||
boardPort.value = portNumber;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (isNumber(port)) {
|
||||
} else if (isNumber(port)) {
|
||||
if (z.number().nonnegative().max(65535).safeParse(port).success) {
|
||||
boardPort.value = port;
|
||||
return true;
|
||||
|
@ -70,7 +82,10 @@ export const useEquipments = defineStore('equipments', () => {
|
|||
return false;
|
||||
}
|
||||
|
||||
function setMatrixKey(keyNum: number | string | undefined, keyValue: boolean): boolean {
|
||||
function setMatrixKey(
|
||||
keyNum: number | string | undefined,
|
||||
keyValue: boolean,
|
||||
): boolean {
|
||||
let _keyNum: number;
|
||||
if (isString(keyNum)) {
|
||||
_keyNum = toNumber(keyNum);
|
||||
|
@ -112,7 +127,7 @@ export const useEquipments = defineStore('equipments', () => {
|
|||
try {
|
||||
const resp = await jtagClient.uploadBitstream(
|
||||
boardAddr.value,
|
||||
Common.toFileParameterOrNull(bitstream),
|
||||
toFileParameterOrUndefined(bitstream),
|
||||
);
|
||||
return resp;
|
||||
} catch (e) {
|
||||
|
@ -127,7 +142,7 @@ export const useEquipments = defineStore('equipments', () => {
|
|||
try {
|
||||
const resp = await jtagClient.downloadBitstream(
|
||||
boardAddr.value,
|
||||
boardPort.value
|
||||
boardPort.value,
|
||||
);
|
||||
return resp;
|
||||
} catch (e) {
|
||||
|
@ -144,7 +159,7 @@ export const useEquipments = defineStore('equipments', () => {
|
|||
try {
|
||||
const resp = await jtagClient.getDeviceIDCode(
|
||||
boardAddr.value,
|
||||
boardPort.value
|
||||
boardPort.value,
|
||||
);
|
||||
return resp;
|
||||
} catch (e) {
|
||||
|
@ -161,7 +176,7 @@ export const useEquipments = defineStore('equipments', () => {
|
|||
const resp = await jtagClient.setSpeed(
|
||||
boardAddr.value,
|
||||
boardPort.value,
|
||||
speed
|
||||
speed,
|
||||
);
|
||||
return resp;
|
||||
} catch (e) {
|
||||
|
@ -179,7 +194,7 @@ export const useEquipments = defineStore('equipments', () => {
|
|||
const resp = await matrixKeypadClient.setMatrixKeyStatus(
|
||||
boardAddr.value,
|
||||
boardPort.value,
|
||||
keyStates
|
||||
keyStates,
|
||||
);
|
||||
return resp;
|
||||
} catch (e) {
|
||||
|
@ -223,7 +238,7 @@ export const useEquipments = defineStore('equipments', () => {
|
|||
const resp = await powerClient.setPowerOnOff(
|
||||
boardAddr.value,
|
||||
boardPort.value,
|
||||
enable
|
||||
enable,
|
||||
);
|
||||
return resp;
|
||||
} catch (e) {
|
||||
|
@ -266,6 +281,5 @@ export const useEquipments = defineStore('equipments', () => {
|
|||
powerClient,
|
||||
powerClientMutex,
|
||||
powerSetOnOff,
|
||||
}
|
||||
})
|
||||
|
||||
};
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
RemoteUpdateClient,
|
||||
TutorialClient,
|
||||
UDPClient,
|
||||
LogicAnalyzerClient,
|
||||
} from "@/APIClient";
|
||||
|
||||
// 支持的客户端类型联合类型
|
||||
|
@ -22,6 +23,7 @@ type SupportedClient =
|
|||
| PowerClient
|
||||
| RemoteUpdateClient
|
||||
| TutorialClient
|
||||
| LogicAnalyzerClient
|
||||
| UDPClient;
|
||||
|
||||
export class AuthManager {
|
||||
|
@ -151,6 +153,10 @@ export class AuthManager {
|
|||
return AuthManager.createAuthenticatedClient(UDPClient);
|
||||
}
|
||||
|
||||
public static createAuthenticatedLogicAnalyzerClient(): LogicAnalyzerClient {
|
||||
return AuthManager.createAuthenticatedClient(LogicAnalyzerClient);
|
||||
}
|
||||
|
||||
// 登录函数
|
||||
public static async login(
|
||||
username: string,
|
||||
|
@ -212,11 +218,7 @@ export class AuthManager {
|
|||
} catch (error) {
|
||||
// 只有在token完全无效的情况下才清除token
|
||||
// 401错误表示token有效但权限不足,不应清除token
|
||||
if (
|
||||
error &&
|
||||
typeof error === "object" &&
|
||||
"status" in error
|
||||
) {
|
||||
if (error && typeof error === "object" && "status" in error) {
|
||||
// 如果是403 (Forbidden) 或401 (Unauthorized),说明token有效但权限不足
|
||||
if (error.status === 401 || error.status === 403) {
|
||||
return false;
|
||||
|
@ -225,7 +227,7 @@ export class AuthManager {
|
|||
AuthManager.clearToken();
|
||||
} else {
|
||||
// 网络错误等,不清除token
|
||||
console.error('管理员权限验证失败:', error);
|
||||
console.error("管理员权限验证失败:", error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { ref } from "vue";
|
||||
import { createInjectionState } from "@vueuse/core";
|
||||
import { RemoteUpdateClient, DataClient, Board } from "@/APIClient";
|
||||
import { Common } from "@/utils/Common";
|
||||
import { isUndefined } from "lodash";
|
||||
import { AuthManager } from "@/utils/AuthManager";
|
||||
import { toFileParameterOrNull } from "./Common";
|
||||
|
||||
// 统一的板卡数据接口,扩展原有的Board类型
|
||||
export interface BoardData extends Board {
|
||||
|
@ -17,7 +17,7 @@ export interface BoardData extends Board {
|
|||
const [useProvideBoardManager, useBoardManager] = createInjectionState(() => {
|
||||
// 远程升级相关参数
|
||||
const devPort = 1234;
|
||||
const remoteUpdater = new RemoteUpdateClient();
|
||||
const remoteUpdater = AuthManager.createAuthenticatedRemoteUpdateClient();
|
||||
|
||||
// 统一的板卡数据
|
||||
const boards = ref<BoardData[]>([]);
|
||||
|
@ -178,10 +178,10 @@ const [useProvideBoardManager, useBoardManager] = createInjectionState(() => {
|
|||
|
||||
const uploadResult = await remoteUpdater.uploadBitstreams(
|
||||
board.ipAddr,
|
||||
Common.toFileParameterOrNull(goldBitstream),
|
||||
Common.toFileParameterOrNull(appBitstream1),
|
||||
Common.toFileParameterOrNull(appBitstream2),
|
||||
Common.toFileParameterOrNull(appBitstream3),
|
||||
toFileParameterOrNull(goldBitstream),
|
||||
toFileParameterOrNull(appBitstream1),
|
||||
toFileParameterOrNull(appBitstream2),
|
||||
toFileParameterOrNull(appBitstream3),
|
||||
);
|
||||
|
||||
if (!uploadResult) {
|
||||
|
|
|
@ -1,22 +1,50 @@
|
|||
import { type FileParameter } from "@/APIClient";
|
||||
import { isNull, isUndefined } from "lodash";
|
||||
|
||||
export namespace Common {
|
||||
export function toFileParameter(object: File): FileParameter {
|
||||
export function toFileParameter(object: File): FileParameter {
|
||||
if (isNull(object) || isUndefined(object))
|
||||
throw new Error("File is Null or Undefined");
|
||||
return {
|
||||
data: object,
|
||||
fileName: object.name
|
||||
}
|
||||
}
|
||||
|
||||
export function toFileParameterOrNull(object?: File | null): FileParameter | null {
|
||||
if (isNull(object) || isUndefined(object)) return null;
|
||||
else return {
|
||||
data: object,
|
||||
fileName: object.name
|
||||
}
|
||||
}
|
||||
|
||||
fileName: object.name,
|
||||
};
|
||||
}
|
||||
|
||||
export function toFileParameterOrNull(
|
||||
object?: File | null,
|
||||
): FileParameter | null {
|
||||
if (isNull(object) || isUndefined(object)) return null;
|
||||
else
|
||||
return {
|
||||
data: object,
|
||||
fileName: object.name,
|
||||
};
|
||||
}
|
||||
|
||||
export function toFileParameterOrUndefined(
|
||||
object?: File | undefined,
|
||||
): FileParameter | undefined {
|
||||
if (isNull(object) || isUndefined(object)) return undefined;
|
||||
else
|
||||
return {
|
||||
data: object,
|
||||
fileName: object.name,
|
||||
};
|
||||
}
|
||||
|
||||
// 自定义 Hook:检查依赖注入值是否为空
|
||||
export function useRequiredInjection<T>(useFn: () => T | undefined): T {
|
||||
const value = useFn();
|
||||
if (value === undefined) {
|
||||
throw new Error("Missing required injection");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function useOptionalInjection<T>(
|
||||
useFn: () => T | undefined,
|
||||
defaultValue: T,
|
||||
): T {
|
||||
const value = useFn();
|
||||
return value ?? defaultValue;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
type="radio"
|
||||
name="function-bar"
|
||||
id="1"
|
||||
checked
|
||||
:checked="checkID === 1"
|
||||
@change="handleTabChange"
|
||||
/>
|
||||
<TerminalIcon class="icon" />
|
||||
|
@ -17,6 +17,7 @@
|
|||
type="radio"
|
||||
name="function-bar"
|
||||
id="2"
|
||||
:checked="checkID === 2"
|
||||
@change="handleTabChange"
|
||||
/>
|
||||
<VideoIcon class="icon" />
|
||||
|
@ -27,6 +28,7 @@
|
|||
type="radio"
|
||||
name="function-bar"
|
||||
id="3"
|
||||
:checked="checkID === 3"
|
||||
@change="handleTabChange"
|
||||
/>
|
||||
<SquareActivityIcon class="icon" />
|
||||
|
@ -37,6 +39,7 @@
|
|||
type="radio"
|
||||
name="function-bar"
|
||||
id="4"
|
||||
:checked="checkID === 4"
|
||||
@change="handleTabChange"
|
||||
/>
|
||||
<Zap class="icon" />
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<Zap class="w-5 h-5" />
|
||||
逻辑信号分析
|
||||
</h2>
|
||||
<LogicalWaveFormDisplay :data="generateTestLogicData()" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -37,6 +38,12 @@
|
|||
<script setup lang="ts">
|
||||
import { Zap, Settings, Layers } from "lucide-vue-next";
|
||||
import { useEquipments } from "@/stores/equipments";
|
||||
import {
|
||||
LogicalWaveFormDisplay,
|
||||
generateTestLogicData,
|
||||
TriggerSettings,
|
||||
ChannelConfig
|
||||
} from "@/components/LogicAnalyzer";
|
||||
|
||||
// 使用全局设备配置
|
||||
const equipments = useEquipments();
|
||||
|
|
Loading…
Reference in New Issue