Compare commits

...

21 Commits

Author SHA1 Message Date
SikongJueluo 81f91b2b71
fix: boundary scan could not close and jtag scan failed 2025-05-20 20:31:52 +08:00
alivender bbfe06822d Merge branch 'master' of ssh://git.swordlost.top:222/SikongJueluo/FPGA_WebLab 2025-05-20 20:19:09 +08:00
alivender d73166187a
Merge branch 'master' of ssh://git.swordlost.top:222/SikongJueluo/FPGA_WebLab 2025-05-20 20:18:58 +08:00
alivender 2eabb79d0f Merge branch 'master' of ssh://git.swordlost.top:222/SikongJueluo/FPGA_WebLab 2025-05-20 20:09:42 +08:00
alivender a865cfc950 fix: 注释掉调试日志以清理控制台输出 2025-05-20 20:09:39 +08:00
SikongJueluo fa7c947351
feat: backend add task id to reduce conflict 2025-05-20 20:08:20 +08:00
SikongJueluo dc64a65702
feat: add power control 2025-05-20 19:11:29 +08:00
SikongJueluo 46621fdb40
fix: jam when doc panel open 2025-05-20 18:34:44 +08:00
SikongJueluo 970a537391
Merge branch 'master' into csharp 2025-05-20 18:14:00 +08:00
SikongJueluo 3883cd8304
fix: matrix key not work 2025-05-20 18:13:52 +08:00
SikongJueluo 2aa2f1dc37
fix: matrix key not work 2025-05-20 18:13:52 +08:00
SikongJueluo 1bdcb672ab
feat: frontend add virtual matrix key 2025-05-20 18:13:52 +08:00
SikongJueluo 7b1d1a5e87
fix: backend change matrix key api to post to fix set failed 2025-05-20 18:13:52 +08:00
SikongJueluo 5bb011e685
feag: backend add matrix key peripheral with its web api 2025-05-20 18:13:52 +08:00
SikongJueluo e3826f0ff6
feat: add logger to udpclient 2025-05-20 18:13:52 +08:00
SikongJueluo f8163be98b
fix: matrix key not work 2025-05-20 18:11:04 +08:00
SikongJueluo e4791b41a8
fix: matrix key not work 2025-05-20 17:23:00 +08:00
SikongJueluo bea1c7e5ae
feat: frontend add virtual matrix key 2025-05-20 17:09:57 +08:00
SikongJueluo b68d8eaf11
fix: backend change matrix key api to post to fix set failed 2025-05-20 11:21:12 +08:00
SikongJueluo 390ce8250d
feag: backend add matrix key peripheral with its web api 2025-05-20 11:14:00 +08:00
SikongJueluo 07eb606324
feat: add logger to udpclient 2025-05-20 09:34:26 +08:00
39 changed files with 3131 additions and 1884 deletions

View File

@ -24,6 +24,9 @@ update:
gen-api: gen-api:
npm run gen-api npm run gen-api
gen-api-from-server:
npx nswag openapi2tsclient /input:http://localhost:5000/swagger/v1/swagger.json /output:src/APIClient.ts
# 构建服务器包含win与linux平台 # 构建服务器包含win与linux平台
[working-directory: "server"] [working-directory: "server"]
build-server self-contained=isSelfContained: _show-dir build-server self-contained=isSelfContained: _show-dir

13
package-lock.json generated
View File

@ -22,6 +22,7 @@
"ts-results-es": "^5.0.1", "ts-results-es": "^5.0.1",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-router": "4", "vue-router": "4",
"yocto-queue": "^1.2.1",
"zod": "^3.24.2" "zod": "^3.24.2"
}, },
"devDependencies": { "devDependencies": {
@ -4075,6 +4076,18 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/yocto-queue": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz",
"integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==",
"license": "MIT",
"engines": {
"node": ">=12.20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/yoctocolors": { "node_modules/yoctocolors": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz",

View File

@ -28,6 +28,7 @@
"ts-results-es": "^5.0.1", "ts-results-es": "^5.0.1",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-router": "4", "vue-router": "4",
"yocto-queue": "^1.2.1",
"zod": "^3.24.2" "zod": "^3.24.2"
}, },
"devDependencies": { "devDependencies": {

View File

@ -0,0 +1,314 @@
{
"version": 1,
"author": "template",
"editor": "system",
"parts": [
{
"id": "board",
"type": "BaseBoard",
"x": 0,
"y": 0,
"attrs": {
"size": 1.2,
"width": 400,
"height": 400,
"roundCorner": 20
},
"rotate": 0,
"group": "MatrixKeypad",
"positionlock": false,
"hidepins": false,
"isOn": true,
"index": 0
},
{
"id": "key_0_0",
"type": "MechanicalButton",
"x": 50,
"y": 50,
"attrs": {
"size": 0.5,
"bindKey": "1",
"bindMatrixKey": "0",
"pins": []
},
"rotate": 0,
"group": "MatrixKeypad",
"positionlock": false,
"hidepins": true,
"isOn": false,
"index": 0
},
{
"id": "key_0_1",
"type": "MechanicalButton",
"x": 150,
"y": 50,
"attrs": {
"size": 0.5,
"bindKey": "2",
"bindMatrixKey": "1",
"pins": []
},
"rotate": 0,
"group": "MatrixKeypad",
"positionlock": false,
"hidepins": true,
"isOn": false,
"index": 1
},
{
"id": "key_0_2",
"type": "MechanicalButton",
"x": 250,
"y": 50,
"attrs": {
"size": 0.5,
"bindKey": "3",
"bindMatrixKey": "2",
"pins": []
},
"rotate": 0,
"group": "MatrixKeypad",
"positionlock": false,
"hidepins": true,
"isOn": false,
"index": 2
},
{
"id": "key_0_3",
"type": "MechanicalButton",
"x": 350,
"y": 50,
"attrs": {
"size": 0.5,
"bindKey": "A",
"bindMatrixKey": "3",
"pins": []
},
"rotate": 0,
"group": "MatrixKeypad",
"positionlock": false,
"hidepins": true,
"isOn": false,
"index": 3
},
{
"id": "key_1_0",
"type": "MechanicalButton",
"x": 50,
"y": 150,
"attrs": {
"size": 0.5,
"bindKey": "4",
"bindMatrixKey": "4",
"pins": []
},
"rotate": 0,
"group": "MatrixKeypad",
"positionlock": false,
"hidepins": true,
"isOn": false,
"index": 4
},
{
"id": "key_1_1",
"type": "MechanicalButton",
"x": 150,
"y": 150,
"attrs": {
"size": 0.5,
"bindKey": "5",
"bindMatrixKey": "5",
"pins": []
},
"rotate": 0,
"group": "MatrixKeypad",
"positionlock": false,
"hidepins": true,
"isOn": false,
"index": 5
},
{
"id": "key_1_2",
"type": "MechanicalButton",
"x": 250,
"y": 150,
"attrs": {
"size": 0.5,
"bindKey": "6",
"bindMatrixKey": "6",
"pins": []
},
"rotate": 0,
"group": "MatrixKeypad",
"positionlock": false,
"hidepins": true,
"isOn": false,
"index": 6
},
{
"id": "key_1_3",
"type": "MechanicalButton",
"x": 350,
"y": 150,
"attrs": {
"size": 0.5,
"bindKey": "B",
"bindMatrixKey": "7",
"pins": []
},
"rotate": 0,
"group": "MatrixKeypad",
"positionlock": false,
"hidepins": true,
"isOn": false,
"index": 7
},
{
"id": "key_2_0",
"type": "MechanicalButton",
"x": 50,
"y": 250,
"attrs": {
"size": 0.5,
"bindKey": "7",
"bindMatrixKey": "8",
"pins": []
},
"rotate": 0,
"group": "MatrixKeypad",
"positionlock": false,
"hidepins": true,
"isOn": false,
"index": 8
},
{
"id": "key_2_1",
"type": "MechanicalButton",
"x": 150,
"y": 250,
"attrs": {
"size": 0.5,
"bindKey": "8",
"bindMatrixKey": "9",
"pins": []
},
"rotate": 0,
"group": "MatrixKeypad",
"positionlock": false,
"hidepins": true,
"isOn": false,
"index": 9
},
{
"id": "key_2_2",
"type": "MechanicalButton",
"x": 250,
"y": 250,
"attrs": {
"size": 0.5,
"bindKey": "9",
"bindMatrixKey": "10",
"pins": []
},
"rotate": 0,
"group": "MatrixKeypad",
"positionlock": false,
"hidepins": true,
"isOn": false,
"index": 10
},
{
"id": "key_2_3",
"type": "MechanicalButton",
"x": 350,
"y": 250,
"attrs": {
"size": 0.5,
"bindKey": "C",
"bindMatrixKey": "11",
"pins": []
},
"rotate": 0,
"group": "MatrixKeypad",
"positionlock": false,
"hidepins": true,
"isOn": false,
"index": 11
},
{
"id": "key_3_0",
"type": "MechanicalButton",
"x": 50,
"y": 350,
"attrs": {
"size": 0.5,
"bindKey": "*",
"bindMatrixKey": "12",
"pins": []
},
"rotate": 0,
"group": "MatrixKeypad",
"positionlock": false,
"hidepins": true,
"isOn": false,
"index": 12
},
{
"id": "key_3_1",
"type": "MechanicalButton",
"x": 150,
"y": 350,
"attrs": {
"size": 0.5,
"bindKey": "0",
"bindMatrixKey": "13",
"pins": []
},
"rotate": 0,
"group": "MatrixKeypad",
"positionlock": false,
"hidepins": true,
"isOn": false,
"index": 13
},
{
"id": "key_3_2",
"type": "MechanicalButton",
"x": 250,
"y": 350,
"attrs": {
"size": 0.5,
"bindKey": "#",
"bindMatrixKey": "14",
"pins": []
},
"rotate": 0,
"group": "MatrixKeypad",
"positionlock": false,
"hidepins": true,
"isOn": false,
"index": 14
},
{
"id": "key_3_3",
"type": "MechanicalButton",
"x": 350,
"y": 350,
"attrs": {
"size": 0.5,
"bindKey": "D",
"bindMatrixKey": "15",
"pins": []
},
"rotate": 0,
"group": "MatrixKeypad",
"positionlock": false,
"hidepins": true,
"isOn": false,
"index": 15
}
],
"connections": []
}

View File

@ -1,3 +1,5 @@
using System.Net;
using System.Net.Sockets;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -52,6 +54,7 @@ try
{ {
options.AddPolicy("Users", policy => policy options.AddPolicy("Users", policy => policy
.AllowAnyOrigin() .AllowAnyOrigin()
.AllowAnyHeader()
); );
}); });

View File

@ -26,7 +26,7 @@ public class BoundaryScanRegs
/// </summary> /// </summary>
[JsonProperty("cell_name")] [JsonProperty("cell_name")]
[JsonRequired] [JsonRequired]
public string CellName { get; set; } public string CellName { get; set; } = "UnknownCellName";
/// <summary> /// <summary>
/// [TODO:description] /// [TODO:description]
@ -146,7 +146,8 @@ public class Parser
/// <returns>[TODO:return]</returns> /// <returns>[TODO:return]</returns>
public Optional<List<BoundaryScanRegs.CellEntry>> GetBoundaryLogicalPorts() public Optional<List<BoundaryScanRegs.CellEntry>> GetBoundaryLogicalPorts()
{ {
var registers = this.BoundaryRegsDesp["registers"]?.ToList().Where((item)=>{ var registers = this.BoundaryRegsDesp["registers"]?.ToList().Where((item) =>
{
return item["port_id"] is not null; return item["port_id"] is not null;
}); });
if (registers is null) return new(); if (registers is null) return new();

View File

@ -1,3 +1,4 @@
using System.Collections;
using DotNext; using DotNext;
namespace Common namespace Common
@ -273,6 +274,21 @@ namespace Common
return ((srcBits >> location) & ((UInt32)0b1)) == 1; return ((srcBits >> location) & ((UInt32)0b1)) == 1;
} }
/// <summary>
/// 将BitArray转化为32bits无符号整型
/// </summary>
/// <param name="bits">BitArray比特数组</param>
/// <returns>32bits无符号整型</returns>
public static Result<UInt32> BitsToNumber(BitArray bits)
{
if (bits.Length > 32)
throw new ArgumentException("Argument length shall be at most 32 bits.");
var array = new UInt32[1];
bits.CopyTo(array, 0);
return array[0];
}
/// <summary> /// <summary>
/// 字符串转二进制字节数组 /// 字符串转二进制字节数组

View File

@ -1,887 +0,0 @@
using System.Net;
using Common;
using DotNext;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using WebProtocol;
namespace server.Controllers;
/// <summary>
/// UDP API
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class UDPController : ControllerBase
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private const string LOCALHOST = "127.0.0.1";
/// <summary>
/// 页面
/// </summary>
[HttpGet]
public string Index()
{
return "This is UDP Controller";
}
/// <summary>
/// 发送字符串
/// </summary>
/// <param name="address">IPV4 或者 IPV6 地址</param>
/// <param name="port">设备端口号</param>
/// <param name="text">发送的文本</param>
/// <response code="200">发送成功</response>
/// <response code="500">发送失败</response>
[HttpPost("SendString")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> SendString(string address = LOCALHOST, int port = 1234, string text = "Hello Server!")
{
var endPoint = new IPEndPoint(IPAddress.Parse(address), port);
var ret = await UDPClientPool.SendStringAsync(endPoint, [text]);
if (ret) { return TypedResults.Ok(); }
else { return TypedResults.InternalServerError(); }
}
/// <summary>
/// 发送二进制数据
/// </summary>
/// <param name="address" example="127.0.0.1">IPV4 或者 IPV6 地址</param>
/// <param name="port" example="1234">设备端口号</param>
/// <param name="bytes" example="FFFFAAAA">16进制文本</param>
[HttpPost("SendBytes")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> SendBytes(string address, int port, string bytes)
{
var endPoint = new IPEndPoint(IPAddress.Parse(address), port);
var ret = await UDPClientPool.SendBytesAsync(endPoint, Number.StringToBytes(bytes));
if (ret) { return TypedResults.Ok(); }
else { return TypedResults.InternalServerError(); }
}
/// <summary>
/// 发送地址包
/// </summary>
/// <param name="address">IP地址</param>
/// <param name="port">UDP 端口号</param>
/// <param name="opts">地址包选项</param>
[HttpPost("SendAddrPackage")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> SendAddrPackage(
string address,
int port,
[FromBody] SendAddrPackOptions opts)
{
var endPoint = new IPEndPoint(IPAddress.Parse(address), port);
var ret = await UDPClientPool.SendAddrPackAsync(endPoint, new WebProtocol.SendAddrPackage(opts));
if (ret) { return TypedResults.Ok(); }
else { return TypedResults.InternalServerError(); }
}
/// <summary>
/// 发送数据包
/// </summary>
/// <param name="address">IP地址</param>
/// <param name="port">UDP 端口号</param>
/// <param name="data">16进制数据</param>
[HttpPost("SendDataPackage")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> SendDataPackage(string address, int port, string data)
{
var endPoint = new IPEndPoint(IPAddress.Parse(address), port);
var ret = await UDPClientPool.SendDataPackAsync(endPoint,
new WebProtocol.SendDataPackage(Number.StringToBytes(data)));
if (ret) { return TypedResults.Ok(); }
else { return TypedResults.InternalServerError(); }
}
/// <summary>
/// 获取指定IP地址接受的数据列表
/// </summary>
/// <param name="address">IP地址</param>
[HttpGet("GetRecvDataArray")]
[ProducesResponseType(typeof(List<UDPData>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> GetRecvDataArray(string address)
{
var ret = await MsgBus.UDPServer.GetDataArrayAsync(address);
if (ret.HasValue)
{
var dataJson = JsonConvert.SerializeObject(ret.Value);
logger.Debug($"Get Receive Successfully: {dataJson}");
return TypedResults.Ok(ret.Value);
}
else
{
logger.Debug("Get Receive Failed");
return TypedResults.InternalServerError();
}
}
}
/// <summary>
/// Jtag API
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class JtagController : ControllerBase
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private const string BITSTREAM_PATH = "bitstream/Jtag";
/// <summary>
/// 页面
/// </summary>
[HttpGet]
public string Index()
{
return "This is Jtag Controller";
}
/// <summary>
/// 获取Jtag ID Code
/// </summary>
/// <param name="address"> 设备地址 </param>
/// <param name="port"> 设备端口 </param>
[HttpGet("GetDeviceIDCode")]
[EnableCors("Users")]
[ProducesResponseType(typeof(uint), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> GetDeviceIDCode(string address, int port)
{
var jtagCtrl = new JtagClient.Jtag(address, port);
var ret = await jtagCtrl.ReadIDCode();
if (ret.IsSuccessful)
{
logger.Info($"Get device {address} ID code: 0x{ret.Value:X4}");
return TypedResults.Ok(ret.Value);
}
else
{
logger.Error(ret.Error);
return TypedResults.InternalServerError(ret.Error);
}
}
/// <summary>
/// 获取状态寄存器
/// </summary>
/// <param name="address"> 设备地址 </param>
/// <param name="port"> 设备端口 </param>
[HttpGet("ReadStatusReg")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> ReadStatusReg(string address, int port)
{
var jtagCtrl = new JtagClient.Jtag(address, port);
var ret = await jtagCtrl.ReadStatusReg();
if (ret.IsSuccessful)
{
var binaryValue = Common.String.Reverse(Convert.ToString(ret.Value, 2).PadLeft(32, '0'));
var decodeValue = new JtagClient.JtagStatusReg(ret.Value);
logger.Info($"Read device {address} Status Register: \n\t 0b{binaryValue} \n\t {decodeValue}");
return TypedResults.Ok(new
{
original = ret.Value,
binaryValue,
decodeValue,
});
}
else
{
logger.Error(ret.Error);
return TypedResults.InternalServerError(ret.Error);
}
}
/// <summary>
/// 上传比特流文件
/// </summary>
/// <param name="address"> 设备地址 </param>
/// <param name="file">比特流文件</param>
[HttpPost("UploadBitstream")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)]
public async ValueTask<IResult> UploadBitstream(string address, IFormFile file)
{
if (file == null || file.Length == 0)
return TypedResults.BadRequest("未选择文件");
// 生成安全的文件名(避免路径遍历攻击)
var fileName = Path.GetRandomFileName();
var uploadsFolder = Path.Combine(Environment.CurrentDirectory, $"{BITSTREAM_PATH}/{address}");
// 如果存在文件,则删除原文件再上传
if (Directory.Exists(uploadsFolder))
{
Directory.Delete(uploadsFolder, true);
}
Directory.CreateDirectory(uploadsFolder);
var filePath = Path.Combine(uploadsFolder, fileName);
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream);
}
logger.Info($"Device {address} Upload Bitstream Successfully");
return TypedResults.Ok(true);
}
/// <summary>
/// 通过Jtag下载比特流文件
/// </summary>
/// <param name="address"> 设备地址 </param>
/// <param name="port"> 设备端口 </param>
[HttpPost("DownloadBitstream")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> DownloadBitstream(string address, int port)
{
// 检查文件
var fileDir = Path.Combine(Environment.CurrentDirectory, $"{BITSTREAM_PATH}/{address}");
if (!Directory.Exists(fileDir))
return TypedResults.BadRequest("Empty bitstream, Please upload it first");
try
{
// 读取文件
var filePath = Directory.GetFiles(fileDir)[0];
using (var fileStream = System.IO.File.Open(filePath, System.IO.FileMode.Open))
{
if (fileStream is null || fileStream.Length <= 0)
return TypedResults.BadRequest("Wrong bitstream, Please upload it again");
// 定义缓冲区大小: 32KB
byte[] buffer = new byte[32 * 1024];
byte[] revBuffer = new byte[32 * 1024];
long totalBytesRead = 0;
// 使用异步流读取文件
using (var memoryStream = new MemoryStream())
{
int bytesRead;
while ((bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
// 反转 32bits
var retBuffer = Common.Number.ReverseBytes(buffer, 4);
if (!retBuffer.IsSuccessful)
return TypedResults.InternalServerError(retBuffer.Error);
revBuffer = retBuffer.Value;
for (int i = 0; i < revBuffer.Length; i++)
{
revBuffer[i] = Common.Number.ReverseBits(revBuffer[i]);
}
await memoryStream.WriteAsync(revBuffer, 0, bytesRead);
totalBytesRead += bytesRead;
}
// 将所有数据转换为字节数组(注意:如果文件非常大,可能不适合完全加载到内存)
var fileBytes = memoryStream.ToArray();
// 下载比特流
var jtagCtrl = new JtagClient.Jtag(address, port);
var ret = await jtagCtrl.DownloadBitstream(fileBytes);
if (ret.IsSuccessful)
{
logger.Info($"Device {address} dowload bitstream successfully");
return TypedResults.Ok(ret.Value);
}
else
{
logger.Error(ret.Error);
return TypedResults.InternalServerError(ret.Error);
}
}
}
}
catch (Exception error)
{
return TypedResults.InternalServerError(error);
}
finally
{
}
}
/// <summary>
/// [TODO:description]
/// </summary>
/// <param name="address">[TODO:parameter]</param>
/// <param name="port">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns>
[HttpPost("BoundaryScanAllPorts")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> BoundaryScanAllPorts(string address, int port)
{
var jtagCtrl = new JtagClient.Jtag(address, port);
var ret = await jtagCtrl.BoundaryScan();
if (!ret.IsSuccessful)
{
if (ret.Error is ArgumentException)
return TypedResults.BadRequest(ret.Error);
else return TypedResults.InternalServerError(ret.Error);
}
return TypedResults.Ok(ret.Value);
}
/// <summary>
/// [TODO:description]
/// </summary>
/// <param name="address">[TODO:parameter]</param>
/// <param name="port">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns>
[HttpPost("BoundaryScanLogicalPorts")]
[EnableCors("Users")]
[ProducesResponseType(typeof(Dictionary<string, bool>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> BoundaryScanLogicalPorts(string address, int port)
{
var jtagCtrl = new JtagClient.Jtag(address, port);
var ret = await jtagCtrl.BoundaryScanLogicalPorts();
if (!ret.IsSuccessful)
{
if (ret.Error is ArgumentException)
return TypedResults.BadRequest(ret.Error);
else return TypedResults.InternalServerError(ret.Error);
}
return TypedResults.Ok(ret.Value);
}
/// <summary>
/// [TODO:description]
/// </summary>
/// <param name="address">[TODO:parameter]</param>
/// <param name="port">[TODO:parameter]</param>
/// <param name="speed">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns>
[HttpPost("SetSpeed")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> SetSpeed(string address, int port, UInt32 speed)
{
var jtagCtrl = new JtagClient.Jtag(address, port);
var ret = await jtagCtrl.SetSpeed(speed);
if (!ret.IsSuccessful)
{
if (ret.Error is ArgumentException)
return TypedResults.BadRequest(ret.Error);
else return TypedResults.InternalServerError(ret.Error);
}
return TypedResults.Ok(ret.Value);
}
}
/// <summary>
/// 远程更新
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class RemoteUpdateController : ControllerBase
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private const string BITSTREAM_PATH = "bitstream/RemoteUpdate";
/// <summary>
/// 上传远程更新比特流文件
/// </summary>
/// <param name="address"> 设备地址 </param>
/// <param name="goldenBitream">黄金比特流文件</param>
/// <param name="bitstream1">比特流文件1</param>
/// <param name="bitstream2">比特流文件2</param>
/// <param name="bitstream3">比特流文件3</param>
/// <returns>上传结果</returns>
[HttpPost("UploadBitstream")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ArgumentException), StatusCodes.Status400BadRequest)]
public async ValueTask<IResult> UploadBitstreams(
string address,
IFormFile? goldenBitream,
IFormFile? bitstream1,
IFormFile? bitstream2,
IFormFile? bitstream3)
{
if ((goldenBitream is null || goldenBitream.Length == 0) &&
(bitstream1 is null || bitstream1.Length == 0) &&
(bitstream2 is null || bitstream2.Length == 0) &&
(bitstream3 is null || bitstream3.Length == 0))
return TypedResults.BadRequest("未选择文件");
// 生成安全的文件名(避免路径遍历攻击)
var fileName = Path.GetRandomFileName();
var uploadsFolder = Path.Combine(Environment.CurrentDirectory, $"{BITSTREAM_PATH}/{address}");
// 如果存在文件,则删除原文件再上传
if (Directory.Exists(uploadsFolder))
{
Directory.Delete(uploadsFolder, true);
}
Directory.CreateDirectory(uploadsFolder);
for (int bitstreamNum = 0; bitstreamNum < 4; bitstreamNum++)
{
IFormFile file;
if (bitstreamNum == 0 && goldenBitream is not null)
file = goldenBitream;
else if (bitstreamNum == 1 && bitstream1 is not null)
file = bitstream1;
else if (bitstreamNum == 2 && bitstream2 is not null)
file = bitstream2;
else if (bitstreamNum == 3 && bitstream3 is not null)
file = bitstream3;
else continue;
var fileFolder = Path.Combine(uploadsFolder, bitstreamNum.ToString());
Directory.CreateDirectory(fileFolder);
var filePath = Path.Combine(fileFolder, fileName);
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream);
}
}
logger.Info($"Device {address} Upload Bitstream Successfully");
return TypedResults.Ok(true);
}
private async ValueTask<Result<byte[]>> ProcessBitstream(string filePath)
{
using (var fileStream = System.IO.File.Open(filePath, System.IO.FileMode.Open))
{
if (fileStream is null || fileStream.Length <= 0)
return new(new ArgumentException("Wrong bitstream path"));
// 定义缓冲区大小: 32KB
byte[] buffer = new byte[32 * 1024];
byte[] revBuffer = new byte[32 * 1024];
long totalBytesRead = 0;
// 使用异步流读取文件
using (var memoryStream = new MemoryStream())
{
int bytesRead;
while ((bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
// 反转 32bits
var retBuffer = Common.Number.ReverseBytes(buffer, 4);
if (!retBuffer.IsSuccessful)
return new(retBuffer.Error);
revBuffer = retBuffer.Value;
await memoryStream.WriteAsync(revBuffer, 0, bytesRead);
totalBytesRead += bytesRead;
}
// 将所有数据转换为字节数组(注意:如果文件非常大,可能不适合完全加载到内存)
var restStreamLen = memoryStream.Length % (4 * 1024);
if (restStreamLen != 0)
{
var appendLen = ((int)(4 * 1024 - restStreamLen));
var bytesAppend = new byte[appendLen];
Array.Fill<byte>(bytesAppend, 0xFF);
await memoryStream.WriteAsync(bytesAppend, 0, appendLen);
}
return new(memoryStream.ToArray());
}
}
}
/// <summary>
/// 远程更新单个比特流文件
/// </summary>
/// <param name="address"> 设备地址 </param>
/// <param name="port"> 设备端口 </param>
/// <param name="bitstreamNum"> 比特流位号 </param>
[HttpPost("DownloadBitstream")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ArgumentException), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> UpdateBitstream(string address, int port, int bitstreamNum)
{
// 检查文件
var fileDir = Path.Combine(Environment.CurrentDirectory, $"{BITSTREAM_PATH}/{address}/{bitstreamNum}");
if (!Directory.Exists(fileDir))
return TypedResults.BadRequest("Empty bitstream, Please upload it first");
try
{
// 读取文件
var filePath = Directory.GetFiles(fileDir)[0];
var fileBytes = await ProcessBitstream(filePath);
if (!fileBytes.IsSuccessful) return TypedResults.InternalServerError(fileBytes.Error);
// 下载比特流
var remoteUpdater = new RemoteUpdateClient.RemoteUpdater(address, port);
var ret = await remoteUpdater.UpdateBitstream(bitstreamNum, fileBytes.Value);
if (ret.IsSuccessful)
{
logger.Info($"Device {address} Update bitstream successfully");
return TypedResults.Ok(ret.Value);
}
else
{
logger.Error(ret.Error);
return TypedResults.InternalServerError(ret.Error);
}
}
catch (Exception error)
{
return TypedResults.InternalServerError(error);
}
}
/// <summary>
/// 下载多个比特流文件
/// </summary>
/// <param name="address">设备地址</param>
/// <param name="port">设备端口</param>
/// <param name="bitstreamNum">比特流编号</param>
/// <returns>总共上传比特流的数量</returns>
[HttpPost("DownloadMultiBitstreams")]
[EnableCors("Users")]
[ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ArgumentException), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> DownloadMultiBitstreams(string address, int port, int? bitstreamNum)
{
// 检查文件
var bitstreamsFolder = Path.Combine(Environment.CurrentDirectory, $"{BITSTREAM_PATH}/{address}");
if (!Directory.Exists(bitstreamsFolder))
return TypedResults.BadRequest("Empty bitstream, Please upload it first");
try
{
var bitstreams = new List<byte[]?>() { null, null, null, null };
int cnt = 0; // 上传比特流数量
for (int i = 0; i < 4; i++)
{
var bitstreamDir = Path.Combine(bitstreamsFolder, i.ToString());
if (!Directory.Exists(bitstreamDir))
continue;
cnt++;
// 读取文件
var filePath = Directory.GetFiles(bitstreamDir)[0];
var fileBytes = await ProcessBitstream(filePath);
if (!fileBytes.IsSuccessful) return TypedResults.InternalServerError(fileBytes.Error);
bitstreams[i] = fileBytes.Value;
}
// 下载比特流
var remoteUpdater = new RemoteUpdateClient.RemoteUpdater(address, port);
{
var ret = await remoteUpdater.UploadBitstreams(bitstreams[0], bitstreams[1], bitstreams[2], bitstreams[3]);
if (!ret.IsSuccessful) return TypedResults.InternalServerError(ret.Error);
if (!ret.Value) return TypedResults.InternalServerError("Upload MultiBitstreams failed");
}
if (bitstreamNum is not null)
{
var ret = await remoteUpdater.HotResetBitstream(bitstreamNum ?? 0);
if (!ret.IsSuccessful) return TypedResults.InternalServerError(ret.Error);
if (!ret.Value) return TypedResults.InternalServerError("Hot reset failed");
}
return TypedResults.Ok(cnt);
}
catch (Exception error)
{
return TypedResults.InternalServerError(error);
}
}
/// <summary>
/// 热复位比特流文件
/// </summary>
/// <param name="address">设备地址</param>
/// <param name="port">设备端口</param>
/// <param name="bitstreamNum">比特流编号</param>
/// <returns>操作结果</returns>
[HttpPost("HotResetBitstream")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ArgumentException), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> HotResetBitstream(string address, int port, int bitstreamNum)
{
var remoteUpdater = new RemoteUpdateClient.RemoteUpdater(address, port);
var ret = await remoteUpdater.HotResetBitstream(bitstreamNum);
if (ret.IsSuccessful)
{
logger.Info($"Device {address} Update bitstream successfully");
return TypedResults.Ok(ret.Value);
}
else
{
logger.Error(ret.Error);
return TypedResults.InternalServerError(ret.Error);
}
}
/// <summary>
/// [TODO:description]
/// </summary>
/// <param name="address">[TODO:parameter]</param>
/// <param name="port">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns>
[HttpPost("GetFirmwareVersion")]
[EnableCors("Users")]
[ProducesResponseType(typeof(UInt32), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ArgumentException), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> GetFirmwareVersion(string address, int port)
{
var remoteUpdater = new RemoteUpdateClient.RemoteUpdater(address, port);
var ret = await remoteUpdater.GetVersion();
if (ret.IsSuccessful)
{
logger.Info($"Device {address} get firmware version successfully");
return TypedResults.Ok(ret.Value);
}
else
{
logger.Error(ret.Error);
return TypedResults.InternalServerError(ret.Error);
}
}
}
/// <summary>
/// [TODO:description]
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class DDSController : ControllerBase
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
/// <summary>
/// [TODO:description]
/// </summary>
/// <param name="address">[TODO:parameter]</param>
/// <param name="port">[TODO:parameter]</param>
/// <param name="channelNum">[TODO:parameter]</param>
/// <param name="waveNum">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns>
[HttpPost("SetWaveNum")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ArgumentException), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> SetWaveNum(string address, int port, int channelNum, int waveNum)
{
var dds = new DDSClient.DDS(address, port);
var ret = await dds.SetWaveNum(channelNum, waveNum);
if (ret.IsSuccessful)
{
logger.Info($"Device {address} set output wave num successfully");
return TypedResults.Ok(ret.Value);
}
else
{
logger.Error(ret.Error);
return TypedResults.InternalServerError(ret.Error);
}
}
/// <summary>
/// [TODO:description]
/// </summary>
/// <param name="address">[TODO:parameter]</param>
/// <param name="port">[TODO:parameter]</param>
/// <param name="channelNum">[TODO:parameter]</param>
/// <param name="waveNum">[TODO:parameter]</param>
/// <param name="step">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns>
[HttpPost("SetFreq")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ArgumentException), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> SetFreq(string address, int port, int channelNum, int waveNum, UInt32 step)
{
var dds = new DDSClient.DDS(address, port);
var ret = await dds.SetFreq(channelNum, waveNum, step);
if (ret.IsSuccessful)
{
logger.Info($"Device {address} set output freqency successfully");
return TypedResults.Ok(ret.Value);
}
else
{
logger.Error(ret.Error);
return TypedResults.InternalServerError(ret.Error);
}
}
/// <summary>
/// [TODO:description]
/// </summary>
/// <param name="address">[TODO:parameter]</param>
/// <param name="port">[TODO:parameter]</param>
/// <param name="channelNum">[TODO:parameter]</param>
/// <param name="waveNum">[TODO:parameter]</param>
/// <param name="phase">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns>
[HttpPost("SetPhase")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ArgumentException), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> SetPhase(string address, int port, int channelNum, int waveNum, int phase)
{
var dds = new DDSClient.DDS(address, port);
var ret = await dds.SetPhase(channelNum, waveNum, phase);
if (ret.IsSuccessful)
{
logger.Info($"Device {address} set output phase successfully");
return TypedResults.Ok(ret.Value);
}
else
{
logger.Error(ret.Error);
return TypedResults.InternalServerError(ret.Error);
}
}
}
/// <summary>
/// [TODO:description]
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class BsdlParserController : ControllerBase
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
[EnableCors("Development")]
[HttpGet("GetBoundaryLogicalPorts")]
public IResult GetBoundaryLogicalPorts()
{
var parser = new BsdlParser.Parser();
var ret = parser.GetBoundaryLogicalPorts();
if (ret.IsNull) return TypedResults.InternalServerError("Get Null");
return TypedResults.Ok(ret.Value);
}
}
/// <summary>
/// 数据控制器
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class DataController : ControllerBase
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
/// <summary>
/// 创建数据库表
/// </summary>
/// <returns>插入的记录数</returns>
[EnableCors("Development")]
[HttpPost("CreateTable")]
public IResult CreateTables()
{
using var db = new Database.AppDataConnection();
db.CreateAllTables();
return TypedResults.Ok();
}
/// <summary>
/// 删除数据库表
/// </summary>
/// <returns>插入的记录数</returns>
[EnableCors("Development")]
[HttpDelete("DropTables")]
public IResult DropTables()
{
using var db = new Database.AppDataConnection();
db.DropAllTables();
return TypedResults.Ok();
}
/// <summary>
/// 获取所有用户
/// </summary>
/// <returns>用户列表</returns>
[HttpGet("AllUsers")]
public IResult AllUsers()
{
using var db = new Database.AppDataConnection();
var ret = db.User.ToList();
return TypedResults.Ok(ret);
}
/// <summary>
/// 注册新用户
/// </summary>
/// <param name="name">用户名</param>
/// <returns>操作结果</returns>
[HttpPost("SignUpUser")]
public IResult SignUpUser(string name)
{
if (name.Length > 255)
return TypedResults.BadRequest("Name Couln't over 255 characters");
using var db = new Database.AppDataConnection();
var ret = db.AddUser(name);
return TypedResults.Ok(ret);
}
}

View File

@ -0,0 +1,30 @@
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
namespace server.Controllers;
/// <summary>
/// [TODO:description]
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class BsdlParserController : ControllerBase
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
/// <summary>
/// [TODO:description]
/// </summary>
/// <returns>[TODO:return]</returns>
[EnableCors("Development")]
[HttpGet("GetBoundaryLogicalPorts")]
public IResult GetBoundaryLogicalPorts()
{
var parser = new BsdlParser.Parser();
var ret = parser.GetBoundaryLogicalPorts();
if (ret.IsNull) return TypedResults.InternalServerError("Get Null");
return TypedResults.Ok(ret.Value);
}
}

View File

@ -0,0 +1,108 @@
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
namespace server.Controllers;
/// <summary>
/// [TODO:description]
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class DDSController : ControllerBase
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
/// <summary>
/// [TODO:description]
/// </summary>
/// <param name="address">[TODO:parameter]</param>
/// <param name="port">[TODO:parameter]</param>
/// <param name="channelNum">[TODO:parameter]</param>
/// <param name="waveNum">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns>
[HttpPost("SetWaveNum")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ArgumentException), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> SetWaveNum(string address, int port, int channelNum, int waveNum)
{
var dds = new Peripherals.DDSClient.DDS(address, port);
var ret = await dds.SetWaveNum(channelNum, waveNum);
if (ret.IsSuccessful)
{
logger.Info($"Device {address} set output wave num successfully");
return TypedResults.Ok(ret.Value);
}
else
{
logger.Error(ret.Error);
return TypedResults.InternalServerError(ret.Error);
}
}
/// <summary>
/// [TODO:description]
/// </summary>
/// <param name="address">[TODO:parameter]</param>
/// <param name="port">[TODO:parameter]</param>
/// <param name="channelNum">[TODO:parameter]</param>
/// <param name="waveNum">[TODO:parameter]</param>
/// <param name="step">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns>
[HttpPost("SetFreq")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ArgumentException), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> SetFreq(string address, int port, int channelNum, int waveNum, UInt32 step)
{
var dds = new Peripherals.DDSClient.DDS(address, port);
var ret = await dds.SetFreq(channelNum, waveNum, step);
if (ret.IsSuccessful)
{
logger.Info($"Device {address} set output freqency successfully");
return TypedResults.Ok(ret.Value);
}
else
{
logger.Error(ret.Error);
return TypedResults.InternalServerError(ret.Error);
}
}
/// <summary>
/// [TODO:description]
/// </summary>
/// <param name="address">[TODO:parameter]</param>
/// <param name="port">[TODO:parameter]</param>
/// <param name="channelNum">[TODO:parameter]</param>
/// <param name="waveNum">[TODO:parameter]</param>
/// <param name="phase">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns>
[HttpPost("SetPhase")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ArgumentException), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> SetPhase(string address, int port, int channelNum, int waveNum, int phase)
{
var dds = new Peripherals.DDSClient.DDS(address, port);
var ret = await dds.SetPhase(channelNum, waveNum, phase);
if (ret.IsSuccessful)
{
logger.Info($"Device {address} set output phase successfully");
return TypedResults.Ok(ret.Value);
}
else
{
logger.Error(ret.Error);
return TypedResults.InternalServerError(ret.Error);
}
}
}

View File

@ -0,0 +1,69 @@
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
namespace server.Controllers;
/// <summary>
/// 数据控制器
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class DataController : ControllerBase
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
/// <summary>
/// 创建数据库表
/// </summary>
/// <returns>插入的记录数</returns>
[EnableCors("Development")]
[HttpPost("CreateTable")]
public IResult CreateTables()
{
using var db = new Database.AppDataConnection();
db.CreateAllTables();
return TypedResults.Ok();
}
/// <summary>
/// 删除数据库表
/// </summary>
/// <returns>插入的记录数</returns>
[EnableCors("Development")]
[HttpDelete("DropTables")]
public IResult DropTables()
{
using var db = new Database.AppDataConnection();
db.DropAllTables();
return TypedResults.Ok();
}
/// <summary>
/// 获取所有用户
/// </summary>
/// <returns>用户列表</returns>
[HttpGet("AllUsers")]
public IResult AllUsers()
{
using var db = new Database.AppDataConnection();
var ret = db.User.ToList();
return TypedResults.Ok(ret);
}
/// <summary>
/// 注册新用户
/// </summary>
/// <param name="name">用户名</param>
/// <returns>操作结果</returns>
[HttpPost("SignUpUser")]
public IResult SignUpUser(string name)
{
if (name.Length > 255)
return TypedResults.BadRequest("Name Couln't over 255 characters");
using var db = new Database.AppDataConnection();
var ret = db.AddUser(name);
return TypedResults.Ok(ret);
}
}

View File

@ -0,0 +1,278 @@
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
namespace server.Controllers;
/// <summary>
/// Jtag API
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class JtagController : ControllerBase
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private const string BITSTREAM_PATH = "bitstream/Jtag";
/// <summary>
/// 页面
/// </summary>
[HttpGet]
public string Index()
{
return "This is Jtag Controller";
}
/// <summary>
/// 获取Jtag ID Code
/// </summary>
/// <param name="address"> 设备地址 </param>
/// <param name="port"> 设备端口 </param>
[HttpGet("GetDeviceIDCode")]
[EnableCors("Users")]
[ProducesResponseType(typeof(uint), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> GetDeviceIDCode(string address, int port)
{
var jtagCtrl = new JtagClient.Jtag(address, port);
var ret = await jtagCtrl.ReadIDCode();
if (ret.IsSuccessful)
{
logger.Info($"Get device {address} ID code: 0x{ret.Value:X4}");
return TypedResults.Ok(ret.Value);
}
else
{
logger.Error(ret.Error);
return TypedResults.InternalServerError(ret.Error);
}
}
/// <summary>
/// 获取状态寄存器
/// </summary>
/// <param name="address"> 设备地址 </param>
/// <param name="port"> 设备端口 </param>
[HttpGet("ReadStatusReg")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> ReadStatusReg(string address, int port)
{
var jtagCtrl = new JtagClient.Jtag(address, port);
var ret = await jtagCtrl.ReadStatusReg();
if (ret.IsSuccessful)
{
var binaryValue = Common.String.Reverse(Convert.ToString(ret.Value, 2).PadLeft(32, '0'));
var decodeValue = new JtagClient.JtagStatusReg(ret.Value);
logger.Info($"Read device {address} Status Register: \n\t 0b{binaryValue} \n\t {decodeValue}");
return TypedResults.Ok(new
{
original = ret.Value,
binaryValue,
decodeValue,
});
}
else
{
logger.Error(ret.Error);
return TypedResults.InternalServerError(ret.Error);
}
}
/// <summary>
/// 上传比特流文件
/// </summary>
/// <param name="address"> 设备地址 </param>
/// <param name="file">比特流文件</param>
[HttpPost("UploadBitstream")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)]
public async ValueTask<IResult> UploadBitstream(string address, IFormFile file)
{
if (file == null || file.Length == 0)
return TypedResults.BadRequest("未选择文件");
// 生成安全的文件名(避免路径遍历攻击)
var fileName = Path.GetRandomFileName();
var uploadsFolder = Path.Combine(Environment.CurrentDirectory, $"{BITSTREAM_PATH}/{address}");
// 如果存在文件,则删除原文件再上传
if (Directory.Exists(uploadsFolder))
{
Directory.Delete(uploadsFolder, true);
}
Directory.CreateDirectory(uploadsFolder);
var filePath = Path.Combine(uploadsFolder, fileName);
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream);
}
logger.Info($"Device {address} Upload Bitstream Successfully");
return TypedResults.Ok(true);
}
/// <summary>
/// 通过Jtag下载比特流文件
/// </summary>
/// <param name="address"> 设备地址 </param>
/// <param name="port"> 设备端口 </param>
[HttpPost("DownloadBitstream")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> DownloadBitstream(string address, int port)
{
// 检查文件
var fileDir = Path.Combine(Environment.CurrentDirectory, $"{BITSTREAM_PATH}/{address}");
if (!Directory.Exists(fileDir))
return TypedResults.BadRequest("Empty bitstream, Please upload it first");
try
{
// 读取文件
var filePath = Directory.GetFiles(fileDir)[0];
using (var fileStream = System.IO.File.Open(filePath, System.IO.FileMode.Open))
{
if (fileStream is null || fileStream.Length <= 0)
return TypedResults.BadRequest("Wrong bitstream, Please upload it again");
// 定义缓冲区大小: 32KB
byte[] buffer = new byte[32 * 1024];
byte[] revBuffer = new byte[32 * 1024];
long totalBytesRead = 0;
// 使用异步流读取文件
using (var memoryStream = new MemoryStream())
{
int bytesRead;
while ((bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
// 反转 32bits
var retBuffer = Common.Number.ReverseBytes(buffer, 4);
if (!retBuffer.IsSuccessful)
return TypedResults.InternalServerError(retBuffer.Error);
revBuffer = retBuffer.Value;
for (int i = 0; i < revBuffer.Length; i++)
{
revBuffer[i] = Common.Number.ReverseBits(revBuffer[i]);
}
await memoryStream.WriteAsync(revBuffer, 0, bytesRead);
totalBytesRead += bytesRead;
}
// 将所有数据转换为字节数组(注意:如果文件非常大,可能不适合完全加载到内存)
var fileBytes = memoryStream.ToArray();
// 下载比特流
var jtagCtrl = new JtagClient.Jtag(address, port);
var ret = await jtagCtrl.DownloadBitstream(fileBytes);
if (ret.IsSuccessful)
{
logger.Info($"Device {address} dowload bitstream successfully");
return TypedResults.Ok(ret.Value);
}
else
{
logger.Error(ret.Error);
return TypedResults.InternalServerError(ret.Error);
}
}
}
}
catch (Exception error)
{
return TypedResults.InternalServerError(error);
}
finally
{
}
}
/// <summary>
/// [TODO:description]
/// </summary>
/// <param name="address">[TODO:parameter]</param>
/// <param name="port">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns>
[HttpPost("BoundaryScanAllPorts")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> BoundaryScanAllPorts(string address, int port)
{
var jtagCtrl = new JtagClient.Jtag(address, port);
var ret = await jtagCtrl.BoundaryScan();
if (!ret.IsSuccessful)
{
if (ret.Error is ArgumentException)
return TypedResults.BadRequest(ret.Error);
else return TypedResults.InternalServerError(ret.Error);
}
return TypedResults.Ok(ret.Value);
}
/// <summary>
/// [TODO:description]
/// </summary>
/// <param name="address">[TODO:parameter]</param>
/// <param name="port">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns>
[HttpPost("BoundaryScanLogicalPorts")]
[EnableCors("Users")]
[ProducesResponseType(typeof(Dictionary<string, bool>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> BoundaryScanLogicalPorts(string address, int port)
{
var jtagCtrl = new JtagClient.Jtag(address, port);
var ret = await jtagCtrl.BoundaryScanLogicalPorts();
if (!ret.IsSuccessful)
{
if (ret.Error is ArgumentException)
return TypedResults.BadRequest(ret.Error);
else return TypedResults.InternalServerError(ret.Error);
}
return TypedResults.Ok(ret.Value);
}
/// <summary>
/// [TODO:description]
/// </summary>
/// <param name="address">[TODO:parameter]</param>
/// <param name="port">[TODO:parameter]</param>
/// <param name="speed">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns>
[HttpPost("SetSpeed")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> SetSpeed(string address, int port, UInt32 speed)
{
var jtagCtrl = new JtagClient.Jtag(address, port);
var ret = await jtagCtrl.SetSpeed(speed);
if (!ret.IsSuccessful)
{
if (ret.Error is ArgumentException)
return TypedResults.BadRequest(ret.Error);
else return TypedResults.InternalServerError(ret.Error);
}
return TypedResults.Ok(ret.Value);
}
}

View File

@ -0,0 +1,101 @@
using System.Collections;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
namespace server.Controllers;
/// <summary>
/// 矩阵键控制器,用于管理矩阵键的启用、禁用和状态设置
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class MatrixKeyController : ControllerBase
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
/// <summary>
/// 启用矩阵键控制。
/// </summary>
/// <param name="address">设备的IP地址</param>
/// <param name="port">设备的端口号</param>
/// <returns>返回操作结果的状态码</returns>
[HttpPost("EnabelMatrixKey")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> EnabelMatrixKey(string address, int port)
{
var matrixKeyCtrl = new Peripherals.MatrixKeyClient.MatrixKey(address, port);
var ret = await matrixKeyCtrl.EnableControl();
if (ret.IsSuccessful)
{
logger.Info($"Enable device {address}:{port.ToString()} matrix key finished: {ret.Value}.");
return TypedResults.Ok(ret.Value);
}
else
{
logger.Error(ret.Error);
return TypedResults.InternalServerError(ret.Error);
}
}
/// <summary>
/// 禁用矩阵键控制。
/// </summary>
/// <param name="address">设备的IP地址</param>
/// <param name="port">设备的端口号</param>
/// <returns>返回操作结果的状态码</returns>
[HttpPost("DisableMatrixKey")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> DisableMatrixKey(string address, int port)
{
var matrixKeyCtrl = new Peripherals.MatrixKeyClient.MatrixKey(address, port);
var ret = await matrixKeyCtrl.DisableControl();
if (ret.IsSuccessful)
{
logger.Info($"Disable device {address}:{port.ToString()} matrix key finished: {ret.Value}.");
return TypedResults.Ok(ret.Value);
}
else
{
logger.Error(ret.Error);
return TypedResults.InternalServerError(ret.Error);
}
}
/// <summary>
/// 设置矩阵键的状态。
/// </summary>
/// <param name="address">设备的IP地址</param>
/// <param name="port">设备的端口号</param>
/// <param name="keyStates">矩阵键的状态数组长度应为16</param>
/// <returns>返回操作结果的状态码</returns>
[HttpPost("SetMatrixKeyStatus")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> SetMatrixKeyStatus(string address, int port, [FromBody] bool[] keyStates)
{
if (keyStates.Length != 16)
return TypedResults.BadRequest($"The length of key states should be 16 instead of {keyStates.Length}");
var matrixKeyCtrl = new Peripherals.MatrixKeyClient.MatrixKey(address, port);
var ret = await matrixKeyCtrl.ControlKey(new BitArray(keyStates));
if (ret.IsSuccessful)
{
logger.Info($"Set device {address}:{port.ToString()} matrix key finished: {ret.Value}.");
return TypedResults.Ok(ret.Value);
}
else
{
logger.Error(ret.Error);
return TypedResults.InternalServerError(ret.Error);
}
}
}

View File

@ -0,0 +1,46 @@
using System.Collections;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
namespace server.Controllers;
/// <summary>
/// 矩阵键控制器,用于管理矩阵键的启用、禁用和状态设置
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class PowerController : ControllerBase
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
/// <summary>
/// [TODO:description]
/// </summary>
/// <param name="address">[TODO:parameter]</param>
/// <param name="port">[TODO:parameter]</param>
/// <param name="enable">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns>
[HttpPost("SetPowerOnOff")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> SetPowerOnOff(string address, int port, bool enable)
{
var powerCtrl = new Peripherals.PowerClient.Power(address, port);
var ret = await powerCtrl.SetPowerOnOff(enable);
if (ret.IsSuccessful)
{
var powerStatus = enable ? "ON" : "OFF";
logger.Info($"Set device {address}:{port.ToString()} power {powerStatus} finished: {ret.Value}.");
return TypedResults.Ok(ret.Value);
}
else
{
logger.Error(ret.Error);
return TypedResults.InternalServerError(ret.Error);
}
}
}

View File

@ -0,0 +1,292 @@
using DotNext;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
namespace server.Controllers;
/// <summary>
/// 远程更新
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class RemoteUpdateController : ControllerBase
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private const string BITSTREAM_PATH = "bitstream/RemoteUpdate";
/// <summary>
/// 上传远程更新比特流文件
/// </summary>
/// <param name="address"> 设备地址 </param>
/// <param name="goldenBitream">黄金比特流文件</param>
/// <param name="bitstream1">比特流文件1</param>
/// <param name="bitstream2">比特流文件2</param>
/// <param name="bitstream3">比特流文件3</param>
/// <returns>上传结果</returns>
[HttpPost("UploadBitstream")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ArgumentException), StatusCodes.Status400BadRequest)]
public async ValueTask<IResult> UploadBitstreams(
string address,
IFormFile? goldenBitream,
IFormFile? bitstream1,
IFormFile? bitstream2,
IFormFile? bitstream3)
{
if ((goldenBitream is null || goldenBitream.Length == 0) &&
(bitstream1 is null || bitstream1.Length == 0) &&
(bitstream2 is null || bitstream2.Length == 0) &&
(bitstream3 is null || bitstream3.Length == 0))
return TypedResults.BadRequest("未选择文件");
// 生成安全的文件名(避免路径遍历攻击)
var fileName = Path.GetRandomFileName();
var uploadsFolder = Path.Combine(Environment.CurrentDirectory, $"{BITSTREAM_PATH}/{address}");
// 如果存在文件,则删除原文件再上传
if (Directory.Exists(uploadsFolder))
{
Directory.Delete(uploadsFolder, true);
}
Directory.CreateDirectory(uploadsFolder);
for (int bitstreamNum = 0; bitstreamNum < 4; bitstreamNum++)
{
IFormFile file;
if (bitstreamNum == 0 && goldenBitream is not null)
file = goldenBitream;
else if (bitstreamNum == 1 && bitstream1 is not null)
file = bitstream1;
else if (bitstreamNum == 2 && bitstream2 is not null)
file = bitstream2;
else if (bitstreamNum == 3 && bitstream3 is not null)
file = bitstream3;
else continue;
var fileFolder = Path.Combine(uploadsFolder, bitstreamNum.ToString());
Directory.CreateDirectory(fileFolder);
var filePath = Path.Combine(fileFolder, fileName);
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream);
}
}
logger.Info($"Device {address} Upload Bitstream Successfully");
return TypedResults.Ok(true);
}
private async ValueTask<Result<byte[]>> ProcessBitstream(string filePath)
{
using (var fileStream = System.IO.File.Open(filePath, System.IO.FileMode.Open))
{
if (fileStream is null || fileStream.Length <= 0)
return new(new ArgumentException("Wrong bitstream path"));
// 定义缓冲区大小: 32KB
byte[] buffer = new byte[32 * 1024];
byte[] revBuffer = new byte[32 * 1024];
long totalBytesRead = 0;
// 使用异步流读取文件
using (var memoryStream = new MemoryStream())
{
int bytesRead;
while ((bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
// 反转 32bits
var retBuffer = Common.Number.ReverseBytes(buffer, 4);
if (!retBuffer.IsSuccessful)
return new(retBuffer.Error);
revBuffer = retBuffer.Value;
await memoryStream.WriteAsync(revBuffer, 0, bytesRead);
totalBytesRead += bytesRead;
}
// 将所有数据转换为字节数组(注意:如果文件非常大,可能不适合完全加载到内存)
var restStreamLen = memoryStream.Length % (4 * 1024);
if (restStreamLen != 0)
{
var appendLen = ((int)(4 * 1024 - restStreamLen));
var bytesAppend = new byte[appendLen];
Array.Fill<byte>(bytesAppend, 0xFF);
await memoryStream.WriteAsync(bytesAppend, 0, appendLen);
}
return new(memoryStream.ToArray());
}
}
}
/// <summary>
/// 远程更新单个比特流文件
/// </summary>
/// <param name="address"> 设备地址 </param>
/// <param name="port"> 设备端口 </param>
/// <param name="bitstreamNum"> 比特流位号 </param>
[HttpPost("DownloadBitstream")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ArgumentException), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> UpdateBitstream(string address, int port, int bitstreamNum)
{
// 检查文件
var fileDir = Path.Combine(Environment.CurrentDirectory, $"{BITSTREAM_PATH}/{address}/{bitstreamNum}");
if (!Directory.Exists(fileDir))
return TypedResults.BadRequest("Empty bitstream, Please upload it first");
try
{
// 读取文件
var filePath = Directory.GetFiles(fileDir)[0];
var fileBytes = await ProcessBitstream(filePath);
if (!fileBytes.IsSuccessful) return TypedResults.InternalServerError(fileBytes.Error);
// 下载比特流
var remoteUpdater = new RemoteUpdateClient.RemoteUpdater(address, port);
var ret = await remoteUpdater.UpdateBitstream(bitstreamNum, fileBytes.Value);
if (ret.IsSuccessful)
{
logger.Info($"Device {address} Update bitstream successfully");
return TypedResults.Ok(ret.Value);
}
else
{
logger.Error(ret.Error);
return TypedResults.InternalServerError(ret.Error);
}
}
catch (Exception error)
{
return TypedResults.InternalServerError(error);
}
}
/// <summary>
/// 下载多个比特流文件
/// </summary>
/// <param name="address">设备地址</param>
/// <param name="port">设备端口</param>
/// <param name="bitstreamNum">比特流编号</param>
/// <returns>总共上传比特流的数量</returns>
[HttpPost("DownloadMultiBitstreams")]
[EnableCors("Users")]
[ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ArgumentException), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> DownloadMultiBitstreams(string address, int port, int? bitstreamNum)
{
// 检查文件
var bitstreamsFolder = Path.Combine(Environment.CurrentDirectory, $"{BITSTREAM_PATH}/{address}");
if (!Directory.Exists(bitstreamsFolder))
return TypedResults.BadRequest("Empty bitstream, Please upload it first");
try
{
var bitstreams = new List<byte[]?>() { null, null, null, null };
int cnt = 0; // 上传比特流数量
for (int i = 0; i < 4; i++)
{
var bitstreamDir = Path.Combine(bitstreamsFolder, i.ToString());
if (!Directory.Exists(bitstreamDir))
continue;
cnt++;
// 读取文件
var filePath = Directory.GetFiles(bitstreamDir)[0];
var fileBytes = await ProcessBitstream(filePath);
if (!fileBytes.IsSuccessful) return TypedResults.InternalServerError(fileBytes.Error);
bitstreams[i] = fileBytes.Value;
}
// 下载比特流
var remoteUpdater = new RemoteUpdateClient.RemoteUpdater(address, port);
{
var ret = await remoteUpdater.UploadBitstreams(bitstreams[0], bitstreams[1], bitstreams[2], bitstreams[3]);
if (!ret.IsSuccessful) return TypedResults.InternalServerError(ret.Error);
if (!ret.Value) return TypedResults.InternalServerError("Upload MultiBitstreams failed");
}
if (bitstreamNum is not null)
{
var ret = await remoteUpdater.HotResetBitstream(bitstreamNum ?? 0);
if (!ret.IsSuccessful) return TypedResults.InternalServerError(ret.Error);
if (!ret.Value) return TypedResults.InternalServerError("Hot reset failed");
}
return TypedResults.Ok(cnt);
}
catch (Exception error)
{
return TypedResults.InternalServerError(error);
}
}
/// <summary>
/// 热复位比特流文件
/// </summary>
/// <param name="address">设备地址</param>
/// <param name="port">设备端口</param>
/// <param name="bitstreamNum">比特流编号</param>
/// <returns>操作结果</returns>
[HttpPost("HotResetBitstream")]
[EnableCors("Users")]
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ArgumentException), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> HotResetBitstream(string address, int port, int bitstreamNum)
{
var remoteUpdater = new RemoteUpdateClient.RemoteUpdater(address, port);
var ret = await remoteUpdater.HotResetBitstream(bitstreamNum);
if (ret.IsSuccessful)
{
logger.Info($"Device {address} Update bitstream successfully");
return TypedResults.Ok(ret.Value);
}
else
{
logger.Error(ret.Error);
return TypedResults.InternalServerError(ret.Error);
}
}
/// <summary>
/// [TODO:description]
/// </summary>
/// <param name="address">[TODO:parameter]</param>
/// <param name="port">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns>
[HttpPost("GetFirmwareVersion")]
[EnableCors("Users")]
[ProducesResponseType(typeof(UInt32), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ArgumentException), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> GetFirmwareVersion(string address, int port)
{
var remoteUpdater = new RemoteUpdateClient.RemoteUpdater(address, port);
var ret = await remoteUpdater.GetVersion();
if (ret.IsSuccessful)
{
logger.Info($"Device {address} get firmware version successfully");
return TypedResults.Ok(ret.Value);
}
else
{
logger.Error(ret.Error);
return TypedResults.InternalServerError(ret.Error);
}
}
}

View File

@ -0,0 +1,133 @@
using System.Net;
using Common;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using WebProtocol;
namespace server.Controllers;
/// <summary>
/// UDP API
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class UDPController : ControllerBase
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private const string LOCALHOST = "127.0.0.1";
/// <summary>
/// 页面
/// </summary>
[HttpGet]
public string Index()
{
return "This is UDP Controller";
}
/// <summary>
/// 发送字符串
/// </summary>
/// <param name="address">IPV4 或者 IPV6 地址</param>
/// <param name="port">设备端口号</param>
/// <param name="text">发送的文本</param>
/// <response code="200">发送成功</response>
/// <response code="500">发送失败</response>
[HttpPost("SendString")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> SendString(string address = LOCALHOST, int port = 1234, string text = "Hello Server!")
{
var endPoint = new IPEndPoint(IPAddress.Parse(address), port);
var ret = await UDPClientPool.SendStringAsync(endPoint, [text]);
if (ret) { return TypedResults.Ok(); }
else { return TypedResults.InternalServerError(); }
}
/// <summary>
/// 发送二进制数据
/// </summary>
/// <param name="address" example="127.0.0.1">IPV4 或者 IPV6 地址</param>
/// <param name="port" example="1234">设备端口号</param>
/// <param name="bytes" example="FFFFAAAA">16进制文本</param>
[HttpPost("SendBytes")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> SendBytes(string address, int port, string bytes)
{
var endPoint = new IPEndPoint(IPAddress.Parse(address), port);
var ret = await UDPClientPool.SendBytesAsync(endPoint, Number.StringToBytes(bytes));
if (ret) { return TypedResults.Ok(); }
else { return TypedResults.InternalServerError(); }
}
/// <summary>
/// 发送地址包
/// </summary>
/// <param name="address">IP地址</param>
/// <param name="port">UDP 端口号</param>
/// <param name="opts">地址包选项</param>
[HttpPost("SendAddrPackage")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> SendAddrPackage(
string address,
int port,
[FromBody] SendAddrPackOptions opts)
{
var endPoint = new IPEndPoint(IPAddress.Parse(address), port);
var ret = await UDPClientPool.SendAddrPackAsync(endPoint, new WebProtocol.SendAddrPackage(opts));
if (ret) { return TypedResults.Ok(); }
else { return TypedResults.InternalServerError(); }
}
/// <summary>
/// 发送数据包
/// </summary>
/// <param name="address">IP地址</param>
/// <param name="port">UDP 端口号</param>
/// <param name="data">16进制数据</param>
[HttpPost("SendDataPackage")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> SendDataPackage(string address, int port, string data)
{
var endPoint = new IPEndPoint(IPAddress.Parse(address), port);
var ret = await UDPClientPool.SendDataPackAsync(endPoint,
new WebProtocol.SendDataPackage(Number.StringToBytes(data)));
if (ret) { return TypedResults.Ok(); }
else { return TypedResults.InternalServerError(); }
}
/// <summary>
/// 获取指定IP地址接收的数据列表
/// </summary>
/// <param name="address">IP地址</param>
[HttpGet("GetRecvDataArray")]
[ProducesResponseType(typeof(List<UDPData>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
public async ValueTask<IResult> GetRecvDataArray(string address, int taskID)
{
var ret = await MsgBus.UDPServer.GetDataArrayAsync(address, taskID);
if (ret.HasValue)
{
var dataJson = JsonConvert.SerializeObject(ret.Value);
logger.Debug($"Get Receive Successfully: {dataJson}");
return TypedResults.Ok(ret.Value);
}
else
{
logger.Debug("Get Receive Failed");
return TypedResults.InternalServerError();
}
}
}

View File

@ -422,7 +422,7 @@ public class Jtag
if (!MsgBus.IsRunning) if (!MsgBus.IsRunning)
return new(new Exception("Message Bus not Working!")); return new(new Exception("Message Bus not Working!"));
var retPack = await MsgBus.UDPServer.WaitForDataAsync(address, port); var retPack = await MsgBus.UDPServer.WaitForDataAsync(address, 0, port);
if (!retPack.IsSuccessful || !retPack.Value.IsSuccessful) if (!retPack.IsSuccessful || !retPack.Value.IsSuccessful)
return new(new Exception("Send address package failed")); return new(new Exception("Send address package failed"));
@ -441,7 +441,7 @@ public class Jtag
(UInt32 devAddr, UInt32 data, UInt32 result, UInt32 resultMask = 0xFF_FF_FF_FF, UInt32 delayMilliseconds = 0) (UInt32 devAddr, UInt32 data, UInt32 result, UInt32 resultMask = 0xFF_FF_FF_FF, UInt32 delayMilliseconds = 0)
{ {
{ {
var ret = await UDPClientPool.WriteAddr(this.ep, devAddr, data, this.timeout); var ret = await UDPClientPool.WriteAddr(this.ep, 0, devAddr, data, this.timeout);
if (!ret.IsSuccessful) return new(ret.Error); if (!ret.IsSuccessful) return new(ret.Error);
if (!ret.Value) return new(new Exception("Write FIFO failed")); if (!ret.Value) return new(new Exception("Write FIFO failed"));
} }
@ -450,7 +450,7 @@ public class Jtag
await Task.Delay(TimeSpan.FromMilliseconds(delayMilliseconds)); await Task.Delay(TimeSpan.FromMilliseconds(delayMilliseconds));
{ {
var ret = await UDPClientPool.ReadAddrWithWait(this.ep, JtagAddr.STATE, result, resultMask, this.timeout); var ret = await UDPClientPool.ReadAddrWithWait(this.ep, 0, JtagAddr.STATE, result, resultMask, this.timeout);
if (!ret.IsSuccessful) return new(ret.Error); if (!ret.IsSuccessful) return new(ret.Error);
return ret.Value; return ret.Value;
} }
@ -460,7 +460,7 @@ public class Jtag
(UInt32 devAddr, byte[] data, UInt32 result, UInt32 resultMask = 0xFF_FF_FF_FF, UInt32 delayMilliseconds = 0) (UInt32 devAddr, byte[] data, UInt32 result, UInt32 resultMask = 0xFF_FF_FF_FF, UInt32 delayMilliseconds = 0)
{ {
{ {
var ret = await UDPClientPool.WriteAddr(this.ep, devAddr, data, this.timeout); var ret = await UDPClientPool.WriteAddr(this.ep, 0, devAddr, data, this.timeout);
if (!ret.IsSuccessful) return new(ret.Error); if (!ret.IsSuccessful) return new(ret.Error);
if (!ret.Value) return new(new Exception("Write FIFO failed")); if (!ret.Value) return new(new Exception("Write FIFO failed"));
} }
@ -469,7 +469,7 @@ public class Jtag
await Task.Delay(TimeSpan.FromMilliseconds(delayMilliseconds)); await Task.Delay(TimeSpan.FromMilliseconds(delayMilliseconds));
{ {
var ret = await UDPClientPool.ReadAddrWithWait(this.ep, JtagAddr.STATE, result, resultMask, this.timeout); var ret = await UDPClientPool.ReadAddrWithWait(this.ep, 0, JtagAddr.STATE, result, resultMask, this.timeout);
if (!ret.IsSuccessful) return new(ret.Error); if (!ret.IsSuccessful) return new(ret.Error);
return ret.Value; return ret.Value;
} }
@ -627,9 +627,9 @@ public class Jtag
public async ValueTask<Result<uint>> ReadIDCode() public async ValueTask<Result<uint>> ReadIDCode()
{ {
// Clear Data // Clear Data
await MsgBus.UDPServer.ClearUDPData(this.address); await MsgBus.UDPServer.ClearUDPData(this.address, 0);
logger.Trace($"Clear up udp server {this.address} receive data"); logger.Trace($"Clear up udp server {this.address,0} receive data");
Result<bool> ret; Result<bool> ret;
@ -665,9 +665,9 @@ public class Jtag
public async ValueTask<Result<uint>> ReadStatusReg() public async ValueTask<Result<uint>> ReadStatusReg()
{ {
// Clear Data // Clear Data
await MsgBus.UDPServer.ClearUDPData(this.address); await MsgBus.UDPServer.ClearUDPData(this.address, 0);
logger.Trace($"Clear up udp server {this.address} receive data"); logger.Trace($"Clear up udp server {this.address,0} receive data");
Result<bool> ret; Result<bool> ret;
@ -702,9 +702,9 @@ public class Jtag
public async ValueTask<Result<bool>> DownloadBitstream(byte[] bitstream) public async ValueTask<Result<bool>> DownloadBitstream(byte[] bitstream)
{ {
// Clear Data // Clear Data
await MsgBus.UDPServer.ClearUDPData(this.address); await MsgBus.UDPServer.ClearUDPData(this.address, 0);
logger.Trace($"Clear up udp server {this.address} receive data"); logger.Trace($"Clear up udp server {this.address,0} receive data");
Result<bool> ret; Result<bool> ret;
@ -776,9 +776,9 @@ public class Jtag
/// <summary> /// <summary>
/// [TODO:description] /// 边界扫描
/// </summary> /// </summary>
/// <returns>[TODO:return]</returns> /// <returns>返回所有引脚边界扫描结果</returns>
public async ValueTask<Result<BitArray>> BoundaryScan() public async ValueTask<Result<BitArray>> BoundaryScan()
{ {
var paser = new BsdlParser.Parser(); var paser = new BsdlParser.Parser();
@ -786,9 +786,9 @@ public class Jtag
logger.Debug($"Get boundar scan registers number: {portNum}"); logger.Debug($"Get boundar scan registers number: {portNum}");
// Clear Data // Clear Data
await MsgBus.UDPServer.ClearUDPData(this.address); await MsgBus.UDPServer.ClearUDPData(this.address, 0);
logger.Trace($"Clear up udp server {this.address} receive data"); logger.Trace($"Clear up udp server {this.address,0} receive data");
Result<bool> ret; Result<bool> ret;
@ -824,9 +824,9 @@ public class Jtag
} }
/// <summary> /// <summary>
/// [TODO:description] /// 执行边界扫描并返回逻辑端口的状态
/// </summary> /// </summary>
/// <returns>[TODO:return]</returns> /// <returns>包含逻辑端口状态的字典键为端口ID值为布尔状态</returns>
public async ValueTask<Result<Dictionary<string, bool>>> BoundaryScanLogicalPorts() public async ValueTask<Result<Dictionary<string, bool>>> BoundaryScanLogicalPorts()
{ {
var bitArray = await BoundaryScan(); var bitArray = await BoundaryScan();
@ -845,18 +845,23 @@ public class Jtag
return portStatus; return portStatus;
} }
/// <summary>
/// 设置JTAG的运行速度。
/// </summary>
/// <param name="speed">运行速度值。</param>
/// <returns>指示操作是否成功的异步结果。</returns>
public async ValueTask<Result<bool>> SetSpeed(UInt32 speed) public async ValueTask<Result<bool>> SetSpeed(UInt32 speed)
{ {
// Clear Data // Clear Data
await MsgBus.UDPServer.ClearUDPData(this.address); await MsgBus.UDPServer.ClearUDPData(this.address, 0);
logger.Trace($"Clear up udp server {this.address,0} receive data");
logger.Trace($"Clear up udp server {this.address} receive data");
var ret = await WriteFIFO( var ret = await WriteFIFO(
JtagAddr.SPEED_CTRL, (speed << 16) | speed, JtagAddr.SPEED_CTRL, (speed << 16) | speed,
JtagState.CMD_EXEC_FINISH, JtagState.CMD_EXEC_FINISH); JtagState.CMD_EXEC_FINISH, JtagState.CMD_EXEC_FINISH);
if (!ret.IsSuccessful) return new (ret.Error); if (!ret.IsSuccessful) return new(ret.Error);
return ret.Value; return ret.Value;
} }
} }

View File

@ -1,7 +1,7 @@
using System.Net; using System.Net;
using DotNext; using DotNext;
namespace DDSClient; namespace Peripherals.DDSClient;
static class DDSAddr static class DDSAddr
{ {
@ -108,11 +108,11 @@ public class DDS
if (waveNum < 0 || waveNum > 3) return new(new ArgumentException( if (waveNum < 0 || waveNum > 3) return new(new ArgumentException(
$"Wave number should be 0 ~ 3 instead of {waveNum}", nameof(waveNum))); $"Wave number should be 0 ~ 3 instead of {waveNum}", nameof(waveNum)));
await MsgBus.UDPServer.ClearUDPData(this.address); await MsgBus.UDPServer.ClearUDPData(this.address, 1);
logger.Trace("Clear udp data finished"); logger.Trace("Clear udp data finished");
var ret = await UDPClientPool.WriteAddr( var ret = await UDPClientPool.WriteAddr(
this.ep, DDSAddr.Channel[channelNum].WaveSelect, (UInt32)waveNum, this.timeout); this.ep, 1, DDSAddr.Channel[channelNum].WaveSelect, (UInt32)waveNum, this.timeout);
if (!ret.IsSuccessful) if (!ret.IsSuccessful)
return new(ret.Error); return new(ret.Error);
return ret.Value; return ret.Value;
@ -132,11 +132,11 @@ public class DDS
if (waveNum < 0 || waveNum > 3) return new(new ArgumentException( if (waveNum < 0 || waveNum > 3) return new(new ArgumentException(
$"Wave number should be 0 ~ 3 instead of {waveNum}", nameof(waveNum))); $"Wave number should be 0 ~ 3 instead of {waveNum}", nameof(waveNum)));
await MsgBus.UDPServer.ClearUDPData(this.address); await MsgBus.UDPServer.ClearUDPData(this.address, 1);
logger.Trace("Clear udp data finished"); logger.Trace("Clear udp data finished");
var ret = await UDPClientPool.WriteAddr( var ret = await UDPClientPool.WriteAddr(
this.ep, DDSAddr.Channel[channelNum].FreqCtrl[waveNum], step, this.timeout); this.ep, 1, DDSAddr.Channel[channelNum].FreqCtrl[waveNum], step, this.timeout);
if (!ret.IsSuccessful) if (!ret.IsSuccessful)
return new(ret.Error); return new(ret.Error);
return ret.Value; return ret.Value;
@ -158,11 +158,11 @@ public class DDS
if (phase < 0 || phase > 4096) return new(new ArgumentException( if (phase < 0 || phase > 4096) return new(new ArgumentException(
$"Phase should be 0 ~ 4096 instead of {phase}", nameof(phase))); $"Phase should be 0 ~ 4096 instead of {phase}", nameof(phase)));
await MsgBus.UDPServer.ClearUDPData(this.address); await MsgBus.UDPServer.ClearUDPData(this.address, 1);
logger.Trace("Clear udp data finished"); logger.Trace("Clear udp data finished");
var ret = await UDPClientPool.WriteAddr( var ret = await UDPClientPool.WriteAddr(
this.ep, DDSAddr.Channel[channelNum].PhaseCtrl[waveNum], (UInt32)phase, this.timeout); this.ep, 1, DDSAddr.Channel[channelNum].PhaseCtrl[waveNum], (UInt32)phase, this.timeout);
if (!ret.IsSuccessful) if (!ret.IsSuccessful)
return new(ret.Error); return new(ret.Error);
return ret.Value; return ret.Value;

View File

@ -0,0 +1,89 @@
using System.Collections;
using System.Net;
using DotNext;
namespace Peripherals.MatrixKeyClient;
class MatrixKeyAddr
{
public const UInt32 BASE = 0x10_00_00_00;
public const UInt32 KEY_ENABLE = BASE + 5;
public const UInt32 KEY_CTRL = BASE + 6;
}
/// <summary>
/// 矩阵键盘外设类,用于控制和管理矩阵键盘的功能。
/// </summary>
public class MatrixKey
{
readonly int timeout;
readonly int port;
readonly string address;
private IPEndPoint ep;
/// <summary>
/// 构造函数,用于初始化矩阵键盘外设实例。
/// </summary>
/// <param name="address">设备的IP地址</param>
/// <param name="port">设备的端口号</param>
/// <param name="timeout">操作的超时时间毫秒默认为1000</param>
/// <returns>无返回值。</returns>
public MatrixKey(string address, int port, int timeout = 1000)
{
this.address = address;
this.port = port;
this.ep = new IPEndPoint(IPAddress.Parse(address), port);
this.timeout = timeout;
}
/// <summary>
/// 启用矩阵键盘的控制功能。
/// </summary>
/// <returns>返回一个包含操作结果的异步任务</returns>
public async ValueTask<Result<bool>> EnableControl()
{
if (MsgBus.IsRunning)
await 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);
if (!ret.IsSuccessful) return new(ret.Error);
return ret.Value;
}
/// <summary>
/// 禁用矩阵键盘的控制功能。
/// </summary>
/// <returns>返回一个包含操作结果的异步任务</returns>
public async ValueTask<Result<bool>> DisableControl()
{
if (MsgBus.IsRunning)
await 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);
if (!ret.IsSuccessful) return new(ret.Error);
return ret.Value;
}
/// <summary>
/// 控制矩阵键盘的按键状态。
/// </summary>
/// <param name="keyStates">表示按键状态的位数组长度必须为16</param>
/// <returns>返回一个包含操作结果的异步任务</returns>
public async ValueTask<Result<bool>> ControlKey(BitArray keyStates)
{
if (MsgBus.IsRunning)
await MsgBus.UDPServer.ClearUDPData(this.address, 1);
else return new(new Exception("Message Bus not work!"));
if (keyStates.Length != 16) return new(new ArgumentException(
$"The number of key should be 16 instead of {keyStates.Length}", nameof(keyStates)));
var ret = await UDPClientPool.WriteAddr(
this.ep, 1, MatrixKeyAddr.KEY_CTRL, Common.Number.BitsToNumber(keyStates).Value, this.timeout);
if (!ret.IsSuccessful) return new(ret.Error);
return ret.Value;
}
}

View File

@ -0,0 +1,56 @@
using System.Net;
using DotNext;
namespace Peripherals.PowerClient;
class PowerAddr
{
public const UInt32 Base = 0x10_00_00_00;
public const UInt32 PowerCtrl = Base + 7;
}
/// <summary>
/// [TODO:description]
/// </summary>
public class Power
{
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
readonly int timeout;
readonly int port;
readonly string address;
private IPEndPoint ep;
/// <summary>
/// [TODO:description]
/// </summary>
/// <param name="address">[TODO:parameter]</param>
/// <param name="port">[TODO:parameter]</param>
/// <param name="timeout">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns>
public Power(string address, int port, int timeout = 1000)
{
this.address = address;
this.port = port;
this.ep = new IPEndPoint(IPAddress.Parse(address), port);
this.timeout = timeout;
}
/// <summary>
/// [TODO:description]
/// </summary>
/// <param name="enable">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns>
public async ValueTask<Result<bool>> SetPowerOnOff(bool enable)
{
if (MsgBus.IsRunning)
await 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);
if (!ret.IsSuccessful) return new(ret.Error);
return ret.Value;
}
}

View File

@ -142,7 +142,7 @@ public class RemoteUpdater
{ {
var ret = await UDPClientPool.WriteAddr( var ret = await UDPClientPool.WriteAddr(
this.ep, RemoteUpdaterAddr.WriteCtrl, this.ep, 0, RemoteUpdaterAddr.WriteCtrl,
Convert.ToUInt32((writeSectorNum << 16) | (1 << 15) | Convert.ToInt32(flashAddr / 4096)), this.timeout); Convert.ToUInt32((writeSectorNum << 16) | (1 << 15) | Convert.ToInt32(flashAddr / 4096)), this.timeout);
if (!ret.IsSuccessful) return new(ret.Error); if (!ret.IsSuccessful) return new(ret.Error);
if (!ret.Value) return new(new Exception("Enable write flash failed")); if (!ret.Value) return new(new Exception("Enable write flash failed"));
@ -150,7 +150,7 @@ public class RemoteUpdater
{ {
var ret = await UDPClientPool.ReadAddrWithWait( var ret = await UDPClientPool.ReadAddrWithWait(
this.ep, RemoteUpdaterAddr.WriteSign, this.ep, 0, RemoteUpdaterAddr.WriteSign,
0x00_00_00_01, 0x00_00_00_01, this.timeoutForWait); 0x00_00_00_01, 0x00_00_00_01, this.timeoutForWait);
if (!ret.IsSuccessful) return new(ret.Error); if (!ret.IsSuccessful) return new(ret.Error);
if (!ret.Value) return new(new Exception( if (!ret.Value) return new(new Exception(
@ -158,14 +158,14 @@ public class RemoteUpdater
} }
{ {
var ret = await UDPClientPool.WriteAddr(this.ep, RemoteUpdaterAddr.WriteFIFO, bytesData, this.timeout); var ret = await UDPClientPool.WriteAddr(this.ep, 0, RemoteUpdaterAddr.WriteFIFO, bytesData, this.timeout);
if (!ret.IsSuccessful) return new(ret.Error); if (!ret.IsSuccessful) return new(ret.Error);
if (!ret.Value) return new(new Exception("Send data to flash failed")); if (!ret.Value) return new(new Exception("Send data to flash failed"));
} }
{ {
var ret = await UDPClientPool.ReadAddrWithWait( var ret = await UDPClientPool.ReadAddrWithWait(
this.ep, RemoteUpdaterAddr.WriteSign, this.ep, 0, RemoteUpdaterAddr.WriteSign,
0x00_00_01_00, 0x00_00_01_00, this.timeoutForWait); 0x00_00_01_00, 0x00_00_01_00, this.timeoutForWait);
if (!ret.IsSuccessful) return new(ret.Error); if (!ret.IsSuccessful) return new(ret.Error);
return ret.Value; return ret.Value;
@ -314,14 +314,14 @@ public class RemoteUpdater
private async ValueTask<Result<bool>> CheckBitstreamCRC(int bitstreamNum, int bitstreamLen, UInt32 checkSum) private async ValueTask<Result<bool>> CheckBitstreamCRC(int bitstreamNum, int bitstreamLen, UInt32 checkSum)
{ {
{ {
var ret = await UDPClientPool.WriteAddr(this.ep, RemoteUpdaterAddr.ReadCtrl2, 0x00_00_00_00, this.timeout); var ret = await UDPClientPool.WriteAddr(this.ep, 0, RemoteUpdaterAddr.ReadCtrl2, 0x00_00_00_00, this.timeout);
if (!ret.IsSuccessful) return new(ret.Error); if (!ret.IsSuccessful) return new(ret.Error);
if (!ret.Value) return new(new Exception("Write read control 2 failed")); if (!ret.Value) return new(new Exception("Write read control 2 failed"));
} }
{ {
var ret = await UDPClientPool.WriteAddr( var ret = await UDPClientPool.WriteAddr(
this.ep, RemoteUpdaterAddr.ReadCtrl1, this.ep, 0, RemoteUpdaterAddr.ReadCtrl1,
Convert.ToUInt32((bitstreamLen << 16) | (1 << 15) | Convert.ToInt32(FlashAddr.Bitstream[bitstreamNum] / 4096)), Convert.ToUInt32((bitstreamLen << 16) | (1 << 15) | Convert.ToInt32(FlashAddr.Bitstream[bitstreamNum] / 4096)),
this.timeout); this.timeout);
if (!ret.IsSuccessful) return new(ret.Error); if (!ret.IsSuccessful) return new(ret.Error);
@ -330,7 +330,7 @@ public class RemoteUpdater
{ {
var ret = await UDPClientPool.ReadAddrWithWait( var ret = await UDPClientPool.ReadAddrWithWait(
this.ep, RemoteUpdaterAddr.ReadSign, this.ep, 0, RemoteUpdaterAddr.ReadSign,
0x00_00_01_00, 0x00_00_01_00, this.timeoutForWait); 0x00_00_01_00, 0x00_00_01_00, this.timeoutForWait);
if (!ret.IsSuccessful) return new(ret.Error); if (!ret.IsSuccessful) return new(ret.Error);
if (!ret.Value) return new(new Exception( if (!ret.Value) return new(new Exception(
@ -338,7 +338,7 @@ public class RemoteUpdater
} }
{ {
var ret = await UDPClientPool.ReadAddr(this.ep, RemoteUpdaterAddr.ReadCRC, this.timeout); var ret = await UDPClientPool.ReadAddr(this.ep, 0, RemoteUpdaterAddr.ReadCRC, this.timeout);
if (!ret.IsSuccessful) return new(ret.Error); if (!ret.IsSuccessful) return new(ret.Error);
var bytes = ret.Value.Options.Data; var bytes = ret.Value.Options.Data;
@ -368,7 +368,7 @@ public class RemoteUpdater
$"Bitsteam num should be 0 ~ 3 for HotRest, but given {bitstreamNum}", nameof(bitstreamNum))); $"Bitsteam num should be 0 ~ 3 for HotRest, but given {bitstreamNum}", nameof(bitstreamNum)));
var ret = await UDPClientPool.WriteAddr( var ret = await UDPClientPool.WriteAddr(
this.ep, RemoteUpdaterAddr.HotResetCtrl, this.ep, 0, RemoteUpdaterAddr.HotResetCtrl,
((FlashAddr.Bitstream[bitstreamNum] << 8) | 1), this.timeout); ((FlashAddr.Bitstream[bitstreamNum] << 8) | 1), this.timeout);
if (!ret.IsSuccessful) return new(ret.Error); if (!ret.IsSuccessful) return new(ret.Error);
return ret.Value; return ret.Value;
@ -381,7 +381,7 @@ public class RemoteUpdater
/// <returns>[TODO:return]</returns> /// <returns>[TODO:return]</returns>
public async ValueTask<Result<bool>> HotResetBitstream(int bitstreamNum) public async ValueTask<Result<bool>> HotResetBitstream(int bitstreamNum)
{ {
await MsgBus.UDPServer.ClearUDPData(this.address); await MsgBus.UDPServer.ClearUDPData(this.address, 0);
logger.Trace("Clear udp data finished"); logger.Trace("Clear udp data finished");
{ {
@ -411,7 +411,7 @@ public class RemoteUpdater
byte[]? bitstream2, byte[]? bitstream2,
byte[]? bitstream3) byte[]? bitstream3)
{ {
await MsgBus.UDPServer.ClearUDPData(this.address); await MsgBus.UDPServer.ClearUDPData(this.address, 0);
logger.Trace("Clear udp data finished"); logger.Trace("Clear udp data finished");
for (int bitstreamNum = 0; bitstreamNum < 4; bitstreamNum++) for (int bitstreamNum = 0; bitstreamNum < 4; bitstreamNum++)
@ -462,7 +462,7 @@ public class RemoteUpdater
$"The length of data should be divided by 4096, bug given {bytesData.Length}", nameof(bytesData))); $"The length of data should be divided by 4096, bug given {bytesData.Length}", nameof(bytesData)));
var bitstreamBlockNum = bytesData.Length / (4 * 1024); var bitstreamBlockNum = bytesData.Length / (4 * 1024);
await MsgBus.UDPServer.ClearUDPData(this.address); await MsgBus.UDPServer.ClearUDPData(this.address, 0);
logger.Trace("Clear udp data finished"); logger.Trace("Clear udp data finished");
{ {
@ -538,11 +538,11 @@ public class RemoteUpdater
/// <returns>[TODO:return]</returns> /// <returns>[TODO:return]</returns>
public async ValueTask<Result<UInt32>> GetVersion() public async ValueTask<Result<UInt32>> GetVersion()
{ {
await MsgBus.UDPServer.ClearUDPData(this.address); await MsgBus.UDPServer.ClearUDPData(this.address, 0);
logger.Trace("Clear udp data finished"); logger.Trace("Clear udp data finished");
{ {
var ret = await UDPClientPool.ReadAddr(this.ep, RemoteUpdaterAddr.Version, this.timeout); var ret = await UDPClientPool.ReadAddr(this.ep, 0, RemoteUpdaterAddr.Version, this.timeout);
if (!ret.IsSuccessful) return new(ret.Error); if (!ret.IsSuccessful) return new(ret.Error);
var retData = ret.Value.Options.Data; var retData = ret.Value.Options.Data;

View File

@ -9,6 +9,8 @@ using WebProtocol;
/// </summary> /// </summary>
public class UDPClientPool public class UDPClientPool
{ {
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private static IPAddress localhost = IPAddress.Parse("127.0.0.1"); private static IPAddress localhost = IPAddress.Parse("127.0.0.1");
/// <summary> /// <summary>
@ -58,6 +60,9 @@ public class UDPClientPool
var sendLen = socket.SendTo(buf, endPoint); var sendLen = socket.SendTo(buf, endPoint);
socket.Close(); socket.Close();
logger.Debug($"UDP socket send bytes to device {endPoint.Address.ToString()}:{endPoint.Port.ToString()}:");
logger.Debug($" Original Data: {BitConverter.ToString(buf).Replace("-", " ")}");
if (sendLen == buf.Length) { return true; } if (sendLen == buf.Length) { return true; }
else { return false; } else { return false; }
} }
@ -86,6 +91,10 @@ public class UDPClientPool
var sendLen = socket.SendTo(sendBytes, endPoint); var sendLen = socket.SendTo(sendBytes, endPoint);
socket.Close(); socket.Close();
logger.Debug($"UDP socket send address package to device {endPoint.Address.ToString()}:{endPoint.Port.ToString()}:");
logger.Debug($" Original Data: {BitConverter.ToString(pkg.ToBytes()).Replace("-", " ")}");
logger.Debug($" Decoded Data: {pkg.ToString()}");
if (sendLen == sendBytes.Length) { return true; } if (sendLen == sendBytes.Length) { return true; }
else { return false; } else { return false; }
} }
@ -115,6 +124,9 @@ public class UDPClientPool
var sendLen = socket.SendTo(sendBytes, endPoint); var sendLen = socket.SendTo(sendBytes, endPoint);
socket.Close(); socket.Close();
logger.Debug($"UDP socket send data package to device {endPoint.Address.ToString()}:{endPoint.Port.ToString()}:");
logger.Debug($" Original Data: {BitConverter.ToString(pkg.ToBytes()).Replace("-", " ")}");
if (sendLen == sendBytes.Length) { return true; } if (sendLen == sendBytes.Length) { return true; }
else { return false; } else { return false; }
} }
@ -172,14 +184,14 @@ public class UDPClientPool
/// <param name="timeout">[TODO:parameter]</param> /// <param name="timeout">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns> /// <returns>[TODO:return]</returns>
public static async ValueTask<Result<RecvDataPackage>> ReadAddr( public static async ValueTask<Result<RecvDataPackage>> ReadAddr(
IPEndPoint endPoint, uint devAddr, int timeout = 1000) IPEndPoint endPoint, int taskID, uint devAddr, int timeout = 1000)
{ {
var ret = false; var ret = false;
var opts = new SendAddrPackOptions(); var opts = new SendAddrPackOptions();
opts.BurstType = BurstType.FixedBurst; opts.BurstType = BurstType.FixedBurst;
opts.BurstLength = 0; opts.BurstLength = 0;
opts.CommandID = 0; opts.CommandID = Convert.ToByte(taskID);
opts.Address = devAddr; opts.Address = devAddr;
// Read Jtag State Register // Read Jtag State Register
@ -192,7 +204,7 @@ public class UDPClientPool
return new(new Exception("Message Bus not Working!")); return new(new Exception("Message Bus not Working!"));
var retPack = await MsgBus.UDPServer.WaitForDataAsync( var retPack = await MsgBus.UDPServer.WaitForDataAsync(
endPoint.Address.ToString(), endPoint.Port, timeout); endPoint.Address.ToString(), taskID, endPoint.Port, timeout);
if (!retPack.IsSuccessful) return new(retPack.Error); if (!retPack.IsSuccessful) return new(retPack.Error);
else if (!retPack.Value.IsSuccessful) else if (!retPack.Value.IsSuccessful)
return new(new Exception("Send address package failed")); return new(new Exception("Send address package failed"));
@ -214,11 +226,11 @@ public class UDPClientPool
/// <param name="timeout">[TODO:parameter]</param> /// <param name="timeout">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns> /// <returns>[TODO:return]</returns>
public static async ValueTask<Result<bool>> ReadAddr( public static async ValueTask<Result<bool>> ReadAddr(
IPEndPoint endPoint, uint devAddr, UInt32 result, UInt32 resultMask, int timeout = 1000) IPEndPoint endPoint, int taskID, uint devAddr, UInt32 result, UInt32 resultMask, int timeout = 1000)
{ {
var address = endPoint.Address.ToString(); var address = endPoint.Address.ToString();
var ret = await ReadAddr(endPoint, devAddr, timeout); var ret = await ReadAddr(endPoint, taskID, devAddr, timeout);
if (!ret.IsSuccessful) return new(ret.Error); if (!ret.IsSuccessful) return new(ret.Error);
if (!ret.Value.IsSuccessful) if (!ret.Value.IsSuccessful)
return new(new Exception($"Read device {address} address {devAddr} failed")); return new(new Exception($"Read device {address} address {devAddr} failed"));
@ -252,7 +264,7 @@ public class UDPClientPool
/// <param name="timeout">[TODO:parameter]</param> /// <param name="timeout">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns> /// <returns>[TODO:return]</returns>
public static async ValueTask<Result<bool>> ReadAddrWithWait( public static async ValueTask<Result<bool>> ReadAddrWithWait(
IPEndPoint endPoint, uint devAddr, UInt32 result, UInt32 resultMask, int timeout = 1000) IPEndPoint endPoint, int taskID, uint devAddr, UInt32 result, UInt32 resultMask, int timeout = 1000)
{ {
var address = endPoint.Address.ToString(); var address = endPoint.Address.ToString();
@ -265,7 +277,7 @@ public class UDPClientPool
try try
{ {
var ret = await ReadAddr(endPoint, devAddr, Convert.ToInt32(timeleft.TotalMilliseconds)); var ret = await ReadAddr(endPoint, taskID, devAddr, Convert.ToInt32(timeleft.TotalMilliseconds));
if (!ret.IsSuccessful) return new(ret.Error); if (!ret.IsSuccessful) return new(ret.Error);
if (!ret.Value.IsSuccessful) if (!ret.Value.IsSuccessful)
return new(new Exception($"Read device {address} address {devAddr} failed")); return new(new Exception($"Read device {address} address {devAddr} failed"));
@ -300,14 +312,14 @@ public class UDPClientPool
/// <param name="timeout">[TODO:parameter]</param> /// <param name="timeout">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns> /// <returns>[TODO:return]</returns>
public static async ValueTask<Result<bool>> WriteAddr( public static async ValueTask<Result<bool>> WriteAddr(
IPEndPoint endPoint, UInt32 devAddr, UInt32 data, int timeout = 1000) IPEndPoint endPoint, int taskID, UInt32 devAddr, UInt32 data, int timeout = 1000)
{ {
var ret = false; var ret = false;
var opts = new SendAddrPackOptions(); var opts = new SendAddrPackOptions();
opts.BurstType = BurstType.FixedBurst; opts.BurstType = BurstType.FixedBurst;
opts.BurstLength = 0; opts.BurstLength = 0;
opts.CommandID = 0; opts.CommandID = Convert.ToByte(taskID);
opts.Address = devAddr; opts.Address = devAddr;
// Write Jtag State Register // Write Jtag State Register
@ -325,7 +337,7 @@ public class UDPClientPool
// Wait for Write Ack // Wait for Write Ack
var udpWriteAck = await MsgBus.UDPServer.WaitForAckAsync( var udpWriteAck = await MsgBus.UDPServer.WaitForAckAsync(
endPoint.Address.ToString(), endPoint.Port, timeout); endPoint.Address.ToString(), taskID, endPoint.Port, timeout);
if (!udpWriteAck.IsSuccessful) return new(udpWriteAck.Error); if (!udpWriteAck.IsSuccessful) return new(udpWriteAck.Error);
return udpWriteAck.Value.IsSuccessful; return udpWriteAck.Value.IsSuccessful;
@ -339,14 +351,15 @@ public class UDPClientPool
/// <param name="dataArray">[TODO:parameter]</param> /// <param name="dataArray">[TODO:parameter]</param>
/// <param name="timeout">[TODO:parameter]</param> /// <param name="timeout">[TODO:parameter]</param>
/// <returns>[TODO:return]</returns> /// <returns>[TODO:return]</returns>
public static async ValueTask<Result<bool>> WriteAddr(IPEndPoint endPoint, UInt32 devAddr, byte[] dataArray, int timeout = 1000) public static async ValueTask<Result<bool>> WriteAddr(
IPEndPoint endPoint, int taskID, UInt32 devAddr, byte[] dataArray, int timeout = 1000)
{ {
var ret = false; var ret = false;
var opts = new SendAddrPackOptions(); var opts = new SendAddrPackOptions();
opts.BurstType = BurstType.FixedBurst; opts.BurstType = BurstType.FixedBurst;
opts.CommandID = 0; opts.CommandID = Convert.ToByte(taskID);
opts.Address = devAddr; opts.Address = devAddr;
// Check Msg Bus // Check Msg Bus
@ -377,7 +390,7 @@ public class UDPClientPool
if (!ret) return new(new Exception("Send data package failed!")); if (!ret) return new(new Exception("Send data package failed!"));
// Wait for Write Ack // Wait for Write Ack
var udpWriteAck = await MsgBus.UDPServer.WaitForAckAsync(endPoint.Address.ToString(), endPoint.Port, timeout); var udpWriteAck = await MsgBus.UDPServer.WaitForAckAsync(endPoint.Address.ToString(), taskID, endPoint.Port, timeout);
if (!udpWriteAck.IsSuccessful) return new(udpWriteAck.Error); if (!udpWriteAck.IsSuccessful) return new(udpWriteAck.Error);
if (!udpWriteAck.Value.IsSuccessful) if (!udpWriteAck.Value.IsSuccessful)

View File

@ -22,6 +22,11 @@ public class UDPData
/// 发送来源的端口号 /// 发送来源的端口号
/// </summary> /// </summary>
public required int Port { get; set; } public required int Port { get; set; }
/// <summary>
/// 任务ID
/// </summary>
public required int TaskID { get; set; }
/// <summary> /// <summary>
/// 接受到的数据 /// 接受到的数据
/// </summary> /// </summary>
@ -44,6 +49,7 @@ public class UDPData
DateTime = this.DateTime, DateTime = this.DateTime,
Address = new string(this.Address), Address = new string(this.Address),
Port = this.Port, Port = this.Port,
TaskID = this.TaskID,
Data = cloneData, Data = cloneData,
HasRead = this.HasRead HasRead = this.HasRead
}; };
@ -119,6 +125,7 @@ public class UDPServer
/// 异步寻找目标发送的内容 /// 异步寻找目标发送的内容
/// </summary> /// </summary>
/// <param name="ipAddr"> 目标IP地址 </param> /// <param name="ipAddr"> 目标IP地址 </param>
/// <param name="taskID">[TODO:parameter]</param>
/// <param name="timeout">超时时间</param> /// <param name="timeout">超时时间</param>
/// <param name="cycle">延迟时间</param> /// <param name="cycle">延迟时间</param>
/// <param name="callerName">调用函数名称</param> /// <param name="callerName">调用函数名称</param>
@ -129,13 +136,14 @@ public class UDPServer
/// Optional 存在时,为最先收到的数据 /// Optional 存在时,为最先收到的数据
/// </returns> /// </returns>
public async ValueTask<Optional<UDPData>> FindDataAsync( public async ValueTask<Optional<UDPData>> FindDataAsync(
string ipAddr, int timeout = 1000, int cycle = 0, string ipAddr, int taskID, int timeout = 1000, int cycle = 0,
[CallerMemberName] string callerName = "", [CallerMemberName] string callerName = "",
[CallerLineNumber] int callerLineNum = 0) [CallerLineNumber] int callerLineNum = 0
)
{ {
UDPData? data = null; UDPData? data = null;
logger.Debug($"Caller \"{callerName}|{callerLineNum}\": Try to find {ipAddr} UDP Data"); logger.Debug($"Caller \"{callerName}|{callerLineNum}\": Try to find {ipAddr}-{taskID} UDP Data");
var startTime = DateTime.Now; var startTime = DateTime.Now;
var isTimeout = false; var isTimeout = false;
@ -149,8 +157,8 @@ public class UDPServer
timeleft = TimeSpan.FromMilliseconds(timeout) - elapsed; timeleft = TimeSpan.FromMilliseconds(timeout) - elapsed;
using (await udpData.AcquireWriteLockAsync(timeleft)) using (await udpData.AcquireWriteLockAsync(timeleft))
{ {
if (udpData.ContainsKey(ipAddr) && if (udpData.ContainsKey($"{ipAddr}-{taskID}") &&
udpData.TryGetValue(ipAddr, out var dataQueue) && udpData.TryGetValue($"{ipAddr}-{taskID}", out var dataQueue) &&
dataQueue.Count > 0) dataQueue.Count > 0)
{ {
data = dataQueue.Dequeue(); data = dataQueue.Dequeue();
@ -177,10 +185,11 @@ public class UDPServer
/// 获取还未被读取的数据列表 /// 获取还未被读取的数据列表
/// </summary> /// </summary>
/// <param name="ipAddr">IP地址</param> /// <param name="ipAddr">IP地址</param>
/// <param name="taskID">[TODO:parameter]</param>
/// <param name="timeout">超时时间</param> /// <param name="timeout">超时时间</param>
/// <param name="cycle">延迟时间</param> /// <param name="cycle">延迟时间</param>
/// <returns>数据列表</returns> /// <returns>数据列表</returns>
public async ValueTask<Optional<List<UDPData>>> GetDataArrayAsync(string ipAddr, int timeout = 1000, int cycle = 0) public async ValueTask<Optional<List<UDPData>>> GetDataArrayAsync(string ipAddr, int taskID, int timeout = 1000, int cycle = 0)
{ {
List<UDPData>? data = null; List<UDPData>? data = null;
@ -196,8 +205,8 @@ public class UDPServer
timeleft = TimeSpan.FromMilliseconds(timeout) - elapsed; timeleft = TimeSpan.FromMilliseconds(timeout) - elapsed;
using (await udpData.AcquireReadLockAsync(timeleft)) using (await udpData.AcquireReadLockAsync(timeleft))
{ {
if (udpData.ContainsKey(ipAddr) && if (udpData.ContainsKey($"{ipAddr}-{taskID}") &&
udpData.TryGetValue(ipAddr, out var dataQueue) && udpData.TryGetValue($"{ipAddr}-{taskID}", out var dataQueue) &&
dataQueue.Count > 0) dataQueue.Count > 0)
{ {
data = dataQueue.ToList(); data = dataQueue.ToList();
@ -226,9 +235,9 @@ public class UDPServer
/// <param name="timeout">超时时间范围</param> /// <param name="timeout">超时时间范围</param>
/// <returns>接收响应包</returns> /// <returns>接收响应包</returns>
public async ValueTask<Result<WebProtocol.RecvRespPackage>> WaitForAckAsync public async ValueTask<Result<WebProtocol.RecvRespPackage>> WaitForAckAsync
(string address, int port = -1, int timeout = 1000) (string address, int taskID, int port = -1, int timeout = 1000)
{ {
var data = await FindDataAsync(address, timeout); var data = await FindDataAsync(address, taskID, timeout);
if (!data.HasValue) if (!data.HasValue)
return new(new Exception("Get None even after time out!")); return new(new Exception("Get None even after time out!"));
@ -251,9 +260,9 @@ public class UDPServer
/// <param name="timeout">超时时间范围</param> /// <param name="timeout">超时时间范围</param>
/// <returns>接收数据包</returns> /// <returns>接收数据包</returns>
public async ValueTask<Result<RecvDataPackage>> WaitForDataAsync public async ValueTask<Result<RecvDataPackage>> WaitForDataAsync
(string address, int port = -1, int timeout = 1000) (string address, int taskID, int port = -1, int timeout = 1000)
{ {
var data = await FindDataAsync(address, timeout); var data = await FindDataAsync(address, taskID, timeout);
if (!data.HasValue) if (!data.HasValue)
return new(new Exception("Get None even after time out!")); return new(new Exception("Get None even after time out!"));
@ -284,7 +293,7 @@ public class UDPServer
// Handle Package // Handle Package
var udpData = RecordUDPData(bytes, remoteEP); var udpData = RecordUDPData(bytes, remoteEP, Convert.ToInt32(bytes[1]));
PrintData(udpData); PrintData(udpData);
BEGIN_RECEIVE: BEGIN_RECEIVE:
@ -308,7 +317,7 @@ public class UDPServer
else { return false; } else { return false; }
} }
private UDPData RecordUDPData(byte[] bytes, IPEndPoint remoteEP) private UDPData RecordUDPData(byte[] bytes, IPEndPoint remoteEP, int taskID)
{ {
var remoteAddress = remoteEP.Address.ToString(); var remoteAddress = remoteEP.Address.ToString();
var remotePort = remoteEP.Port; var remotePort = remoteEP.Port;
@ -316,6 +325,7 @@ public class UDPServer
{ {
Address = remoteAddress, Address = remoteAddress,
Port = remotePort, Port = remotePort,
TaskID = taskID,
Data = bytes, Data = bytes,
DateTime = DateTime.Now, DateTime = DateTime.Now,
HasRead = false, HasRead = false,
@ -324,7 +334,8 @@ public class UDPServer
using (udpData.AcquireWriteLock()) using (udpData.AcquireWriteLock())
{ {
// Record UDP Receive Data // Record UDP Receive Data
if (udpData.ContainsKey(remoteAddress) && udpData.TryGetValue(remoteAddress, out var dataQueue)) if (udpData.ContainsKey($"{remoteAddress}-{taskID}") &&
udpData.TryGetValue($"{remoteAddress}-{taskID}", out var dataQueue))
{ {
dataQueue.Enqueue(data); dataQueue.Enqueue(data);
logger.Trace("Receive data from old client"); logger.Trace("Receive data from old client");
@ -333,7 +344,7 @@ public class UDPServer
{ {
var queue = new Queue<UDPData>(); var queue = new Queue<UDPData>();
queue.Enqueue(data); queue.Enqueue(data);
udpData.Add(remoteAddress, queue); udpData.Add($"{remoteAddress}-{taskID}", queue);
logger.Trace("Receive data from new client"); logger.Trace("Receive data from new client");
} }
} }
@ -410,12 +421,12 @@ public class UDPServer
/// </summary> /// </summary>
/// <param name="ipAddr">IP地址</param> /// <param name="ipAddr">IP地址</param>
/// <returns>无</returns> /// <returns>无</returns>
public async Task ClearUDPData(string ipAddr) public async Task ClearUDPData(string ipAddr, int taskID)
{ {
using (await udpData.AcquireWriteLockAsync()) using (await udpData.AcquireWriteLockAsync())
{ {
if (udpData.ContainsKey(ipAddr) && if (udpData.ContainsKey($"{ipAddr}-{taskID}") &&
udpData.TryGetValue(ipAddr, out var dataQueue) && udpData.TryGetValue($"{ipAddr}-{taskID}", out var dataQueue) &&
dataQueue.Count > 0) dataQueue.Count > 0)
{ {
dataQueue.Clear(); dataQueue.Clear();

File diff suppressed because it is too large Load Diff

View File

@ -182,6 +182,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, shallowRef, onMounted } from "vue"; import { ref, computed, shallowRef, onMounted } from "vue";
import motherboardSvg from "../components/equipments/svg/motherboard.svg"; import motherboardSvg from "../components/equipments/svg/motherboard.svg";
import buttonSvg from "../components//equipments/svg/button.svg";
// Props // Props
interface Props { interface Props {
@ -219,6 +220,7 @@ const availableComponents = [
{ type: "SMA", name: "SMA连接器" }, { type: "SMA", name: "SMA连接器" },
{ type: "MotherBoard", name: "主板" }, { type: "MotherBoard", name: "主板" },
{ type: "PG2L100H_FBG676", name: "PG2L100H FBG676芯片" }, { type: "PG2L100H_FBG676", name: "PG2L100H FBG676芯片" },
{ type: "BaseBoard", name: "通用底板" },
]; ];
// --- --- // --- ---
@ -233,6 +235,13 @@ const availableTemplates = ref([
path: "/EquipmentTemplates/PG2L100H_Pango100pro.json", path: "/EquipmentTemplates/PG2L100H_Pango100pro.json",
thumbnailUrl: motherboardSvg, thumbnailUrl: motherboardSvg,
}, },
{
name: "矩阵键盘",
id: "MatrixKey",
description: "包含4x4共16个按键的矩阵键盘",
path: "/EquipmentTemplates/MatrixKey.json",
thumbnailUrl: buttonSvg,
},
]); ]);
// / // /
@ -372,6 +381,7 @@ async function addTemplate(template: any) {
id: template.id, id: template.id,
name: template.name, name: template.name,
template: templateData, template: templateData,
capsPage: template.capsPage
}); });
// //

View File

@ -1,7 +1,8 @@
<template> <template>
<div class="flex-1 h-full w-full bg-base-200 relative overflow-hidden diagram-container" ref="canvasContainer" <div class="flex-1 h-full w-full bg-base-200 relative overflow-hidden diagram-container" ref="canvasContainer"
@mousedown="handleCanvasMouseDown" @mousedown.middle.prevent="startMiddleDrag" @wheel.prevent="onZoom" @mousedown="handleCanvasMouseDown" @mousedown.middle.prevent="startMiddleDrag" @wheel.prevent="onZoom"
@contextmenu.prevent="handleContextMenu"> <!-- 工具栏 --> @contextmenu.prevent="handleContextMenu">
<!-- 工具栏 -->
<div class="absolute top-2 right-2 flex gap-2 z-30"> <div class="absolute top-2 right-2 flex gap-2 z-30">
<button class="btn btn-sm btn-primary" @click="openDiagramFileSelector"> <button class="btn btn-sm btn-primary" @click="openDiagramFileSelector">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24" <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24"
@ -32,7 +33,7 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /> d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg> </svg>
{{ props.showDocPanel ? '属性面板' : '文档' }} {{ props.showDocPanel ? "属性面板" : "文档" }}
</button> </button>
</div> </div>
@ -64,16 +65,23 @@
'component-disabled': !component.isOn, 'component-disabled': !component.isOn,
'component-hidepins': component.hidepins, 'component-hidepins': component.hidepins,
}" :style="{ }" :style="{
top: component.y + 'px', top: component.y + 'px',
left: component.x + 'px', left: component.x + 'px',
zIndex: component.index ?? 0, zIndex: component.index ?? 0,
transform: component.rotate transform: component.rotate
? `rotate(${component.rotate}deg)` ? `rotate(${component.rotate}deg)`
: 'none', : 'none',
opacity: component.isOn ? 1 : 0.6, opacity: component.isOn ? 1 : 0.6,
display: 'block', display: 'block',
}" @mousedown.left.stop="startComponentDrag($event, component)" @mouseover="hoveredComponent = component.id" }" @mousedown.left.stop="startComponentDrag($event, component)" @mouseover="
@mouseleave="hoveredComponent = null"> (event) => {
hoveredComponent = component.id;
}
" @mouseleave="
(event) => {
hoveredComponent = null;
}
">
<!-- 动态渲染组件 --> <!-- 动态渲染组件 -->
<component :is="getComponentDefinition(component.type)" v-if="props.componentModules[component.type]" <component :is="getComponentDefinition(component.type)" v-if="props.componentModules[component.type]"
v-bind="prepareComponentProps(component.attrs || {}, component.id)" @update:bindKey=" v-bind="prepareComponentProps(component.attrs || {}, component.id)" @update:bindKey="
@ -98,10 +106,10 @@
<!-- 通知组件 --> <!-- 通知组件 -->
<div v-if="showNotification" class="toast toast-top toast-center z-50 w-fit-content"> <div v-if="showNotification" class="toast toast-top toast-center z-50 w-fit-content">
<div :class="`alert ${notificationType === 'success' <div :class="`alert ${notificationType === 'success'
? 'alert-success' ? 'alert-success'
: notificationType === 'error' : notificationType === 'error'
? 'alert-error' ? 'alert-error'
: 'alert-info' : 'alert-info'
}`"> }`">
<span>{{ notificationMessage }}</span> <span>{{ notificationMessage }}</span>
</div> </div>
@ -173,7 +181,7 @@ const emit = defineEmits([
// //
const props = defineProps<{ const props = defineProps<{
componentModules: Record<string, any>; componentModules: Record<string, any>;
showDocPanel?: boolean; // showDocPanel?: boolean; //
}>(); }>();
// --- --- // --- ---
@ -607,7 +615,7 @@ function onComponentDrag(e: MouseEvent) {
function stopComponentDrag() { function stopComponentDrag() {
// //
if (draggingComponentId.value) { if (draggingComponentId.value) {
console.log(`组件拖拽结束: ${draggingComponentId.value}`); // console.log(`: ${draggingComponentId.value}`);
// //
saveDiagramData(diagramData.value); saveDiagramData(diagramData.value);

View File

@ -13,6 +13,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useDialogStore } from "@/stores/dialog"; import { useDialogStore } from "@/stores/dialog";
const dialog = useDialogStore(); const dialog = useDialogStore();
dialog.enableDialog = true;
</script> </script>
<style scoped lang="postcss"> <style scoped lang="postcss">

View File

@ -0,0 +1,42 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" :width="width" :height="height" :class="$attrs">
<rect :width="width" :height="height" :rx="props.roundCorner" fill="#222222" />
</svg>
<Teleport to="#ComponentCapabilities" v-if="selectecComponentID === props.componentId && !!slot.default">
<slot></slot>
</Teleport>
</template>
<script lang="ts" setup>
import { ref, computed, inject } from "vue";
import { CanvasCurrentSelectedComponentID } from "../InjectKeys";
export interface Props {
size?: number;
width?: number;
height?: number;
roundCorner?: number;
componentId?: string;
}
const slot = defineSlots();
const props = withDefaults(defineProps<Props>(), getDefaultProps());
const selectecComponentID = inject(CanvasCurrentSelectedComponentID, ref(null));
//
const width = computed(() => props.width * props.size);
const height = computed(() => props.height * props.size);
</script>
<script lang="ts">
export function getDefaultProps(): Props {
return {
size: 1,
width: 200,
height: 200,
roundCorner: 20,
};
}
</script>
<style scoped lang="postcss"></style>

View File

@ -385,6 +385,7 @@ const currentWaveformPath = computed(() => {
function selectWaveform(index: number) { function selectWaveform(index: number) {
currentWaveformIndex.value = index; currentWaveformIndex.value = index;
updateModelValue(); updateModelValue();
applyOutputWave();
} }
async function applyOutputWave() { async function applyOutputWave() {
@ -408,7 +409,7 @@ async function applyOutputWave() {
eqps.boardPort, eqps.boardPort,
0, 0,
currentWaveformIndex.value, currentWaveformIndex.value,
toInteger(frequency.value * Math.pow(2, 32 - 20)), toInteger((frequency.value * Math.pow(2, 32 - 20)) / 10),
); );
if (!ret) { if (!ret) {
dialog.error("应用失败"); dialog.error("应用失败");
@ -424,7 +425,7 @@ async function applyOutputWave() {
toInteger((phase.value * 4096) / 360), toInteger((phase.value * 4096) / 360),
); );
if (ret) { if (ret) {
dialog.info("应用成功"); // dialog.info("");
} else { } else {
dialog.error("应用失败"); dialog.error("应用失败");
} }
@ -455,6 +456,7 @@ function increaseFrequency() {
frequency.value = parseFloat(frequency.value.toFixed(1)); // frequency.value = parseFloat(frequency.value.toFixed(1)); //
frequencyInput.value = formatFrequency(frequency.value); frequencyInput.value = formatFrequency(frequency.value);
updateModelValue(); updateModelValue();
applyOutputWave();
} }
function decreaseFrequency() { function decreaseFrequency() {
@ -475,6 +477,7 @@ function decreaseFrequency() {
frequency.value = parseFloat(frequency.value.toFixed(1)); // frequency.value = parseFloat(frequency.value.toFixed(1)); //
frequencyInput.value = formatFrequency(frequency.value); frequencyInput.value = formatFrequency(frequency.value);
updateModelValue(); updateModelValue();
applyOutputWave();
} }
function applyFrequencyInput() { function applyFrequencyInput() {
@ -505,6 +508,7 @@ function increasePhase() {
} }
phaseInput.value = phase.value.toString(); phaseInput.value = phase.value.toString();
updateModelValue(); updateModelValue();
applyOutputWave();
} }
function decreasePhase() { function decreasePhase() {
@ -514,6 +518,7 @@ function decreasePhase() {
} }
phaseInput.value = phase.value.toString(); phaseInput.value = phase.value.toString();
updateModelValue(); updateModelValue();
applyOutputWave();
} }
function applyPhaseInput() { function applyPhaseInput() {

View File

@ -80,16 +80,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from "vue"; import { ref, onMounted, onUnmounted, computed } from "vue";
import Pin from "./Pin.vue"; import Pin from "./Pin.vue";
import { useEquipments } from "@/stores/equipments";
import { useDialogStore } from "@/stores/dialog";
import { useConstraintsStore } from "../../stores/constraints"; import { useConstraintsStore } from "../../stores/constraints";
const { notifyConstraintChange } = useConstraintsStore(); import { isNull, isUndefined } from "mathjs";
import z from "zod";
// Pin import { toNumber } from "lodash";
const pinRefs = ref<Record<string, any>>({});
// //
interface ButtonProps { export interface ButtonProps {
size?: number; size: number;
bindKey?: string;
componentId?: string; componentId?: string;
pins?: { pins?: {
pinId: string; pinId: string;
@ -97,21 +97,20 @@ interface ButtonProps {
x: number; x: number;
y: number; y: number;
}[]; }[];
bindKey?: string;
bindMatrixKey?: string;
} }
const props = withDefaults(defineProps<ButtonProps>(), { const props = defineProps<ButtonProps>();
size: 1,
bindKey: "", // Global Stores
componentId: "button-default", const constrainsts = useConstraintsStore();
pins: () => [ const dialog = useDialogStore();
{ const eqps = useEquipments();
pinId: "BTN",
constraint: "", // Pin
x: 80, const pinRefs = ref<Record<string, any>>({});
y: 140,
},
],
});
// //
const width = computed(() => 160 * props.size); const width = computed(() => 160 * props.size);
@ -153,15 +152,27 @@ function toggleButtonState(isPressed: boolean) {
isKeyPressed.value = isPressed; isKeyPressed.value = isPressed;
btnHeight.value = isPressed ? 180 : 200; btnHeight.value = isPressed ? 180 : 200;
//
if (eqps.enableMatrixKey) {
const ret = eqps.setMatrixKey(props.bindMatrixKey, isPressed);
if (ret) eqps.matrixKeypadSetKeyStates(eqps.matrixKeyStates);
else
dialog.error(
`绑定的矩阵键盘值只能是0 ~ 15而不是: ${props.bindMatrixKey}`,
);
}
// //
if (isPressed) { if (isPressed) {
emit("press"); emit("press");
//
//
if (props.pins) { if (props.pins) {
//
//
props.pins.forEach((pin) => { props.pins.forEach((pin) => {
if (pin.constraint) { if (pin.constraint) {
notifyConstraintChange(pin.constraint, "high"); constrainsts.notifyConstraintChange(pin.constraint, "high");
} }
}); });
} }
@ -172,7 +183,7 @@ function toggleButtonState(isPressed: boolean) {
if (props.pins) { if (props.pins) {
props.pins.forEach((pin) => { props.pins.forEach((pin) => {
if (pin.constraint) { if (pin.constraint) {
notifyConstraintChange(pin.constraint, "low"); constrainsts.notifyConstraintChange(pin.constraint, "low");
} }
}); });
} }
@ -200,35 +211,37 @@ onUnmounted(() => {
defineExpose({ defineExpose({
toggleButtonState, toggleButtonState,
getInfo: () => ({ getInfo: () => ({
//
bindKey: props.bindKey, bindKey: props.bindKey,
componentId: props.componentId, componentId: props.componentId,
pins: props.pins, pins: props.pins,
}), }),
// //
getPinPosition: (pinId: string) => { getPinPosition: (pinId: string) => {
console.log(`[MechanicalButton] 调用getPinPosition寻找pinId: ${pinId}`); console.debug(`[MechanicalButton] 调用getPinPosition寻找pinId: ${pinId}`);
console.log( console.debug(
`[MechanicalButton] 组件ID: ${props.componentId}, 当前尺寸: ${props.size}, 组件宽高: ${width.value}x${height.value}`, `[MechanicalButton] 组件ID: ${props.componentId}, 当前尺寸: ${props.size}, 组件宽高: ${width.value}x${height.value}`,
); );
console.log(`[MechanicalButton] 当前存在的pins:`, props.pins); console.debug(`[MechanicalButton] 当前存在的pins:`, props.pins);
// ID // ID
if (props.pins && props.pins.length > 0) { if (props.pins && props.pins.length > 0) {
const customPin = props.pins.find((p) => p.pinId === pinId); const customPin = props.pins.find((p) => p.pinId === pinId);
if (customPin) { if (customPin) {
console.log(`[MechanicalButton] 找到自定义引脚: ${pinId},配置位置:`, { console.debug(
x: customPin.x, `[MechanicalButton] 找到自定义引脚: ${pinId},配置位置:`,
y: customPin.y, {
}); x: customPin.x,
y: customPin.y,
},
);
// //
// xysize=1size // xysize=1size
const scaledX = customPin.x * props.size; const scaledX = customPin.x * props.size;
const scaledY = customPin.y * props.size; const scaledY = customPin.y * props.size;
console.log(`[MechanicalButton] 返回缩放后的坐标:`, { console.debug(`[MechanicalButton] 返回缩放后的坐标:`, {
x: scaledX, x: scaledX,
y: scaledY, y: scaledY,
}); });
@ -237,12 +250,12 @@ defineExpose({
y: scaledY, y: scaledY,
}; };
} else { } else {
console.log(`[MechanicalButton] 未找到pinId: ${pinId}的引脚配置`); console.debug(`[MechanicalButton] 未找到pinId: ${pinId}的引脚配置`);
} }
} else { } else {
console.log(`[MechanicalButton] 没有配置任何引脚`); console.debug(`[MechanicalButton] 没有配置任何引脚`);
} }
console.log(`[MechanicalButton] 返回null未找到引脚`); console.debug(`[MechanicalButton] 返回null未找到引脚`);
return null; return null;
}, },
}); });
@ -253,7 +266,6 @@ defineExpose({
export function getDefaultProps() { export function getDefaultProps() {
return { return {
size: 1, size: 1,
bindKey: "",
pins: [ pins: [
{ {
pinId: "BTN", pinId: "BTN",
@ -262,6 +274,8 @@ export function getDefaultProps() {
y: 140, y: 140,
}, },
], ],
bindKey: "",
bindMatrixKey: "",
}; };
} }
</script> </script>

View File

@ -23,7 +23,7 @@ import { toNumber } from "lodash";
// //
export interface MotherBoardProps { export interface MotherBoardProps {
size?: number; size: number;
boardAddr?: string; boardAddr?: string;
boardPort?: string; boardPort?: string;
componentId?: string; componentId?: string;
@ -63,7 +63,6 @@ export function getDefaultProps(): MotherBoardProps {
size: 1, size: 1,
boardAddr: "127.0.0.1", boardAddr: "127.0.0.1",
boardPort: "1234", boardPort: "1234",
componentId: "DefaultMotherBoardID",
}; };
} }
</script> </script>

View File

@ -9,24 +9,40 @@
IDCode: 0x{{ jtagIDCode.toString(16).padStart(8, "0").toUpperCase() }} IDCode: 0x{{ jtagIDCode.toString(16).padStart(8, "0").toUpperCase() }}
</p> </p>
<button class="btn btn-circle w-6 h-6" :onclick="getIDCode"> <button class="btn btn-circle w-6 h-6" :onclick="getIDCode">
<svg class="icon opacity-70 fill-primary" viewBox="0 0 1024 1024" version="1.1" <svg
xmlns="http://www.w3.org/2000/svg" p-id="4865" width="200" height="200"> class="icon opacity-70 fill-primary"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="4865"
width="200"
height="200"
>
<path <path
d="M894.481158 505.727133c0 49.589418-9.711176 97.705276-28.867468 143.007041-18.501376 43.74634-44.98454 83.031065-78.712713 116.759237-33.728172 33.728172-73.012897 60.211337-116.759237 78.712713-45.311998 19.156292-93.417623 28.877701-143.007041 28.877701s-97.695043-9.721409-142.996808-28.877701c-43.756573-18.501376-83.031065-44.98454-116.76947-78.712713-33.728172-33.728172-60.211337-73.012897-78.712713-116.759237-19.156292-45.301765-28.867468-93.417623-28.867468-143.007041 0-49.579185 9.711176-97.695043 28.867468-142.996808 18.501376-43.74634 44.98454-83.031065 78.712713-116.759237 33.738405-33.728172 73.012897-60.211337 116.76947-78.712713 45.301765-19.166525 93.40739-28.877701 142.996808-28.877701 52.925397 0 104.008842 11.010775 151.827941 32.745798 46.192042 20.977777 86.909395 50.79692 121.016191 88.608084 4.389984 4.860704 8.646937 9.854439 12.781094 14.97097l0-136.263453c0-11.307533 9.168824-20.466124 20.466124-20.466124 11.307533 0 20.466124 9.15859 20.466124 20.466124l0 183.64253c0 5.433756-2.148943 10.632151-5.986341 14.46955-3.847631 3.837398-9.046027 5.996574-14.479783 5.996574l-183.64253-0.020466c-11.307533 0-20.466124-9.168824-20.466124-20.466124 0-11.307533 9.168824-20.466124 20.466124-20.466124l132.293025 0.020466c-3.960195-4.952802-8.063653-9.782807-12.289907-14.479783-30.320563-33.605376-66.514903-60.098773-107.549481-78.753645-42.467207-19.289322-87.850837-29.072129-134.902456-29.072129-87.195921 0-169.172981 33.9533-230.816946 95.597265-61.654198 61.654198-95.597265 143.621025-95.597265 230.816946s33.943067 169.172981 95.597265 230.816946c61.643965 61.654198 143.621025 95.607498 230.816946 95.607498s169.172981-33.9533 230.816946-95.607498c61.654198-61.643965 95.597265-143.621025 95.597265-230.816946 0-11.2973 9.168824-20.466124 20.466124-20.466124C885.322567 485.261009 894.481158 494.429833 894.481158 505.727133z" d="M894.481158 505.727133c0 49.589418-9.711176 97.705276-28.867468 143.007041-18.501376 43.74634-44.98454 83.031065-78.712713 116.759237-33.728172 33.728172-73.012897 60.211337-116.759237 78.712713-45.311998 19.156292-93.417623 28.877701-143.007041 28.877701s-97.695043-9.721409-142.996808-28.877701c-43.756573-18.501376-83.031065-44.98454-116.76947-78.712713-33.728172-33.728172-60.211337-73.012897-78.712713-116.759237-19.156292-45.301765-28.867468-93.417623-28.867468-143.007041 0-49.579185 9.711176-97.695043 28.867468-142.996808 18.501376-43.74634 44.98454-83.031065 78.712713-116.759237 33.738405-33.728172 73.012897-60.211337 116.76947-78.712713 45.301765-19.166525 93.40739-28.877701 142.996808-28.877701 52.925397 0 104.008842 11.010775 151.827941 32.745798 46.192042 20.977777 86.909395 50.79692 121.016191 88.608084 4.389984 4.860704 8.646937 9.854439 12.781094 14.97097l0-136.263453c0-11.307533 9.168824-20.466124 20.466124-20.466124 11.307533 0 20.466124 9.15859 20.466124 20.466124l0 183.64253c0 5.433756-2.148943 10.632151-5.986341 14.46955-3.847631 3.837398-9.046027 5.996574-14.479783 5.996574l-183.64253-0.020466c-11.307533 0-20.466124-9.168824-20.466124-20.466124 0-11.307533 9.168824-20.466124 20.466124-20.466124l132.293025 0.020466c-3.960195-4.952802-8.063653-9.782807-12.289907-14.479783-30.320563-33.605376-66.514903-60.098773-107.549481-78.753645-42.467207-19.289322-87.850837-29.072129-134.902456-29.072129-87.195921 0-169.172981 33.9533-230.816946 95.597265-61.654198 61.654198-95.597265 143.621025-95.597265 230.816946s33.943067 169.172981 95.597265 230.816946c61.643965 61.654198 143.621025 95.607498 230.816946 95.607498s169.172981-33.9533 230.816946-95.607498c61.654198-61.643965 95.597265-143.621025 95.597265-230.816946 0-11.2973 9.168824-20.466124 20.466124-20.466124C885.322567 485.261009 894.481158 494.429833 894.481158 505.727133z"
p-id="4866"></path> p-id="4866"
></path>
</svg> </svg>
</button> </button>
</div> </div>
</div> </div>
<div class="divider"></div> <div class="divider"></div>
<UploadCard class="bg-base-200" :upload-event="eqps.jtagUploadBitstream" <UploadCard
:download-event="eqps.jtagDownloadBitstream" :bitstream-file="eqps.jtagBitstream" class="bg-base-200"
@update:bitstream-file="handleBitstreamChange"> :upload-event="eqps.jtagUploadBitstream"
:download-event="eqps.jtagDownloadBitstream"
:bitstream-file="eqps.jtagBitstream"
@update:bitstream-file="handleBitstreamChange"
>
</UploadCard> </UploadCard>
<div class="divider"></div> <div class="divider"></div>
<div class="w-full"> <div class="w-full">
<legend class="fieldset-legend text-sm mb-0.3">Jtag运行频率</legend> <legend class="fieldset-legend text-sm mb-0.3">Jtag运行频率</legend>
<select class="select w-full" @change="handleSelectJtagSpeed" :value="props.jtagFreq"> <select
class="select w-full"
@change="handleSelectJtagSpeed"
:value="props.jtagFreq"
>
<option v-for="option in selectJtagSpeedOptions" :value="option.id"> <option v-for="option in selectJtagSpeedOptions" :value="option.id">
{{ option.text }} {{ option.text }}
</option> </option>
@ -35,15 +51,48 @@
<div class="flex flex-row items-center"> <div class="flex flex-row items-center">
<fieldset class="fieldset w-70"> <fieldset class="fieldset w-70">
<legend class="fieldset-legend text-sm">边界扫描刷新率 / Hz</legend> <legend class="fieldset-legend text-sm">边界扫描刷新率 / Hz</legend>
<input type="number" class="input validator" required placeholder="Type a number between 1 to 1000" min="1" <input
max="1000" v-model="jtagBoundaryScanFreq" title="Type a number between 1 to 1000" /> type="number"
class="input validator"
required
placeholder="Type a number between 1 to 1000"
min="1"
max="1000"
v-model="jtagBoundaryScanFreq"
title="Type a number between 1 to 1000"
/>
<p class="validator-hint">输入一个1 ~ 1000的数</p> <p class="validator-hint">输入一个1 ~ 1000的数</p>
</fieldset> </fieldset>
<button class="btn btn-primary grow mx-4" :class="eqps.enableJtagBoundaryScan ? '' : 'btn-soft'" <button
:onclick="toggleJtagBoundaryScan"> class="btn btn-primary grow mx-4"
:class="eqps.enableJtagBoundaryScan ? '' : 'btn-soft'"
:onclick="toggleJtagBoundaryScan"
>
{{ eqps.enableJtagBoundaryScan ? "关闭边界扫描" : "启动边界扫描" }} {{ eqps.enableJtagBoundaryScan ? "关闭边界扫描" : "启动边界扫描" }}
</button> </button>
</div> </div>
<div class="divider"></div>
<h1 class="font-bold text-center text-2xl">外设</h1>
<div class="flex flex-row justify-around">
<div class="flex flex-row">
<input
type="checkbox"
class="checkbox"
:checked="eqps.enableMatrixKey"
@change="handleMatrixkeyCheckboxChange"
/>
<p class="mx-2">启用矩阵键盘</p>
</div>
<div class="flex flex-row">
<input
type="checkbox"
class="checkbox"
:checked="eqps.enablePower"
@change="handlePowerCheckboxChange"
/>
<p class="mx-2">启用电源</p>
</div>
</div>
</div> </div>
</template> </template>
@ -100,12 +149,30 @@ function handleSelectJtagSpeed(event: Event) {
emits("changeJtagFreq", target.value); emits("changeJtagFreq", target.value);
} }
async function toggleJtagBoundaryScan() { async function handleMatrixkeyCheckboxChange(event: Event) {
if (eqps.jtagClientMutex.isLocked()) { const target = event.target as HTMLInputElement;
dialog.warn("Jtag正在被占用"); if (target.checked) {
return; const ret = await eqps.matrixKeypadEnable(true);
if (!ret) {
}
} else {
const ret = await eqps.matrixKeypadEnable(false);
if (!ret) {
}
} }
}
async function handlePowerCheckboxChange(event: Event) {
const target = event.target as HTMLInputElement;
const ret = await eqps.powerSetOnOff(target.checked);
if (target.checked) {
eqps.enablePower = ret;
} else {
eqps.enablePower = !ret;
}
}
async function toggleJtagBoundaryScan() {
eqps.enableJtagBoundaryScan = !eqps.enableJtagBoundaryScan; eqps.enableJtagBoundaryScan = !eqps.enableJtagBoundaryScan;
} }

View File

@ -177,26 +177,26 @@ defineExpose({
getPinPosition: (pinId: string) => { getPinPosition: (pinId: string) => {
// ID // ID
if (props.pins && props.pins.length > 0) { if (props.pins && props.pins.length > 0) {
console.log('SMT_LED查找Pin ID:', pinId); // console.log('SMT_LEDPin ID:', pinId);
console.log('SMT_LED组件尺寸:', props.size, '宽高:', width.value, 'x', height.value); // console.log('SMT_LED:', props.size, ':', width.value, 'x', height.value);
const customPin = props.pins.find(p => p.pinId === pinId); const customPin = props.pins.find(p => p.pinId === pinId);
console.log('找到的引脚配置:', customPin); // console.log(':', customPin);
if (customPin) { if (customPin) {
// //
const scaledX = customPin.x * props.size; const scaledX = customPin.x * props.size;
const scaledY = customPin.y * props.size; const scaledY = customPin.y * props.size;
console.log('使用Pin缩放后的坐标:', scaledX, scaledY); // console.log('使Pin:', scaledX, scaledY);
return { return {
x: scaledX, x: scaledX,
y: scaledY y: scaledY
}; };
} }
console.log('未找到匹配的引脚'); // console.log('');
return null; return null;
} }
console.log('没有引脚配置'); // console.log('');
return null; return null;
} }
}); });

View File

@ -1,40 +1,61 @@
import { ref, computed } from 'vue' import { ref, reactive, watchPostEffect, computed } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { isUndefined } from 'lodash'; import { isUndefined } from 'lodash';
import Queue from 'yocto-queue';
export const useDialogStore = defineStore('dialog', () => { export const useDialogStore = defineStore('dialog', () => {
type Title = "Error" | "Info" | "Warn"; type Title = "Error" | "Info" | "Warn";
const enableDialog = ref(false);
const isDialogOpen = ref(false); const isDialogOpen = ref(false);
const dialogTitle = ref<Title>("Error"); const dialogTitle = ref<Title>("Error");
const dialogContent = ref("这是一个错误"); const dialogContent = ref("这是一个错误");
const contentQueue = new Queue<{ type: Title; content: string }>()
function openDialog(title: Title, content?: string) { function openDialog(title?: Title, content?: string) {
if (!isUndefined(content) && content.length != 0) if (isUndefined(title)) {
dialogContent.value = content; if (contentQueue.size != 0) {
dialogTitle.value = title; const dialog = contentQueue.dequeue();
isDialogOpen.value = true; if (isUndefined(dialog)) return;
openDialog(dialog.type, dialog.content);
}
} else {
if (!isUndefined(content) && content.length != 0)
dialogContent.value = content;
dialogTitle.value = title;
isDialogOpen.value = true;
}
} }
function closeDialog() { function closeDialog() {
isDialogOpen.value = false; isDialogOpen.value = false;
openDialog();
} }
function info(content?: string) { function info(content: string) {
openDialog("Info", content); contentQueue.enqueue({ type: "Info", content: content });
openDialog();
// openDialog("Info", content);
} }
function error(content?: string) { function error(content: string) {
openDialog("Error", content); contentQueue.enqueue({ type: "Error", content: content });
openDialog();
// openDialog("Error", content);
} }
function warn(content?: string) { function warn(content: string) {
openDialog("Warn", content); contentQueue.enqueue({ type: "Warn", content: content });
openDialog();
// openDialog("Warn", content);
} }
return { return {
enableDialog,
isDialogOpen, isDialogOpen,
dialogTitle, dialogTitle,
dialogContent, dialogContent,
dialogQueue: contentQueue,
openDialog, openDialog,
closeDialog, closeDialog,
info, info,

View File

@ -1,10 +1,10 @@
import { ref, watchEffect } from 'vue' import { ref, reactive, watchPostEffect } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { isString, toNumber, isUndefined } from 'lodash'; import { isString, toNumber } from 'lodash';
import { Common } from '@/Common'; import { Common } from '@/Common';
import z from "zod" import z from "zod"
import { isNumber } from 'mathjs'; import { isNumber } from 'mathjs';
import { JtagClient } from "@/APIClient"; import { JtagClient, MatrixKeyClient, PowerClient } from "@/APIClient";
import { Mutex, withTimeout } from 'async-mutex'; import { Mutex, withTimeout } from 'async-mutex';
import { useConstraintsStore } from "@/stores/constraints"; import { useConstraintsStore } from "@/stores/constraints";
import { useDialogStore } from './dialog'; import { useDialogStore } from './dialog';
@ -14,15 +14,36 @@ export const useEquipments = defineStore('equipments', () => {
const constrainsts = useConstraintsStore(); const constrainsts = useConstraintsStore();
const dialog = useDialogStore(); const dialog = useDialogStore();
// Basic Info
const boardAddr = ref("127.0.0.1"); const boardAddr = ref("127.0.0.1");
const boardPort = ref(1234); const boardPort = ref(1234);
// Jtag
const jtagBitstream = ref<File>(); const jtagBitstream = ref<File>();
const jtagBoundaryScanFreq = ref(100); const jtagBoundaryScanFreq = ref(100);
const jtagClientMutex = withTimeout(new Mutex(), 1000, new Error("JtagClient Mutex Timeout!")) const jtagClientMutex = withTimeout(new Mutex(), 1000, new Error("JtagClient Mutex Timeout!"))
const jtagClient = new JtagClient(); const jtagClient = new JtagClient();
const enableJtagBoundaryScan = ref(false); // 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();
// Power
const powerClientMutex = withTimeout(new Mutex(), 1000, new Error("Matrixkeyclient Mutex Timeout!"));
const powerClient = new PowerClient();
// Enable Setting
const enableJtagBoundaryScan = ref(false);
const enableMatrixKey = ref(false);
const enablePower = ref(false)
// Watch
watchPostEffect(async () => {
if (true === enableJtagBoundaryScan.value) jtagBoundaryScan();
});
// Parse and Set
function setAddr(address: string | undefined): boolean { function setAddr(address: string | undefined): boolean {
if (isString(address) && z.string().ip("4").safeParse(address).success) { if (isString(address) && z.string().ip("4").safeParse(address).success) {
boardAddr.value = address; boardAddr.value = address;
@ -49,9 +70,22 @@ export const useEquipments = defineStore('equipments', () => {
return false; return false;
} }
watchEffect(() => { function setMatrixKey(keyNum: number | string | undefined, keyValue: boolean): boolean {
if (enableJtagBoundaryScan.value) jtagBoundaryScan(); let _keyNum: number;
}); if (isString(keyNum)) {
_keyNum = toNumber(keyNum);
} else if (isNumber(keyNum)) {
_keyNum = keyNum;
} else {
return false;
}
if (z.number().nonnegative().max(16).safeParse(_keyNum).success) {
matrixKeyStates[_keyNum] = keyValue;
return true;
}
return false;
}
async function jtagBoundaryScan() { async function jtagBoundaryScan() {
const release = await jtagClientMutex.acquire(); const release = await jtagClientMutex.acquire();
@ -138,11 +172,78 @@ export const useEquipments = defineStore('equipments', () => {
} }
} }
async function matrixKeypadSetKeyStates(keyStates: boolean[]) {
const release = await matrixKeypadClientMutex.acquire();
console.log("set Key !!!!!!!!!!!!");
try {
const resp = await matrixKeypadClient.setMatrixKeyStatus(
boardAddr.value,
boardPort.value,
keyStates
);
return resp;
} catch (e) {
dialog.error("设置矩阵键盘时,服务器发生错误");
return false;
} finally {
release();
}
}
async function matrixKeypadEnable(enable: boolean) {
const release = await matrixKeypadClientMutex.acquire();
try {
if (enable) {
const resp = await matrixKeypadClient.enabelMatrixKey(
boardAddr.value,
boardPort.value,
);
enableMatrixKey.value = resp;
return resp;
} else {
const resp = await matrixKeypadClient.disableMatrixKey(
boardAddr.value,
boardPort.value,
);
enableMatrixKey.value = !resp;
return resp;
}
} catch (e) {
enableMatrixKey.value = false;
dialog.error("设置矩阵键盘是否启用时,服务器发生错误");
return false;
} finally {
release();
}
}
async function powerSetOnOff(enable: boolean) {
const release = await powerClientMutex.acquire();
try {
const resp = await powerClient.setPowerOnOff(
boardAddr.value,
boardPort.value,
enable
);
return resp;
} catch (e) {
dialog.error("无法开关电源");
console.error(e);
return false;
} finally {
release();
}
}
return { return {
boardAddr, boardAddr,
boardPort, boardPort,
setAddr, setAddr,
setPort, setPort,
setMatrixKey,
// Jtag
enableJtagBoundaryScan,
jtagBitstream, jtagBitstream,
jtagBoundaryScanFreq, jtagBoundaryScanFreq,
jtagClientMutex, jtagClientMutex,
@ -151,7 +252,20 @@ export const useEquipments = defineStore('equipments', () => {
jtagDownloadBitstream, jtagDownloadBitstream,
jtagGetIDCode, jtagGetIDCode,
jtagSetSpeed, jtagSetSpeed,
enableJtagBoundaryScan,
// Matrix Key
enableMatrixKey,
matrixKeyStates,
matrixKeypadClientMutex,
matrixKeypadClient,
matrixKeypadEnable,
matrixKeypadSetKeyStates,
// Power
enablePower,
powerClient,
powerClientMutex,
powerSetOnOff,
} }
}) })

View File

@ -2,60 +2,34 @@
<div class="h-screen flex flex-col overflow-hidden"> <div class="h-screen flex flex-col overflow-hidden">
<div class="flex flex-1 overflow-hidden relative"> <div class="flex flex-1 overflow-hidden relative">
<!-- 左侧图形化区域 --> <!-- 左侧图形化区域 -->
<div <div class="relative bg-base-200 overflow-hidden h-full" :style="{ width: leftPanelWidth + '%' }">
class="relative bg-base-200 overflow-hidden h-full" <DiagramCanvas ref="diagramCanvas" :componentModules="componentModules" :showDocPanel="showDocPanel"
:style="{ width: leftPanelWidth + '%' }" @component-selected="handleComponentSelected" @component-moved="handleComponentMoved"
> @component-delete="handleComponentDelete" @wire-created="handleWireCreated" @wire-deleted="handleWireDeleted"
<DiagramCanvas @diagram-updated="handleDiagramUpdated" @open-components="openComponentsMenu"
ref="diagramCanvas" @load-component-module="handleLoadComponentModule" @toggle-doc-panel="toggleDocPanel" />
:componentModules="componentModules"
:showDocPanel="showDocPanel"
@component-selected="handleComponentSelected"
@component-moved="handleComponentMoved"
@component-delete="handleComponentDelete"
@wire-created="handleWireCreated"
@wire-deleted="handleWireDeleted"
@diagram-updated="handleDiagramUpdated"
@open-components="openComponentsMenu"
@load-component-module="handleLoadComponentModule"
@toggle-doc-panel="toggleDocPanel"
/>
</div> </div>
<!-- 拖拽分割线 --> <!-- 拖拽分割线 -->
<div <div
class="resizer bg-base-100 hover:bg-primary hover:opacity-70 active:bg-primary active:opacity-90 transition-colors" class="resizer bg-base-100 hover:bg-primary hover:opacity-70 active:bg-primary active:opacity-90 transition-colors"
@mousedown="startResize" @mousedown="startResize"></div>
></div> <!-- 右侧编辑区域 --> <!-- 右侧编辑区域 -->
<div <div class="bg-base-200 h-full overflow-hidden flex flex-col" :style="{ width: 100 - leftPanelWidth + '%' }">
class="bg-base-200 h-full overflow-hidden flex flex-col"
:style="{ width: 100 - leftPanelWidth + '%' }"
>
<div class="overflow-y-auto flex-1"> <div class="overflow-y-auto flex-1">
<!-- 使用条件渲染显示不同的面板 --> <!-- 使用条件渲染显示不同的面板 -->
<PropertyPanel <PropertyPanel v-show="!showDocPanel" :componentData="selectedComponentData"
v-if="!showDocPanel" :componentConfig="selectedComponentConfig" @updateProp="updateComponentProp"
:componentData="selectedComponentData" @updateDirectProp="updateComponentDirectProp" />
:componentConfig="selectedComponentConfig" <div v-show="showDocPanel" class="doc-panel overflow-y-auto h-full">
@updateProp="updateComponentProp"
@updateDirectProp="updateComponentDirectProp"
/> <div
v-else
class="doc-panel overflow-y-auto h-full"
>
<MarkdownRenderer :content="documentContent" /> <MarkdownRenderer :content="documentContent" />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- 元器件选择组件 --> <!-- 元器件选择组件 -->
<ComponentSelector <ComponentSelector :open="showComponentsMenu" @update:open="showComponentsMenu = $event"
:open="showComponentsMenu" @add-component="handleAddComponent" @add-template="handleAddTemplate" @close="showComponentsMenu = false" />
@update:open="showComponentsMenu = $event"
@add-component="handleAddComponent"
@add-template="handleAddTemplate"
@close="showComponentsMenu = false"
/>
</div> </div>
</template> </template>
@ -80,7 +54,7 @@ const showDocPanel = ref(false);
const documentContent = ref(""); const documentContent = ref("");
// //
import { useRoute } from 'vue-router'; import { useRoute } from "vue-router";
const route = useRoute(); const route = useRoute();
// //
@ -97,29 +71,32 @@ async function toggleDocPanel() {
async function loadDocumentContent() { async function loadDocumentContent() {
try { try {
// ID // ID
const tutorialId = route.query.tutorial as string || '02'; // 02 const tutorialId = (route.query.tutorial as string) || "02"; // 02
// //
let docPath = `/doc/${tutorialId}/doc.md`; let docPath = `/doc/${tutorialId}/doc.md`;
// 线 02_key // 线 02_key
// 使 // 使
if (!tutorialId.includes('_')) { if (!tutorialId.includes("_")) {
docPath = `/doc/${tutorialId}/doc.md`; docPath = `/doc/${tutorialId}/doc.md`;
} }
// //
const response = await fetch(docPath); const response = await fetch(docPath);
if (!response.ok) { if (!response.ok) {
throw new Error(`Failed to load document: ${response.status}`); throw new Error(`Failed to load document: ${response.status}`);
} }
// //
documentContent.value = (await response.text()) documentContent.value = (await response.text()).replace(
.replace(/.\/images/gi, `/doc/${tutorialId}/images`); /.\/images/gi,
`/doc/${tutorialId}/images`,
);
} catch (error) { } catch (error) {
console.error('加载文档失败:', error); console.error("加载文档失败:", error);
documentContent.value = '# 文档加载失败\n\n无法加载请求的文档。'; } documentContent.value = "# 文档加载失败\n\n无法加载请求的文档。";
}
} }
// //
@ -176,7 +153,7 @@ async function loadComponentModule(type: string) {
[type]: module, [type]: module,
}; };
console.log(`Loaded module for ${type}:`, module); // console.log(`Loaded module for ${type}:`, module);
} catch (error) { } catch (error) {
console.error(`Failed to load component module ${type}:`, error); console.error(`Failed to load component module ${type}:`, error);
return null; return null;
@ -187,7 +164,7 @@ async function loadComponentModule(type: string) {
// //
async function handleLoadComponentModule(type: string) { async function handleLoadComponentModule(type: string) {
console.log("Handling load component module request for:", type); // console.log("Handling load component module request for:", type);
await loadComponentModule(type); await loadComponentModule(type);
} }
@ -290,9 +267,9 @@ async function handleAddComponent(componentData: {
) { ) {
try { try {
capsPage = componentModule.default.getCapabilities(); capsPage = componentModule.default.getCapabilities();
console.log(`获取到${componentData.type}组件的能力页面`); // console.log(`${componentData.type}`);
} catch (error) { } catch (error) {
console.error(`获取${componentData.type}组件能力页面失败:`, error); // console.error(`${componentData.type}:`, error);
} }
} }
@ -372,16 +349,16 @@ async function handleAddTemplate(templateData: {
// (handleAddComponent) // (handleAddComponent)
viewportCenter.x = (viewportWidth / 2 - position.x) / scale; viewportCenter.x = (viewportWidth / 2 - position.x) / scale;
viewportCenter.y = (viewportHeight / 2 - position.y) / scale; viewportCenter.y = (viewportHeight / 2 - position.y) / scale;
console.log( // console.log(
`=== 计算的视口中心: x=${viewportCenter.x}, y=${viewportCenter.y}, scale=${scale}`, // `=== : x=${viewportCenter.x}, y=${viewportCenter.y}, scale=${scale}`,
); // );
} }
} }
} catch (error) { } catch (error) {
console.error("获取视口中心位置时出错:", error); console.error("获取视口中心位置时出错:", error);
} }
console.log("=== 使用视口中心添加模板组件:", viewportCenter); // console.log("=== 使:", viewportCenter);
// //
const mainPart = templateData.template.parts[0]; const mainPart = templateData.template.parts[0];
@ -421,9 +398,9 @@ async function handleAddTemplate(templateData: {
newPart.x = viewportCenter.x + relativeX; newPart.x = viewportCenter.x + relativeX;
newPart.y = viewportCenter.y + relativeY; newPart.y = viewportCenter.y + relativeY;
console.log( // console.log(
`=== 组件[${newPart.id}]位置调整: (${oldX},${oldY}) -> (${newPart.x},${newPart.y})`, // `=== [${newPart.id}]: (${oldX},${oldY}) -> (${newPart.x},${newPart.y})`,
); // );
} }
return newPart; return newPart;
@ -564,7 +541,6 @@ async function handleComponentSelected(componentData: DiagramPart | null) {
// //
function handleDiagramUpdated(data: DiagramData) { function handleDiagramUpdated(data: DiagramData) {
diagramData.value = data; diagramData.value = data;
console.log("Diagram data updated:", data);
} }
// //
@ -843,8 +819,10 @@ body {
padding: 1.5rem; padding: 1.5rem;
max-width: 100%; max-width: 100%;
margin: 0; margin: 0;
background-color: transparent; /* 使用透明背景 */ background-color: transparent;
border: none; /* 确保没有边框 */ /* 使用透明背景 */
border: none;
/* 确保没有边框 */
} }
/* 文档切换按钮样式 */ /* 文档切换按钮样式 */