diff --git a/.justfile b/.justfile
new file mode 100644
index 0000000..7df70f6
--- /dev/null
+++ b/.justfile
@@ -0,0 +1,12 @@
+@_show-dir:
+ echo "Current Working Directory:"
+ pwd
+ echo
+
+[working-directory: "server"]
+publish: _show-dir
+ dotnet publish --self-contained false -t:PublishAllRids
+
+[working-directory: "server"]
+run-server: _show-dir
+ dotnet run
diff --git a/flake.nix b/flake.nix
index bd3a2c9..44bd8b9 100644
--- a/flake.nix
+++ b/flake.nix
@@ -7,7 +7,10 @@
let
supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
forEachSupportedSystem = f: nixpkgs.lib.genAttrs supportedSystems (system: f {
- pkgs = import nixpkgs { inherit system; };
+ pkgs = import nixpkgs {
+ inherit system;
+ config.permittedInsecurePackages = ["dotnet-sdk-6.0.428"];
+ };
});
in
{
@@ -26,6 +29,7 @@
dotnetCorePackages.sdk_8_0
])
nuget
+ # msbuild
omnisharp-roslyn
csharpier
diff --git a/server/Program.cs b/server/Program.cs
index 692e916..6e55bbc 100644
--- a/server/Program.cs
+++ b/server/Program.cs
@@ -1,51 +1,97 @@
using System.Reflection;
using Microsoft.OpenApi.Models;
+using NLog;
+using NLog.Web;
-var builder = WebApplication.CreateBuilder(args);
-builder.Services.AddEndpointsApiExplorer();
-builder.Services.AddSwaggerGen(options =>
+// Early init of NLog to allow startup and exception logging, before host is built
+var logger = NLog.LogManager.Setup()
+ .LoadConfigurationFromAppSettings()
+ .GetCurrentClassLogger();
+logger.Debug("Init Main...");
+
+try
{
- options.SwaggerDoc("v1", new OpenApiInfo
+ var builder = WebApplication.CreateBuilder(args);
+
+ // Add services to the container.
+ builder.Services.AddControllersWithViews();
+
+ // NLog: Setup NLog for Dependency injection
+ builder.Logging.ClearProviders();
+ builder.Host.UseNLog();
+ builder.Services.AddEndpointsApiExplorer();
+
+ // Add Swagger
+ builder.Services.AddSwaggerGen(options =>
{
- Title = "FPGA Web Lab API",
- Description = "Use FPGA in the cloud",
- Version = "v1"
+ options.SwaggerDoc("v1", new OpenApiInfo
+ {
+ Title = "FPGA Web Lab API",
+ Description = "Use FPGA in the cloud",
+ Version = "v1"
+ });
+ // Generate Doc and Exam
+ var executingAssembly = Assembly.GetExecutingAssembly();
+ var xmlFilename = $"{executingAssembly.GetName().Name}.xml";
+ options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
+ var referencedProjectsXmlDocPaths =
+ executingAssembly.GetReferencedAssemblies()
+ .Where(assembly => assembly.Name != null && assembly.Name.StartsWith("server", StringComparison.InvariantCultureIgnoreCase))
+ .Select(assembly => Path.Combine(AppContext.BaseDirectory, $"{assembly.Name}.xml"))
+ .Where(path => File.Exists(path));
+ foreach (var xmlDocPath in referencedProjectsXmlDocPaths) options.IncludeXmlComments(xmlDocPath);
});
- // Generate Doc and Exam
- var executingAssembly = Assembly.GetExecutingAssembly();
- var xmlFilename = $"{executingAssembly.GetName().Name}.xml";
- options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
- var referencedProjectsXmlDocPaths =
- executingAssembly.GetReferencedAssemblies()
- .Where(assembly => assembly.Name != null && assembly.Name.StartsWith("server", StringComparison.InvariantCultureIgnoreCase))
- .Select(assembly => Path.Combine(AppContext.BaseDirectory, $"{assembly.Name}.xml"))
- .Where(path => File.Exists(path));
- foreach (var xmlDocPath in referencedProjectsXmlDocPaths) options.IncludeXmlComments(xmlDocPath);
-});
-var app = builder.Build();
-if (app.Environment.IsDevelopment())
-{
+ var app = builder.Build();
+ // Configure the HTTP request pipeline.
+ app.UseExceptionHandler("/Home/Error");
+ // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
+ app.UseHsts();
+ app.UseHttpsRedirection();
+ app.UseStaticFiles();
+ app.UseRouting();
+
+ app.UseAuthorization();
+
+ app.MapControllerRoute(
+ name: "default",
+ pattern: "{controller=Home}/{action=Index}/{id?}");
+
+ // if (app.Environment.IsDevelopment())
+ // {
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "FPAG WebLab API V1");
});
+ // }
+
+ // Setup Program
+ MsgBus.Init();
+
+ // Router
+ app.MapGet("/", () => Results.Redirect("/swagger"));
+ app.MapPut("/api/SendString", Router.API.SendString);
+ app.MapPut("/api/SendAddrPackage", Router.API.SendAddrPackage);
+ app.MapPut("/api/SendDataPackage", Router.API.SendDataPackage);
+ app.MapPut("/api/jtag/RunCommand", Router.API.Jtag.RunCommand);
+ app.MapPut("/api/jtag/GetIDCode", Router.API.Jtag.GetDeviceIDCode);
+
+ app.Run("http://localhost:5000");
+}
+catch (Exception exception)
+{
+ // NLog: catch setup errors
+ logger.Error(exception, "Stopped program because of exception");
+ throw;
+}
+finally
+{
+ // Close UDP Server
+ logger.Info("Program is Closing now...");
+ MsgBus.Exit();
+
+ // Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux)
+ NLog.LogManager.Shutdown();
}
-// Setup Program
-MsgBus.Init();
-
-// Router
-app.MapGet("/", () => "Hello World!");
-app.MapPut("/api/SendString", Router.API.SendString);
-app.MapPut("/api/SendAddrPackage", Router.API.SendAddrPackage);
-app.MapPut("/api/SendDataPackage", Router.API.SendDataPackage);
-app.MapPut("/api/jtag/RunCommand", Router.API.Jtag.RunCommand);
-app.MapPut("/api/jtag/GetIDCode", Router.API.Jtag.GetDeviceIDCode);
-
-app.Run("http://localhost:5000");
-
-// Close UDP Server
-Console.WriteLine("Program is Closing now...");
-MsgBus.Exit();
diff --git a/server/PublishAllRids.targets b/server/PublishAllRids.targets
new file mode 100644
index 0000000..5182a98
--- /dev/null
+++ b/server/PublishAllRids.targets
@@ -0,0 +1,28 @@
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+
+
+ true
+
+
+
+
+
+
+
+
+
+ RuntimeIdentifier=%(RuntimeIdentifierForPublish.Identity)
+
+
+
+
+
+
+
diff --git a/server/appsettings.json b/server/appsettings.json
index 4d56694..b2362cc 100644
--- a/server/appsettings.json
+++ b/server/appsettings.json
@@ -2,8 +2,60 @@
"Logging": {
"LogLevel": {
"Default": "Information",
- "Microsoft.AspNetCore": "Warning"
+ "Microsoft": "Warning",
+ "Microsoft.AspNetCore": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ },
+ "NLog": {
+ "RemoveLoggerFactoryFilter": true
}
},
- "AllowedHosts": "*"
+ "AllowedHosts": "*",
+ "NLog": {
+ "extensions": [
+ {
+ "assembly": "NLog.Web.AspNetCore"
+ }
+ ],
+ "throwConfigExceptions": true,
+ "targets": {
+ "async": true,
+ "AllFile": {
+ "type": "File",
+ "fileName": "./log/all-${shortdate}.log"
+ },
+ "WebAllFile": {
+ "type": "File",
+ "fileName": "./log/web-all-${shortdate}.log"
+ },
+ "Console": {
+ "type": "ColoredConsole"
+ }
+ },
+ "rules": [
+ {
+ "logger": "*",
+ "finalMinLevel": "Trace",
+ "writeTo": "AllFile"
+ },
+ {
+ "logger": "*",
+ "finalMinLevel": "Trace",
+ "writeTo": "WebAllFile"
+ },
+ {
+ "logger": "System.*",
+ "finalMinLevel": "Warn"
+ },
+ {
+ "logger": "Microsoft.*",
+ "finalMinLevel": "Warn"
+ },
+ {
+ "logger": "Microsoft.Hosting.Lifetime*",
+ "finalMinLevel": "Info",
+ "writeTo": "Console"
+ }
+ ]
+ }
}
diff --git a/server/server.csproj b/server/server.csproj
index faf5a41..114fcae 100644
--- a/server/server.csproj
+++ b/server/server.csproj
@@ -1,10 +1,13 @@
+
net9.0
enable
enable
true
+ win-x64;linux-x64;
+ true
@@ -13,6 +16,7 @@
+
diff --git a/server/src/JtagController.cs b/server/src/JtagController.cs
index ae26c01..269f7bb 100644
--- a/server/src/JtagController.cs
+++ b/server/src/JtagController.cs
@@ -236,11 +236,12 @@ class Jtag
var opts = new SendAddrPackOptions();
opts.burstType = BurstType.FixedBurst;
- opts.burstLength = 4;
+ opts.burstLength = 0;
opts.commandID = 0;
opts.address = devAddr;
// Read Jtag State Register
+ opts.isWrite = false;
ret = await UDPClientPool.SendAddrPackAsync(ep, new SendAddrPackage(opts));
if (!ret) throw new Exception("Send Address Package Failed!");
@@ -276,7 +277,7 @@ class Jtag
var opts = new SendAddrPackOptions();
opts.burstType = BurstType.FixedBurst;
- opts.burstLength = 4;
+ opts.burstLength = 0;
opts.commandID = 0;
opts.address = devAddr;
@@ -289,6 +290,7 @@ class Jtag
if (!ret) throw new Exception("Send Data Package Failed!");
// Read Jtag State Register
+ opts.isWrite = false;
opts.address = JtagAddr.STATE;
ret = await UDPClientPool.SendAddrPackAsync(ep, new SendAddrPackage(opts));
if (!ret) throw new Exception("Send 2rd Address Package Failed!");
diff --git a/server/src/MsgBus.cs b/server/src/MsgBus.cs
index 2f9510a..1101792 100644
--- a/server/src/MsgBus.cs
+++ b/server/src/MsgBus.cs
@@ -1,19 +1,36 @@
+///
+/// 多线程通信总线
+///
public static class MsgBus
{
- private static readonly UDPServer udpServer = new UDPServer(33000);
+ private static readonly UDPServer udpServer = new UDPServer(1234);
+ ///
+ /// 获取UDP服务器
+ ///
public static UDPServer UDPServer { get { return udpServer; } }
private static bool isRunning = false;
+ ///
+ /// 获取通信总线运行状态
+ ///
public static bool IsRunning { get { return isRunning; } }
+ ///
+ /// 通信总线初始化
+ ///
+ /// 无
public static void Init()
{
udpServer.Start();
isRunning = true;
}
+ ///
+ /// 关闭通信总线
+ ///
+ /// 无
public static void Exit()
{
udpServer.Stop();
diff --git a/server/src/Router.cs b/server/src/Router.cs
index 2b7ea41..e9e193a 100644
--- a/server/src/Router.cs
+++ b/server/src/Router.cs
@@ -16,10 +16,10 @@ namespace Router
class API
{
///
- /// Send some String
+ /// 发送字符串
///
- /// IP V4/V6 address
- /// Device UDP port
+ /// IPV4 或者 IPV6 地址
+ /// 设备端口号
/// Text for send
/// Json: true or false
public static async ValueTask SendString(string address, int port, string text)
diff --git a/server/src/UdpServer.cs b/server/src/UdpServer.cs
index 28fe6e3..546c5e7 100644
--- a/server/src/UdpServer.cs
+++ b/server/src/UdpServer.cs
@@ -4,11 +4,24 @@ using System.Text;
using DotNext;
using DotNext.Threading;
+/// UDP接受数据包格式
public struct UDPData
{
+ ///
+ /// 接受到的时间
+ ///
public DateTime datetime;
+ ///
+ /// 发送来源的IP地址
+ ///
public string addr;
+ ///
+ /// 发送来源的端口号
+ ///
public int port;
+ ///
+ /// 接受到的数据
+ ///
public byte[] data;
}
@@ -51,18 +64,29 @@ public class UDPServer
/// Find UDP Receive Data According to ip address
///
/// IP Address
- /// Read and Write Wait for Milliseconds
+ /// Read and Write Wait for Milliseconds
/// UDP Data
- public Optional FindData(string ipAddr, int rwTimeout = 1000)
+ public Optional FindData(string ipAddr, int timeout = 1000)
{
UDPData? data = null;
- using (udpDataLock.AcquireWriteLock(TimeSpan.FromMilliseconds(rwTimeout)))
+
+ var startTime = DateTime.Now;
+ var isTimeout = false;
+ var timeleft = TimeSpan.FromMilliseconds(timeout);
+ while (!isTimeout)
{
- if (udpData.ContainsKey(ipAddr) && udpData[ipAddr].Count > 0)
+
+ using (udpDataLock.AcquireWriteLock(timeleft))
{
- data = udpData[ipAddr][0];
- udpData[ipAddr].RemoveAt(0);
+ if (udpData.ContainsKey(ipAddr) && udpData[ipAddr].Count > 0)
+ {
+ data = udpData[ipAddr][0];
+ udpData[ipAddr].RemoveAt(0);
+ }
}
+
+ timeleft = DateTime.Now.Subtract(startTime);
+ isTimeout = timeleft >= TimeSpan.FromMilliseconds(timeout);
}
if (data == null)
@@ -76,21 +100,36 @@ public class UDPServer
}
///
- /// Find UDP Receive Data Async
+ /// 异步寻找目标发送的内容
///
- /// [TODO:parameter]
- /// [TODO:parameter]
- /// [TODO:return]
+ /// 目标IP地址
+ /// 超时时间
+ ///
+ /// 异步Optional 数据包:
+ /// Optional 为空时,表明找不到数据;
+ /// Optional 存在时,为最先收到的数据
+ ///
public async ValueTask> FindDataAsync(string ipAddr, int timeout = 1000)
{
UDPData? data = null;
- using (await udpDataLock.AcquireWriteLockAsync(TimeSpan.FromMilliseconds(1000)))
+
+ var startTime = DateTime.Now;
+ var isTimeout = false;
+ var timeleft = TimeSpan.FromMilliseconds(timeout);
+ while (!isTimeout)
{
- if (udpData.ContainsKey(ipAddr) && udpData[ipAddr].Count > 0)
+
+ using (await udpDataLock.AcquireWriteLockAsync(timeleft))
{
- data = udpData[ipAddr][0];
- udpData[ipAddr].RemoveAt(0);
+ if (udpData.ContainsKey(ipAddr) && udpData[ipAddr].Count > 0)
+ {
+ data = udpData[ipAddr][0];
+ udpData[ipAddr].RemoveAt(0);
+ }
}
+
+ timeleft = DateTime.Now.Subtract(startTime);
+ isTimeout = timeleft >= TimeSpan.FromMilliseconds(timeout);
}
if (data == null)
@@ -152,8 +191,22 @@ public class UDPServer
recvData = resData.Error.ToString();
}
else if (sign == (byte)WebProtocol.PackSign.SendData) { }
- else if (sign == (byte)WebProtocol.PackSign.RecvData) { }
- else if (sign == (byte)WebProtocol.PackSign.RecvResp) { }
+ else if (sign == (byte)WebProtocol.PackSign.RecvData)
+ {
+ var resData = WebProtocol.RecvDataPackage.FromBytes(bytes);
+ if (resData.IsSuccessful)
+ recvData = resData.Value.Options.ToString();
+ else
+ recvData = resData.Error.ToString();
+ }
+ else if (sign == (byte)WebProtocol.PackSign.RecvResp)
+ {
+ var resData = WebProtocol.RecvRespPackage.FromBytes(bytes);
+ if (resData.IsSuccessful)
+ recvData = resData.Value.Options.ToString();
+ else
+ recvData = resData.Error.ToString();
+ }
else
{
recvData = Encoding.ASCII.GetString(bytes, 0, bytes.Length);
diff --git a/server/src/WebProtocol.cs b/server/src/WebProtocol.cs
index 4c48312..a2cf0d5 100644
--- a/server/src/WebProtocol.cs
+++ b/server/src/WebProtocol.cs
@@ -42,8 +42,10 @@ namespace WebProtocol
public BurstType burstType;
/// 1
public byte commandID;
+ /// 标识写入还是读取
/// true
public bool isWrite;
+ /// 突发长度:0是32bits,255是32bits x 256
/// 255
public byte burstLength;
/// 0x10_00_00_00
@@ -62,6 +64,19 @@ namespace WebProtocol
/// Package Options which to receive from boards
public struct RecvPackOptions
{
+ /// 数据包类型
+ public enum PackType
+ {
+ /// 读响应包
+ ReadResp,
+ /// 写响应包
+ WriteResp
+ };
+
+ ///
+ /// 数据包类型
+ ///
+ public PackType type;
///
/// Task ID
///
@@ -85,7 +100,7 @@ namespace WebProtocol
}
}
- /// Package which send address to write
+ /// Server->FPGA 地址包
public struct SendAddrPackage
{
readonly byte sign = (byte)PackSign.SendAddr;
@@ -94,22 +109,36 @@ namespace WebProtocol
readonly byte _reserved = 0;
readonly UInt32 address;
+ ///
+ /// 使用地址包选项构造地址包
+ ///
+ /// 地址包选项
public SendAddrPackage(SendAddrPackOptions opts)
{
- byte byteBurstType = Convert.ToByte((byte)opts.burstType << 5);
- byte byteCommandID = Convert.ToByte((opts.commandID & 0x03) << 3);
+ byte byteBurstType = Convert.ToByte((byte)opts.burstType << 6);
+ byte byteCommandID = Convert.ToByte((opts.commandID & 0x03) << 4);
byte byteIsWrite = (opts.isWrite ? (byte)0x01 : (byte)0x00);
this.commandType = Convert.ToByte(byteBurstType | byteCommandID | byteIsWrite);
this.burstLength = opts.burstLength;
this.address = opts.address;
}
- public SendAddrPackage(BurstType burstType, byte commandID, bool isWrite)
+ ///
+ /// 使用完整参数构造地址包
+ ///
+ /// 突发类型
+ /// 任务ID
+ /// 是否是写数据
+ /// 突发长度
+ /// 设备地址
+ public SendAddrPackage(BurstType burstType, byte commandID, bool isWrite, byte burstLength, UInt32 address)
{
- byte byteBurstType = Convert.ToByte((byte)burstType << 5);
- byte byteCommandID = Convert.ToByte((commandID & 0x03) << 3);
+ byte byteBurstType = Convert.ToByte((byte)burstType << 6);
+ byte byteCommandID = Convert.ToByte((commandID & 0x03) << 4);
byte byteIsWrite = (isWrite ? (byte)0x01 : (byte)0x00);
this.commandType = Convert.ToByte(byteBurstType | byteCommandID | byteIsWrite);
+ this.burstLength = burstLength;
+ this.address = address;
}
public SendAddrPackage(byte commandType, byte burstLength, UInt32 address)
@@ -119,6 +148,10 @@ namespace WebProtocol
this.address = address;
}
+ ///
+ /// 将地址包转化为字节数组
+ ///
+ /// 字节数组
public byte[] ToBytes()
{
var arr = new byte[8];
@@ -133,11 +166,15 @@ namespace WebProtocol
return arr;
}
+ ///
+ /// 讲地址包转化为Json格式的地址包选项
+ ///
+ /// 字符串
public override string ToString()
{
SendAddrPackOptions opts;
- opts.burstType = (BurstType)(commandType >> 5);
- opts.commandID = Convert.ToByte((commandType >> 3) & 0b0011);
+ opts.burstType = (BurstType)(commandType >> 6);
+ opts.commandID = Convert.ToByte((commandType >> 4) & 0b0011);
opts.isWrite = Convert.ToBoolean(commandType & 0x01);
opts.burstLength = burstLength;
opts.address = address;
@@ -194,6 +231,7 @@ namespace WebProtocol
}
+ /// FPGA->Server 读数据包
public struct RecvDataPackage
{
readonly byte sign = (byte)PackSign.RecvData;
@@ -202,6 +240,13 @@ namespace WebProtocol
readonly byte _reserved = 0;
readonly byte[] bodyData;
+ ///
+ /// FPGA->Server 读数据包
+ /// 构造函数
+ ///
+ /// 任务ID号
+ /// 读数据包响应
+ /// 数据
public RecvDataPackage(byte commandID, byte resp, byte[] bodyData)
{
this.commandID = commandID;
@@ -209,13 +254,17 @@ namespace WebProtocol
this.bodyData = bodyData;
}
+ ///
+ /// 获取读数据包选项
+ ///
public RecvPackOptions Options
{
get
{
RecvPackOptions opts;
+ opts.type = RecvPackOptions.PackType.ReadResp;
opts.commandID = commandID;
- opts.isSuccess = Convert.ToBoolean((resp >> 1) == 0b01 ? true : false);
+ opts.isSuccess = Convert.ToBoolean((resp >> 1) == 0b01 ? false : true);
opts.data = bodyData;
return opts;
@@ -246,14 +295,28 @@ namespace WebProtocol
this.resp = resp;
}
- public RecvPackOptions Options()
+ public RecvPackOptions Options
{
- RecvPackOptions opts;
- opts.commandID = commandID;
- opts.isSuccess = Convert.ToBoolean((resp >> 1) == 0b01 ? true : false);
- opts.data = null;
+ get
+ {
+ RecvPackOptions opts;
+ opts.type = RecvPackOptions.PackType.WriteResp;
+ opts.commandID = commandID;
+ opts.isSuccess = Convert.ToBoolean((resp >> 1) == 0b01 ? false : true);
+ opts.data = null;
- return opts;
+ return opts;
+ }
+ }
+
+ public static Result FromBytes(byte[] bytes)
+ {
+ if (bytes[0] != (byte)PackSign.RecvResp)
+ throw new ArgumentException(
+ "The sign of bytes is not RecvData Package!",
+ nameof(bytes)
+ );
+ return new RecvRespPackage(bytes[1], bytes[2]);
}
}
}