From ba79a2093b1f6dc280ea0aa0fa3a68a8334a3998 Mon Sep 17 00:00:00 2001
From: alivender <13898766233@163.com>
Date: Fri, 18 Jul 2025 14:37:25 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8netsh=E6=8C=87?=
=?UTF-8?q?=E4=BB=A4=E8=AE=BE=E7=BD=AEarp?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
server/src/ArpClient.cs | 69 +++++++++++++--
server/src/Controllers/DataController.cs | 54 +++++++++++-
server/src/Controllers/NetConfigController.cs | 85 ++++++++++---------
server/src/Peripherals/CameraClient.cs | 33 +++----
src/views/User/Index.vue | 4 +-
5 files changed, 179 insertions(+), 66 deletions(-)
diff --git a/server/src/ArpClient.cs b/server/src/ArpClient.cs
index 3d35969..8f04b4a 100644
--- a/server/src/ArpClient.cs
+++ b/server/src/ArpClient.cs
@@ -84,7 +84,7 @@ public static class Arp
{
// 格式化 MAC 地址以适配不同操作系统
string formattedMac = FormatMacAddress(macAddress);
- string command = GetArpAddCommand(ipAddress, formattedMac, interfaceName);
+ string command = await GetArpAddCommandAsync(ipAddress, formattedMac, interfaceName);
var result = await ExecuteCommandAsync(command);
return result.IsSuccess;
}
@@ -181,13 +181,20 @@ public static class Arp
///
/// 获取 ARP 添加命令
///
- private static string GetArpAddCommand(string ipAddress, string macAddress, string? interfaceName)
+ private static async Task GetArpAddCommandAsync(string ipAddress, string macAddress, string? interfaceName)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
- return string.IsNullOrWhiteSpace(interfaceName)
- ? $"arp -s {ipAddress} {macAddress}"
- : $"arp -s {ipAddress} {macAddress} {interfaceName}";
+ if (!string.IsNullOrWhiteSpace(interfaceName))
+ {
+ // 通过 arp -a 获取接口索引
+ var interfaceIdx = await GetWindowsInterfaceIndexAsync(interfaceName);
+ if (interfaceIdx.HasValue)
+ {
+ return $"netsh -c i i add neighbors {interfaceIdx.Value} {ipAddress} {macAddress}";
+ }
+ }
+ return $"arp -s {ipAddress} {macAddress}";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
@@ -207,6 +214,54 @@ public static class Arp
}
}
+ ///
+ /// 获取 Windows 接口索引
+ ///
+ /// 接口IP地址
+ /// 接口索引(十进制),如果未找到则返回null
+ private static async Task GetWindowsInterfaceIndexAsync(string interfaceIp)
+ {
+ try
+ {
+ var result = await ExecuteCommandAsync("arp -a");
+ if (!result.IsSuccess)
+ return null;
+
+ var lines = result.Output.Split('\n', StringSplitOptions.RemoveEmptyEntries);
+
+ foreach (var line in lines)
+ {
+ // 匹配接口行格式: Interface: 172.6.1.5 --- 0xa
+ var interfacePattern = @"Interface:\s+(\d+\.\d+\.\d+\.\d+)\s+---\s+(0x[a-fA-F0-9]+)";
+ var match = Regex.Match(line, interfacePattern);
+
+ if (match.Success && match.Groups[1].Value == interfaceIp)
+ {
+ // 将十六进制索引转换为十进制
+ var hexIndex = match.Groups[2].Value;
+ // 去掉 "0x" 前缀
+ var hexValue = hexIndex.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
+ ? hexIndex.Substring(2)
+ : hexIndex;
+
+ if (int.TryParse(hexValue, System.Globalization.NumberStyles.HexNumber, null, out int decimalIndex))
+ {
+ logger.Debug($"找到接口 {interfaceIp} 的索引: {hexIndex} -> {decimalIndex}");
+ return decimalIndex;
+ }
+ }
+ }
+
+ logger.Warn($"未找到接口 {interfaceIp} 的索引");
+ return null;
+ }
+ catch (Exception ex)
+ {
+ logger.Error(ex, $"获取接口 {interfaceIp} 索引失败");
+ return null;
+ }
+ }
+
///
/// 获取 ARP 删除命令
///
@@ -478,7 +533,9 @@ public static class Arp
}
// 新增 ARP 记录
- return await AddArpEntryAsync(ipAddress, formattedMac, interfaceName);
+ var ret = await AddArpEntryAsync(ipAddress, formattedMac, interfaceName);
+ if(!ret) logger.Error($"添加 ARP 记录失败: {ipAddress} -> {formattedMac} on {interfaceName}");
+ return true;
}
///
diff --git a/server/src/Controllers/DataController.cs b/server/src/Controllers/DataController.cs
index 15a00e3..ee4f237 100644
--- a/server/src/Controllers/DataController.cs
+++ b/server/src/Controllers/DataController.cs
@@ -1,4 +1,5 @@
using System.IdentityModel.Tokens.Jwt;
+using System.Net;
using System.Security.Claims;
using System.Text;
using Microsoft.AspNetCore.Authorization;
@@ -16,6 +17,8 @@ namespace server.Controllers;
public class DataController : ControllerBase
{
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
+ // 固定的实验板IP,端口,MAC地址
+ private const string BOARD_IP = "169.254.109.0";
///
/// [TODO:description]
@@ -48,6 +51,53 @@ public class DataController : ControllerBase
public DateTime? BoardExpireTime { get; set; }
}
+ ///
+ /// 获取本机IP地址(优先选择与实验板同网段的IP)
+ ///
+ /// 本机IP地址
+ private IPAddress GetLocalIPAddress()
+ {
+ try
+ {
+ var boardIpSegments = BOARD_IP.Split('.').Take(3).ToArray();
+
+ // 优先选择与实验板IP前三段相同的IP
+ var sameSegmentIP = System.Net.NetworkInformation.NetworkInterface
+ .GetAllNetworkInterfaces()
+ .Where(nic => nic.OperationalStatus == System.Net.NetworkInformation.OperationalStatus.Up
+ && nic.NetworkInterfaceType != System.Net.NetworkInformation.NetworkInterfaceType.Loopback)
+ .SelectMany(nic => nic.GetIPProperties().UnicastAddresses)
+ .Where(addr => addr.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
+ .Select(addr => addr.Address)
+ .FirstOrDefault(addr =>
+ {
+ var segments = addr.ToString().Split('.');
+ return segments.Length == 4 &&
+ segments[0] == boardIpSegments[0] &&
+ segments[1] == boardIpSegments[1] &&
+ segments[2] == boardIpSegments[2];
+ });
+
+ if (sameSegmentIP != null)
+ return sameSegmentIP;
+
+ // 如果没有找到同网段的IP,返回第一个可用的IP
+ return System.Net.NetworkInformation.NetworkInterface
+ .GetAllNetworkInterfaces()
+ .Where(nic => nic.OperationalStatus == System.Net.NetworkInformation.OperationalStatus.Up
+ && nic.NetworkInterfaceType != System.Net.NetworkInformation.NetworkInterfaceType.Loopback)
+ .SelectMany(nic => nic.GetIPProperties().UnicastAddresses)
+ .Where(addr => addr.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
+ .Select(addr => addr.Address)
+ .FirstOrDefault() ?? IPAddress.Loopback;
+ }
+ catch (Exception ex)
+ {
+ logger.Error(ex, "获取本机IP地址失败");
+ return IPAddress.Loopback;
+ }
+ }
+
///
/// 用户登录,获取 JWT 令牌
///
@@ -228,7 +278,7 @@ public class DataController : ControllerBase
return NotFound("没有可用的实验板");
var boardInfo = boardOpt.Value;
- if (!(await Arp.CheckOrAddAsync(boardInfo.IpAddr, boardInfo.MacAddr)))
+ if (!(await Arp.CheckOrAddAsync(boardInfo.IpAddr, boardInfo.MacAddr, GetLocalIPAddress().ToString())))
{
logger.Error($"无法配置ARP,实验板可能会无法连接");
}
@@ -296,7 +346,7 @@ public class DataController : ControllerBase
return NotFound("未找到对应的实验板");
var boardInfo = ret.Value.Value;
- if (!(await Arp.CheckOrAddAsync(boardInfo.IpAddr, boardInfo.MacAddr)))
+ if (!(await Arp.CheckOrAddAsync(boardInfo.IpAddr, boardInfo.MacAddr, GetLocalIPAddress().ToString())))
{
logger.Error($"无法配置ARP,实验板可能会无法连接");
}
diff --git a/server/src/Controllers/NetConfigController.cs b/server/src/Controllers/NetConfigController.cs
index 3adda80..f0dfad7 100644
--- a/server/src/Controllers/NetConfigController.cs
+++ b/server/src/Controllers/NetConfigController.cs
@@ -119,46 +119,7 @@ public class NetConfigController : ControllerBase
/// 网络接口名称
private string GetLocalNetworkInterface()
{
- try
- {
- var boardIpSegments = BOARD_IP.Split('.').Take(3).ToArray();
-
- // 优先选择与实验板IP前三段相同的网络接口
- var sameSegmentInterface = System.Net.NetworkInformation.NetworkInterface
- .GetAllNetworkInterfaces()
- .Where(nic => nic.OperationalStatus == System.Net.NetworkInformation.OperationalStatus.Up
- && nic.NetworkInterfaceType != System.Net.NetworkInformation.NetworkInterfaceType.Loopback)
- .FirstOrDefault(nic =>
- {
- var ipAddresses = nic.GetIPProperties().UnicastAddresses
- .Where(addr => addr.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
- .Select(addr => addr.Address);
-
- return ipAddresses.Any(addr =>
- {
- var segments = addr.ToString().Split('.');
- return segments.Length == 4 &&
- segments[0] == boardIpSegments[0] &&
- segments[1] == boardIpSegments[1] &&
- segments[2] == boardIpSegments[2];
- });
- });
-
- if (sameSegmentInterface != null)
- return sameSegmentInterface.Name;
-
- // 如果没有找到同网段的接口,返回第一个可用的接口
- return System.Net.NetworkInformation.NetworkInterface
- .GetAllNetworkInterfaces()
- .Where(nic => nic.OperationalStatus == System.Net.NetworkInformation.OperationalStatus.Up
- && nic.NetworkInterfaceType != System.Net.NetworkInformation.NetworkInterfaceType.Loopback)
- .FirstOrDefault()?.Name ?? "未知";
- }
- catch (Exception ex)
- {
- logger.Error(ex, "获取本机网络接口名称失败");
- return "未知";
- }
+ return GetLocalIPAddress().ToString();
}
///
@@ -484,6 +445,50 @@ public class NetConfigController : ControllerBase
}
}
+ ///
+ /// 设置板卡MAC地址
+ ///
+ /// 板卡MAC地址(格式:AA:BB:CC:DD:EE:FF)
+ /// 操作结果
+ [HttpPost("SetBoardMAC")]
+ [EnableCors("Users")]
+ [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(StatusCodes.Status500InternalServerError)]
+ public async Task SetBoardMAC(string boardMac)
+ {
+ if (string.IsNullOrWhiteSpace(boardMac))
+ return BadRequest("板卡MAC地址不能为空");
+
+ // 解析MAC地址
+ if (!TryParseMacAddress(boardMac, out var macBytes))
+ return BadRequest("MAC地址格式不正确,请使用格式:AA:BB:CC:DD:EE:FF");
+
+ try
+ {
+ if (!(await InitializeArpAsync()))
+ {
+ throw new Exception("无法配置ARP记录");
+ }
+ // 创建网络配置客户端
+ var netConfig = new NetConfig(BOARD_IP, BOARD_PORT, 0);
+ var result = await netConfig.SetBoardMAC(macBytes);
+
+ if (!result.IsSuccessful)
+ {
+ logger.Error($"设置板卡MAC地址失败: {result.Error}");
+ return StatusCode(StatusCodes.Status500InternalServerError, $"设置失败: {result.Error}");
+ }
+
+ return Ok(result.Value);
+ }
+ catch (Exception ex)
+ {
+ logger.Error(ex, "设置板卡MAC地址时发生异常");
+ return StatusCode(StatusCodes.Status500InternalServerError, "设置失败,请稍后重试");
+ }
+ }
+
///
/// 设置主机MAC地址
///
diff --git a/server/src/Peripherals/CameraClient.cs b/server/src/Peripherals/CameraClient.cs
index 65b8609..855808a 100644
--- a/server/src/Peripherals/CameraClient.cs
+++ b/server/src/Peripherals/CameraClient.cs
@@ -8,11 +8,11 @@ static class CameraAddr
{
public const UInt32 BASE = 0x7000_0000;
- public const UInt32 STORE_ADDR = BASE + 0x12;
- public const UInt32 STORE_NUM = BASE + 0x13;
- public const UInt32 EXPECTED_VH = BASE + 0x14;
- public const UInt32 CAPTURE_ON = BASE + 0x15;
- public const UInt32 CAMERA_POWER = BASE + 0x16; //[0]: rstn, 0 is reset. [8]: power down, 1 is down.
+ public const UInt32 DMA0_START_WRITE_ADDR = BASE + 0x0C;
+ public const UInt32 DMA0_END_WRITE_ADDR = BASE + 0x0D;
+ public const UInt32 DMA0_CAPTURE_CTRL = BASE + 0x0E; //[0]: on, 1 is on. [8]: reset, 1 is reset.
+ public const UInt32 EXPECTED_VH = BASE + 0x0F;
+ public const UInt32 CAMERA_POWER = BASE + 0x10; //[0]: rstn, 0 is reset. [8]: power down, 1 is down.
}
class Camera
@@ -156,7 +156,7 @@ class Camera
public async ValueTask> EnableHardwareTrans(bool isEnable)
{
- var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, CameraAddr.CAPTURE_ON, Convert.ToUInt32(isEnable));
+ var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, CameraAddr.DMA0_CAPTURE_CTRL, (isEnable ? 0x00000001u : 0x00000100u));
if (!ret.IsSuccessful)
{
logger.Error($"Failed to write CAPTURE_ON to camera at {this.address}:{this.port}, error: {ret.Error}");
@@ -361,7 +361,7 @@ class Camera
// 1. 配置UDP相关寄存器
{
- var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, CameraAddr.STORE_ADDR, FrameAddr);
+ var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, CameraAddr.DMA0_START_WRITE_ADDR, FrameAddr);
if (!ret.IsSuccessful)
{
logger.Error($"Failed to write STORE_ADDR: {ret.Error}");
@@ -375,7 +375,7 @@ class Camera
}
{
- var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, CameraAddr.STORE_NUM, frameLength);
+ var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, CameraAddr.DMA0_END_WRITE_ADDR, FrameAddr + frameLength - 1);
if (!ret.IsSuccessful)
{
logger.Error($"Failed to write STORE_NUM: {ret.Error}");
@@ -635,7 +635,7 @@ class Camera
{
var basicRegisters = new UInt16[][]
{
- [0x3103, 0x03], // system clock from pad, bit[1]
+ [0x3103, 0x03], // system clock from pad, bit[1] //02
[0x3017, 0xff],
[0x3018, 0xff],
[0x3037, 0x13],
@@ -750,6 +750,7 @@ class Camera
[0x3c04, 0x28],
[0x3c05, 0x98],
[0x3c06, 0x00],
+ [0x3c07, 0x07],
[0x3c08, 0x00],
[0x3c09, 0x1c],
[0x3c0a, 0x9c],
@@ -803,12 +804,12 @@ class Camera
{
var aecRegisters = new UInt16[][]
{
- [0x3a0f, 0x30], // AEC控制;stable range in high
- [0x3a10, 0x28], // AEC控制;stable range in low
- [0x3a1b, 0x30], // AEC控制;stable range out high
- [0x3a1e, 0x26], // AEC控制;stable range out low
- [0x3a11, 0x60], // AEC控制; fast zone high
- [0x3a1f, 0x14], // AEC控制; fast zone low
+ [0x3a0f, 0x30], // AEC控制;stable range in high //78
+ [0x3a10, 0x28], // AEC控制;stable range in low //68
+ [0x3a1b, 0x30], // AEC控制;stable range out high //78
+ [0x3a1e, 0x26], // AEC控制;stable range out low //68
+ [0x3a11, 0x60], // AEC控制; fast zone high //D0
+ [0x3a1f, 0x14], // AEC控制; fast zone low //40
[0x3b07, 0x0a] // 帧曝光模式
};
@@ -952,7 +953,7 @@ class Camera
{
var timingRegisters = new UInt16[][]
{
- [0x3035, 0x41], // 60fps
+ [0x3035, 0x21], // 60fps
[0x3036, PLL_MUX],// PLL倍频
[0x3c07, 0x08],
[0x3820, 0x41], // vflip
diff --git a/src/views/User/Index.vue b/src/views/User/Index.vue
index e979e69..cb2116f 100644
--- a/src/views/User/Index.vue
+++ b/src/views/User/Index.vue
@@ -1,6 +1,6 @@