512 lines
20 KiB
C#
512 lines
20 KiB
C#
using System;
|
|
using System.IO;
|
|
using Aisu.SIM1.Face.Models;
|
|
using Aisu.SIM1.Face.Protocol;
|
|
using Aisu.SIM1.Face.UI.Common;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
|
|
namespace Aisu.SIM1.Face.UI.Pages
|
|
{
|
|
public sealed class FacePhotoEnrollPage : FaceModulePageBase
|
|
{
|
|
// 内部状态字段。
|
|
private InputField m_filePathInput;
|
|
// 内部状态字段。
|
|
private Text m_fileSizeText;
|
|
// 内部状态字段。
|
|
private Dropdown m_bioTypeDropdown;
|
|
// 内部状态字段。
|
|
private InputField m_userNameInput;
|
|
// 内部状态字段。
|
|
private Toggle m_duplicateToggle;
|
|
// 内部状态字段。
|
|
private Toggle m_specifyUserIdToggle;
|
|
// 内部状态字段。
|
|
private InputField m_userIdInput;
|
|
// 内部状态字段。
|
|
private Slider m_progress;
|
|
// 内部状态字段。
|
|
private Text m_seqText;
|
|
// 内部状态字段。
|
|
private Text m_userIdText;
|
|
|
|
public override void Build(FaceModuleUiBuilder builder)
|
|
{
|
|
builder.Text(transform, "照片 / 特征注册", 30);
|
|
m_filePathInput = builder.Input(transform, "文件路径", string.Empty);
|
|
var row = builder.Row(transform);
|
|
builder.Button(row.transform, "刷新文件信息", RefreshFileInfo);
|
|
builder.Button(row.transform, "使用 0xF7 注册", EnrollWithPhoto);
|
|
builder.Button(row.transform, "使用 0xD7 指定 ID 注册", EnrollWithPhotoAndId);
|
|
m_fileSizeText = builder.Text(transform, "文件大小: -");
|
|
m_bioTypeDropdown = builder.Dropdown(transform, new[] { "普通照片", "加密照片", "普通特征码", "压缩特征码" });
|
|
m_userNameInput = builder.Input(transform, "用户姓名", "User");
|
|
m_duplicateToggle = builder.Toggle(transform, "启用查重", true);
|
|
m_specifyUserIdToggle = builder.Toggle(transform, "指定 UserID", false);
|
|
m_userIdInput = builder.Input(transform, "指定 UserID", "1");
|
|
m_progress = builder.Slider(transform);
|
|
m_seqText = builder.Text(transform, "当前 Seq: -");
|
|
m_userIdText = builder.Text(transform, "注册 UserID: -");
|
|
m_resultText = builder.Text(transform, "注册结果: -");
|
|
}
|
|
|
|
/// <summary>刷新文件大小显示。</summary>
|
|
/// <summary>
|
|
/// 执行 RefreshFileInfo 方法。
|
|
/// </summary>
|
|
public void RefreshFileInfo()
|
|
{
|
|
var path = ReadText(m_filePathInput);
|
|
m_fileSizeText.text = File.Exists(path) ? "文件大小: " + new FileInfo(path).Length + " bytes" : "文件不存在";
|
|
}
|
|
|
|
/// <summary>使用 0xF7 注册。</summary>
|
|
/// <summary>
|
|
/// 执行 EnrollWithPhoto 方法。
|
|
/// </summary>
|
|
public void EnrollWithPhoto()
|
|
{
|
|
RunAsync(async () =>
|
|
{
|
|
if (!RequireReady()) return;
|
|
var bytes = ReadValidFile();
|
|
var result = await m_manager.Images.EnrollWithPhotoAsync(bytes, (BioType)m_bioTypeDropdown.value, ReadText(m_userNameInput), m_duplicateToggle.isOn, OnProgress);
|
|
ShowEnrollResult(result);
|
|
});
|
|
}
|
|
|
|
/// <summary>使用 0xD7 指定 ID 注册。</summary>
|
|
/// <summary>
|
|
/// 执行 EnrollWithPhotoAndId 方法。
|
|
/// </summary>
|
|
public void EnrollWithPhotoAndId()
|
|
{
|
|
RunAsync(async () =>
|
|
{
|
|
if (!RequireReady()) return;
|
|
var bytes = ReadValidFile();
|
|
var userId = ReadInt(m_userIdInput, 0);
|
|
if (!m_specifyUserIdToggle.isOn)
|
|
{
|
|
SetResult("请先启用指定 UserID");
|
|
return;
|
|
}
|
|
|
|
var result = await m_manager.Images.EnrollWithPhotoAndIdAsync(userId, bytes, (BioType)m_bioTypeDropdown.value, ReadText(m_userNameInput), m_duplicateToggle.isOn, OnProgress);
|
|
ShowEnrollResult(result);
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 ReadValidFile 方法。
|
|
/// </summary>
|
|
private byte[] ReadValidFile()
|
|
{
|
|
var path = ReadText(m_filePathInput);
|
|
if (!File.Exists(path)) throw new FileNotFoundException("文件不存在", path);
|
|
var bytes = File.ReadAllBytes(path);
|
|
if (bytes.Length == 0) throw new InvalidOperationException("文件为空");
|
|
if (bytes.Length > 8 * 1024 * 1024) throw new InvalidOperationException("文件过大,建议小于 8MB");
|
|
return bytes;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 OnProgress 方法。
|
|
/// </summary>
|
|
private void OnProgress(int sent, int total)
|
|
{
|
|
m_progress.value = total > 0 ? (float)sent / total : 0;
|
|
m_seqText.text = "当前 Seq: " + Mathf.CeilToInt((float)sent / FaceModuleConstants.PhotoRegisterMtu);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 ShowEnrollResult 方法。
|
|
/// </summary>
|
|
private void ShowEnrollResult(FaceEnrollResult result)
|
|
{
|
|
m_userIdText.text = "注册 UserID: " + result.UserId;
|
|
SetResult(result.Success ? "注册成功" : FaceProtocolEncoding.ResultMessage(result.ResultCode));
|
|
}
|
|
}
|
|
|
|
public sealed class FaceFeaturePage : FaceModulePageBase
|
|
{
|
|
// 内部状态字段。
|
|
private InputField m_userIdInput;
|
|
// 内部状态字段。
|
|
private Dropdown m_featureTypeDropdown;
|
|
// 内部状态字段。
|
|
private InputField m_filePathInput;
|
|
// 内部状态字段。
|
|
private InputField m_userNameInput;
|
|
// 内部状态字段。
|
|
private Slider m_progress;
|
|
// 内部状态字段。
|
|
private Text m_seqText;
|
|
// 内部状态字段。
|
|
private Text m_lengthText;
|
|
// 内部状态字段。
|
|
private byte[] m_lastFeature;
|
|
|
|
public override void Build(FaceModuleUiBuilder builder)
|
|
{
|
|
builder.Text(transform, "特征管理", 30);
|
|
m_userIdInput = builder.Input(transform, "UserID", "1");
|
|
m_featureTypeDropdown = builder.Dropdown(transform, new[] { "彩色人脸模板", "彩色 + 红外人脸模板", "掌纹掌静脉模板" });
|
|
m_filePathInput = builder.Input(transform, "特征文件路径", string.Empty);
|
|
m_userNameInput = builder.Input(transform, "用户姓名", "FeatureUser");
|
|
var row = builder.Row(transform);
|
|
builder.Button(row.transform, "读取特征", ReadFeature);
|
|
builder.Button(row.transform, "保存特征文件", SaveFeature);
|
|
builder.Button(row.transform, "写入特征", WriteFeature);
|
|
m_progress = builder.Slider(transform);
|
|
m_seqText = builder.Text(transform, "当前 Seq: -");
|
|
m_lengthText = builder.Text(transform, "特征长度: -");
|
|
m_resultText = builder.Text(transform, "操作结果: -");
|
|
}
|
|
|
|
/// <summary>读取特征。</summary>
|
|
/// <summary>
|
|
/// 执行 ReadFeature 方法。
|
|
/// </summary>
|
|
public void ReadFeature()
|
|
{
|
|
RunAsync(async () =>
|
|
{
|
|
if (!RequireReady()) return;
|
|
m_lastFeature = await m_manager.Features.ReadFeatureAsync(ReadInt(m_userIdInput, 0), FeatureTypeValue());
|
|
m_lengthText.text = "特征长度: " + (m_lastFeature == null ? 0 : m_lastFeature.Length);
|
|
SetResult(m_lastFeature.Length > 0 ? "特征读取完成" : "读取命令已完成,未收到特征数据");
|
|
});
|
|
}
|
|
|
|
/// <summary>保存特征文件。</summary>
|
|
/// <summary>
|
|
/// 执行 SaveFeature 方法。
|
|
/// </summary>
|
|
public void SaveFeature()
|
|
{
|
|
if (m_lastFeature == null || m_lastFeature.Length == 0)
|
|
{
|
|
SetResult("没有可保存的特征数据");
|
|
return;
|
|
}
|
|
|
|
var dir = Path.Combine(Application.persistentDataPath, "FaceModuleFeatures");
|
|
Directory.CreateDirectory(dir);
|
|
var name = $"{DateTime.Now:yyyyMMdd_HHmmss}_User_{ReadInt(m_userIdInput, 0)}_Feature_{FeatureTypeValue()}.bin";
|
|
File.WriteAllBytes(Path.Combine(dir, name), m_lastFeature);
|
|
SetResult("特征已保存: " + dir);
|
|
}
|
|
|
|
/// <summary>写入特征。</summary>
|
|
/// <summary>
|
|
/// 执行 WriteFeature 方法。
|
|
/// </summary>
|
|
public void WriteFeature()
|
|
{
|
|
RunAsync(async () =>
|
|
{
|
|
if (!RequireReady()) return;
|
|
var path = ReadText(m_filePathInput);
|
|
if (!File.Exists(path)) throw new FileNotFoundException("特征文件不存在", path);
|
|
var bytes = File.ReadAllBytes(path);
|
|
var result = await m_manager.Features.WriteFeatureAsync(bytes, FeatureTypeValue(), ReadText(m_userNameInput), OnProgress);
|
|
SetResult(result.Success ? "写入成功 UserID=" + result.UserId : FaceProtocolEncoding.ResultMessage(result.ResultCode));
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 FeatureTypeValue 方法。
|
|
/// </summary>
|
|
private FeatureType FeatureTypeValue()
|
|
{
|
|
return (FeatureType)(m_featureTypeDropdown.value + 1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 OnProgress 方法。
|
|
/// </summary>
|
|
private void OnProgress(int sent, int total)
|
|
{
|
|
m_progress.value = total > 0 ? (float)sent / total : 0;
|
|
m_seqText.text = "当前 Seq: " + Mathf.CeilToInt((float)sent / FaceModuleConstants.FeaturePacketSize);
|
|
m_lengthText.text = "特征长度: " + total;
|
|
}
|
|
}
|
|
|
|
public sealed class FaceUvcSettingsPage : FaceModulePageBase
|
|
{
|
|
// 内部状态字段。
|
|
private Dropdown m_usbTypeDropdown;
|
|
// 内部状态字段。
|
|
private InputField m_bitrateInput;
|
|
// 内部状态字段。
|
|
private InputField m_qualityInput;
|
|
// 内部状态字段。
|
|
private Toggle m_mirrorToggle;
|
|
// 内部状态字段。
|
|
private Toggle m_rotateToggle;
|
|
// 内部状态字段。
|
|
private Toggle m_faceBoxToggle;
|
|
|
|
public override void Build(FaceModuleUiBuilder builder)
|
|
{
|
|
builder.Text(transform, "UVC 设置", 30);
|
|
m_usbTypeDropdown = builder.Dropdown(transform, new[] { "USB1.1 Bulk 0x11", "USB2.0 Bulk 0x20", "USB1.1 ISOC 0x91", "USB2.0 ISOC 0xA0" });
|
|
m_bitrateInput = builder.Input(transform, "UVC 码率 Mbps", "8");
|
|
m_qualityInput = builder.Input(transform, "MJPG 质量 10-100", "80");
|
|
m_mirrorToggle = builder.Toggle(transform, "镜像", false);
|
|
m_rotateToggle = builder.Toggle(transform, "180 度倒转", false);
|
|
m_faceBoxToggle = builder.Toggle(transform, "显示人脸框", true);
|
|
var row = builder.Row(transform);
|
|
builder.Button(row.transform, "读取 UVC 参数", ReadUvc);
|
|
builder.Button(row.transform, "设置 UVC 参数", SetUvc);
|
|
builder.Button(row.transform, "设置人脸框显示", SetFaceBox);
|
|
m_resultText = builder.Text(transform, "操作结果: -");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 ReadUvc 方法。
|
|
/// </summary>
|
|
public void ReadUvc()
|
|
{
|
|
RunAsync(async () =>
|
|
{
|
|
if (!RequireReady()) return;
|
|
var p = await m_manager.Uvc.ReadUsbUvcParametersAsync();
|
|
m_bitrateInput.text = p.BitrateMbps.ToString();
|
|
m_qualityInput.text = p.MjpgQuality.ToString();
|
|
m_mirrorToggle.isOn = p.Mirror;
|
|
m_rotateToggle.isOn = p.Rotate180;
|
|
SetResult("UVC 参数读取成功");
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 SetUvc 方法。
|
|
/// </summary>
|
|
public void SetUvc()
|
|
{
|
|
RunAsync(async () =>
|
|
{
|
|
if (!RequireReady()) return;
|
|
var bitrate = ReadByte(m_bitrateInput, 8);
|
|
var quality = Mathf.Clamp(ReadByte(m_qualityInput, 80), 10, 100);
|
|
await m_manager.Uvc.SetUsbUvcParametersAsync(new UsbUvcParameters
|
|
{
|
|
UsbType = UsbTypeValue(),
|
|
BitrateMbps = bitrate,
|
|
MjpgQuality = (byte)quality,
|
|
Mirror = m_mirrorToggle.isOn,
|
|
Rotate180 = m_rotateToggle.isOn
|
|
});
|
|
SetResult("UVC 参数设置成功");
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 SetFaceBox 方法。
|
|
/// </summary>
|
|
public void SetFaceBox()
|
|
{
|
|
RunAsync(async () => { if (RequireReady()) { await m_manager.Uvc.SetFaceLocationDisplayAsync(m_faceBoxToggle.isOn); SetResult("人脸框显示设置成功"); } });
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 UsbTypeValue 方法。
|
|
/// </summary>
|
|
private byte UsbTypeValue()
|
|
{
|
|
switch (m_usbTypeDropdown.value)
|
|
{
|
|
case 0: return 0x11;
|
|
case 1: return 0x20;
|
|
case 2: return 0x91;
|
|
default: return 0xA0;
|
|
}
|
|
}
|
|
}
|
|
|
|
public sealed class FaceAdvancedSettingsPage : FaceModulePageBase
|
|
{
|
|
// 内部状态字段。
|
|
private Dropdown m_rgbDropdown;
|
|
// 内部状态字段。
|
|
private Toggle m_duplicateToggle;
|
|
// 内部状态字段。
|
|
private Toggle m_demoToggle;
|
|
|
|
public override void Build(FaceModuleUiBuilder builder)
|
|
{
|
|
builder.Text(transform, "高级设置", 30);
|
|
m_rgbDropdown = builder.Dropdown(transform, new[] { "0", "1", "2 默认", "3", "4" });
|
|
m_rgbDropdown.value = 2;
|
|
m_duplicateToggle = builder.Toggle(transform, "启用查重", true);
|
|
m_demoToggle = builder.Toggle(transform, "Demo Mode: 所有人可解锁,但仍做活体检测", false);
|
|
var row = builder.Row(transform);
|
|
builder.Button(row.transform, "设置 RGB_LEVEL", SetRgb);
|
|
builder.Button(row.transform, "设置查重", SetDuplicate);
|
|
builder.Button(row.transform, "读取查重状态", ReadDuplicate);
|
|
builder.Button(row.transform, "设置 Demo Mode", SetDemoMode);
|
|
m_resultText = builder.Text(transform, "操作结果: -");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 SetRgb 方法。
|
|
/// </summary>
|
|
public void SetRgb()
|
|
{
|
|
RunAsync(async () => { if (RequireReady()) { await m_manager.Uvc.SetRgbLevelAsync((byte)m_rgbDropdown.value); SetResult("RGB_LEVEL 设置成功"); } });
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 SetDuplicate 方法。
|
|
/// </summary>
|
|
public void SetDuplicate()
|
|
{
|
|
RunAsync(async () => { if (RequireReady()) { await m_manager.Uvc.SetDuplicateCheckAsync(m_duplicateToggle.isOn); SetResult("查重设置成功"); } });
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 ReadDuplicate 方法。
|
|
/// </summary>
|
|
public void ReadDuplicate()
|
|
{
|
|
RunAsync(async () =>
|
|
{
|
|
if (!RequireReady()) return;
|
|
m_duplicateToggle.isOn = await m_manager.Uvc.ReadDuplicateCheckAsync();
|
|
SetResult("查重状态已读取");
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 SetDemoMode 方法。
|
|
/// </summary>
|
|
public void SetDemoMode()
|
|
{
|
|
RunAsync(async () => { if (RequireReady()) { await m_manager.Basic.SetDemoModeAsync(m_demoToggle.isOn); SetResult("Demo Mode 设置成功"); } });
|
|
}
|
|
}
|
|
|
|
public sealed class FaceEncryptionPage : FaceModulePageBase
|
|
{
|
|
// 内部状态字段。
|
|
private InputField m_releaseKeyInput;
|
|
// 内部状态字段。
|
|
private InputField m_debugKeyInput;
|
|
// 内部状态字段。
|
|
private InputField m_seedInput;
|
|
// 内部状态字段。
|
|
private Text m_stateText;
|
|
|
|
public override void Build(FaceModuleUiBuilder builder)
|
|
{
|
|
builder.Text(transform, "加密通信", 30);
|
|
m_releaseKeyInput = builder.Input(transform, "Release Key 16 bytes Hex", string.Empty);
|
|
m_debugKeyInput = builder.Input(transform, "Debug Key 16 bytes Hex", string.Empty);
|
|
m_seedInput = builder.Input(transform, "Seed 4 bytes Hex", string.Empty);
|
|
var row = builder.Row(transform);
|
|
builder.Button(row.transform, "设置 Release Key", SetReleaseKey);
|
|
builder.Button(row.transform, "设置 Debug Key", SetDebugKey);
|
|
builder.Button(row.transform, "生成随机 Seed", GenerateSeed);
|
|
builder.Button(row.transform, "Init Encryption", InitEncryption);
|
|
m_stateText = builder.Text(transform, "当前加密状态: 加密算法未接入,当前仅完成协议入口");
|
|
m_resultText = builder.Text(transform, "操作结果: -");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 SetReleaseKey 方法。
|
|
/// </summary>
|
|
public void SetReleaseKey()
|
|
{
|
|
RunAsync(async () => { if (RequireReady()) { await m_manager.Encryption.SetReleaseEncryptionKeyAsync(ParseHex(m_releaseKeyInput.text, 16)); SetResult("Release Key 设置成功"); } });
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 SetDebugKey 方法。
|
|
/// </summary>
|
|
public void SetDebugKey()
|
|
{
|
|
RunAsync(async () => { if (RequireReady()) { await m_manager.Encryption.SetDebugEncryptionKeyAsync(ParseHex(m_debugKeyInput.text, 16)); SetResult("Debug Key 设置成功"); } });
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 GenerateSeed 方法。
|
|
/// </summary>
|
|
public void GenerateSeed()
|
|
{
|
|
var bytes = new byte[4];
|
|
using (var rng = new System.Security.Cryptography.RNGCryptoServiceProvider())
|
|
{
|
|
rng.GetBytes(bytes);
|
|
}
|
|
|
|
m_seedInput.text = BitConverter.ToString(bytes).Replace("-", " ");
|
|
SetResult("Seed 已生成");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 InitEncryption 方法。
|
|
/// </summary>
|
|
public void InitEncryption()
|
|
{
|
|
RunAsync(async () =>
|
|
{
|
|
if (!RequireReady()) return;
|
|
await m_manager.Encryption.InitEncryptionAsync(ParseHex(m_seedInput.text, 4), 0);
|
|
m_stateText.text = "当前加密状态: 协议入口已调用,供应商算法仍未接入";
|
|
SetResult("Init Encryption 完成");
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// 执行 ParseHex 方法。
|
|
/// </summary>
|
|
private static byte[] ParseHex(string text, int expectedLength)
|
|
{
|
|
var parts = text.Split(new[] { ' ', '-', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
|
if (parts.Length != expectedLength) throw new ArgumentException("需要 " + expectedLength + " bytes Hex");
|
|
var bytes = new byte[parts.Length];
|
|
for (var i = 0; i < parts.Length; i++) bytes[i] = Convert.ToByte(parts[i], 16);
|
|
return bytes;
|
|
}
|
|
}
|
|
|
|
public sealed class FaceFirmwarePage : FaceModulePageBase
|
|
{
|
|
// 内部状态字段。
|
|
private Slider m_progress;
|
|
// 内部状态字段。
|
|
private Text m_percentText;
|
|
|
|
public override void Build(FaceModuleUiBuilder builder)
|
|
{
|
|
builder.Text(transform, "固件升级", 30);
|
|
builder.Text(transform, "风险提示: 升级过程中不要断电;固件文件应由 U 盘或模组侧准备。", 22);
|
|
builder.Button(transform, "启动 U 盘升级", StartUpgrade);
|
|
m_progress = builder.Slider(transform);
|
|
m_percentText = builder.Text(transform, "当前进度: 0%");
|
|
m_resultText = builder.Text(transform, "操作结果: -");
|
|
}
|
|
|
|
/// <summary>启动 U 盘固件升级。</summary>
|
|
/// <summary>
|
|
/// 执行 StartUpgrade 方法。
|
|
/// </summary>
|
|
public void StartUpgrade()
|
|
{
|
|
RunAsync(async () =>
|
|
{
|
|
if (!RequireReady()) return;
|
|
await m_manager.Basic.UpgradeFirmwareAsync();
|
|
m_progress.value = 1;
|
|
m_percentText.text = "当前进度: 已启动";
|
|
SetResult("固件升级命令已发送");
|
|
});
|
|
}
|
|
}
|
|
}
|