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]); } } }