2173 lines
76 KiB
C#
2173 lines
76 KiB
C#
|
|
using System;
|
|||
|
|
using UnityEngine;
|
|||
|
|
|
|||
|
|
namespace BLEProtocol
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// BLE通信协议常量定义
|
|||
|
|
/// </summary>
|
|||
|
|
public static class BLEConstants
|
|||
|
|
{
|
|||
|
|
// 帧头
|
|||
|
|
public const byte FRAME_HEADER_1 = 0xAA;
|
|||
|
|
public const byte FRAME_HEADER_2 = 0x55;
|
|||
|
|
|
|||
|
|
// 读/写标识
|
|||
|
|
public const byte RW_READ = 0x00; // 读操作
|
|||
|
|
public const byte RW_WRITE = 0x01; // 写操作
|
|||
|
|
public const byte RW_NOTIFY = 0x02; // 通知
|
|||
|
|
|
|||
|
|
// 命令码 - 设备注册与认证类 (0x01-0x0F)
|
|||
|
|
public const byte CMD_QUERY_REGISTRATION = 0x01; // 查询设备注册状态
|
|||
|
|
public const byte CMD_DEVICE_REGISTER = 0x02; // 设备注册绑定
|
|||
|
|
public const byte CMD_USER_LOGIN = 0x03; // 用户登录
|
|||
|
|
public const byte CMD_FINGERPRINT_ENABLE = 0x04; // 指纹登录使能设置
|
|||
|
|
public const byte CMD_FINGERPRINT_RECORD = 0x05; // 指纹录制请求
|
|||
|
|
public const byte CMD_QUERY_USER_LIST = 0x07; // 查询用户列表
|
|||
|
|
public const byte CMD_UNREGISTER_USER = 0x08; // 注销用户
|
|||
|
|
|
|||
|
|
// 通知类型
|
|||
|
|
public const byte NOTIFY_MOSQUITO_DATA = 0x0B; // 蚊虫数据通知
|
|||
|
|
|
|||
|
|
// 命令码 - 设备设置类 (0x10-0x1F)
|
|||
|
|
public const byte CMD_LANGUAGE_SETTING = 0x10; // 语言设置
|
|||
|
|
public const byte CMD_TIME_SETTING = 0x11; // 时间设置
|
|||
|
|
public const byte CMD_SCHEDULE_TASK = 0x12; // 定时任务设置
|
|||
|
|
|
|||
|
|
// 命令码 - 外设控制类 (0x20-0x3F)
|
|||
|
|
public const byte CMD_LCD_SLEEP_SETTING = 0x20; // LCD休眠设置
|
|||
|
|
public const byte CMD_LCD_BRIGHTNESS_SETTING = 0x21; // LCD亮度设置
|
|||
|
|
public const byte CMD_RGB_CONTROL = 0x22; // RGB指示灯控制
|
|||
|
|
public const byte CMD_WIFI_CONTROL = 0x23; // WIFI控制
|
|||
|
|
public const byte CMD_BLE_CONTROL = 0x24; // BLE控制
|
|||
|
|
public const byte CMD_MULTIMEDIA_CONTROL = 0x25; // 多媒体控制
|
|||
|
|
public const byte CMD_FILL_LIGHT_CONTROL = 0x26; // 补光灯控制
|
|||
|
|
public const byte CMD_FILL_LIGHT_CONNECTION_STATUS = 0x27; // 查询补光灯连接状态
|
|||
|
|
public const byte CMD_VISIBLE_LASER_CONTROL = 0x28; // 可见光激光器控制
|
|||
|
|
|
|||
|
|
// 命令码 - 害虫消灭控制类 (0x40-0x4F)
|
|||
|
|
public const byte CMD_ANGLE_CONTROL = 0x40; // 角度控制
|
|||
|
|
public const byte CMD_DISTANCE_CONTROL = 0x41; // 距离控制
|
|||
|
|
|
|||
|
|
// 命令码 - 安全设置类 (0x50-0x5F)
|
|||
|
|
public const byte CMD_MILLIMETER_WAVE_SETTING = 0x50; // 毫米波雷达设置
|
|||
|
|
public const byte CMD_VISUAL_DETECTION_SETTING = 0x51; // 视觉检测设置
|
|||
|
|
public const byte CMD_ACCELEROMETER_SETTING = 0x52; // 加速度传感器设置
|
|||
|
|
public const byte CMD_ACCELEROMETER_DATA = 0x53; // 查询加速度传感器数据
|
|||
|
|
public const byte CMD_TEMPERATURE_MONITOR_SETTING = 0x55; // 温度监控设置
|
|||
|
|
public const byte CMD_ENVIRONMENT_CHANGE_TEST = 0x56; // 环境变化检测和试射测试
|
|||
|
|
|
|||
|
|
// 命令码 - 设备管理类 (0x60-0x6F)
|
|||
|
|
public const byte CMD_FACTORY_RESET = 0x60; // 恢复出厂设置
|
|||
|
|
|
|||
|
|
// 命令码 - 状态查询类 (0xA0-0xBF)
|
|||
|
|
public const byte CMD_WORK_MODE_SETTING = 0xA0; // 工作模式设置
|
|||
|
|
public const byte CMD_HARDWARE_STATUS = 0xA1; // 查询硬件状态
|
|||
|
|
public const byte CMD_DEVICE_INFO = 0xA2; // 查询设备信息
|
|||
|
|
public const byte CMD_STATISTICS_DATA = 0xA3; // 查询统计数据
|
|||
|
|
public const byte CMD_MOSQUITO_DATA_QUERY = 0xA4; // 查询蚊虫数据
|
|||
|
|
public const byte CMD_SENSOR_DATA = 0xA5; // 查询设备传感器数据
|
|||
|
|
|
|||
|
|
// 命令码 - 连接管理类 (0x70-0x7F)
|
|||
|
|
public const byte CMD_DISCONNECT = 0x70; // 主动断开连接
|
|||
|
|
|
|||
|
|
// 命令码 - OTA升级类 (0x80-0x9F)
|
|||
|
|
public const byte CMD_OTA_QUERY_VERSION = 0x80; // 查询OTA版本信息
|
|||
|
|
public const byte CMD_OTA_START = 0x81; // 开始升级
|
|||
|
|
public const byte CMD_OTA_TRANSFER_DATA = 0x82; // 传输固件数据
|
|||
|
|
public const byte CMD_OTA_END_TRANSFER = 0x83; // 结束传输
|
|||
|
|
public const byte CMD_OTA_CANCEL = 0x85; // 取消升级
|
|||
|
|
|
|||
|
|
// 状态码(0=成功,非0=错误)
|
|||
|
|
public const byte STATUS_SUCCESS = 0x00; // 成功
|
|||
|
|
public const byte STATUS_PARAM_ERROR = 0x01; // 参数错误
|
|||
|
|
public const byte STATUS_PERMISSION_ERROR = 0x02; // 权限错误
|
|||
|
|
public const byte STATUS_DEVICE_BUSY = 0x03; // 设备忙
|
|||
|
|
public const byte STATUS_CMD_NOT_SUPPORT = 0x04; // 命令不支持
|
|||
|
|
public const byte STATUS_DATA_CHECK_FAIL = 0x05; // 数据校验失败
|
|||
|
|
public const byte STATUS_TIMEOUT = 0x06; // 超时
|
|||
|
|
public const byte STATUS_DEVICE_ERROR = 0x07; // 设备错误
|
|||
|
|
|
|||
|
|
// 最大用户名长度
|
|||
|
|
public const int MAX_USERNAME_LENGTH = 16;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 通信帧结构
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct BLEFrame
|
|||
|
|
{
|
|||
|
|
public byte Header1; // 帧头1: 0xAA
|
|||
|
|
public byte Header2; // 帧头2: 0x55
|
|||
|
|
public byte Command; // 命令码
|
|||
|
|
public byte ReadWrite; // 读/写标识: 0x00=读, 0x01=写, 0x02=通知
|
|||
|
|
public byte Length; // 数据长度
|
|||
|
|
public byte[] Data; // 数据字段
|
|||
|
|
public ushort Crc16; // CRC16校验
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 将帧序列化为字节数组
|
|||
|
|
/// </summary>
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
int dataLength = Data != null ? Data.Length : 0;
|
|||
|
|
byte[] bytes = new byte[6 + dataLength + 2]; // 帧头(2) + 命令(1) + 读写(1) + 长度(1) + 数据 + CRC(2)
|
|||
|
|
|
|||
|
|
bytes[0] = Header1;
|
|||
|
|
bytes[1] = Header2;
|
|||
|
|
bytes[2] = Command;
|
|||
|
|
bytes[3] = ReadWrite;
|
|||
|
|
bytes[4] = (byte)dataLength;
|
|||
|
|
|
|||
|
|
if (dataLength > 0)
|
|||
|
|
{
|
|||
|
|
Buffer.BlockCopy(Data, 0, bytes, 5, dataLength);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 计算CRC16 (帧头到数据字段)
|
|||
|
|
ushort crc = CRC16.Calculate(bytes, 0, 5 + dataLength);
|
|||
|
|
bytes[5 + dataLength] = (byte)(crc & 0xFF);
|
|||
|
|
bytes[6 + dataLength] = (byte)((crc >> 8) & 0xFF);
|
|||
|
|
|
|||
|
|
// 调试日志:打印发送的CRC
|
|||
|
|
UnityEngine.Debug.Log($"[CRC-SEND] 命令=0x{Command:X2}, 数据长度={dataLength}, CRC=0x{crc:X4}, CRC字节={bytes[5 + dataLength]:X2}-{bytes[6 + dataLength]:X2}");
|
|||
|
|
|
|||
|
|
return bytes;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 从字节数组解析帧
|
|||
|
|
/// </summary>
|
|||
|
|
public static bool TryParse(byte[] bytes, out BLEFrame frame)
|
|||
|
|
{
|
|||
|
|
frame = new BLEFrame();
|
|||
|
|
|
|||
|
|
if (bytes == null || bytes.Length < 7) // 最小帧长度: 帧头(2) + 命令(1) + 读写(1) + 长度(1) + CRC(2) = 7字节
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 验证帧头
|
|||
|
|
if (bytes[0] != BLEConstants.FRAME_HEADER_1 || bytes[1] != BLEConstants.FRAME_HEADER_2)
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
frame.Header1 = bytes[0];
|
|||
|
|
frame.Header2 = bytes[1];
|
|||
|
|
frame.Command = bytes[2];
|
|||
|
|
frame.ReadWrite = bytes[3];
|
|||
|
|
frame.Length = bytes[4];
|
|||
|
|
|
|||
|
|
int dataLength = frame.Length;
|
|||
|
|
int expectedLength = 7 + dataLength; // 帧头(2) + 命令(1) + 读写(1) + 长度(1) + 数据(dataLength) + CRC(2)
|
|||
|
|
if (bytes.Length < expectedLength)
|
|||
|
|
{
|
|||
|
|
Debug.LogWarning($"数据长度不足: 需要 {expectedLength} 字节, 实际 {bytes.Length} 字节");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 提取数据
|
|||
|
|
if (dataLength > 0)
|
|||
|
|
{
|
|||
|
|
frame.Data = new byte[dataLength];
|
|||
|
|
Buffer.BlockCopy(bytes, 5, frame.Data, 0, dataLength);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 提取CRC
|
|||
|
|
frame.Crc16 = (ushort)(bytes[5 + dataLength] | (bytes[6 + dataLength] << 8));
|
|||
|
|
|
|||
|
|
// 验证CRC
|
|||
|
|
ushort calculatedCrc = CRC16.Calculate(bytes, 0, 5 + dataLength);
|
|||
|
|
Debug.Log($"[CRC调试] 计算值={calculatedCrc:X4}, 接收值={frame.Crc16:X4}");
|
|||
|
|
Debug.Log($"[CRC调试] 计算数据: {System.BitConverter.ToString(bytes, 0, 5 + dataLength)}");
|
|||
|
|
Debug.Log($"[CRC调试] 接收CRC字节: {bytes[5 + dataLength]:X2}-{bytes[6 + dataLength]:X2}");
|
|||
|
|
if (calculatedCrc != frame.Crc16)
|
|||
|
|
{
|
|||
|
|
Debug.LogWarning($"CRC校验失败: 计算值={calculatedCrc:X4} ({calculatedCrc & 0xFF:X2}-{(calculatedCrc >> 8) & 0xFF:X2}), 接收值={frame.Crc16:X4} ({frame.Crc16 & 0xFF:X2}-{(frame.Crc16 >> 8) & 0xFF:X2})");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 设备注册状态响应
|
|||
|
|
/// 命令 0x01 返回:
|
|||
|
|
/// - 文档标准:1字节(0x00=未注册, 0x01=已注册)
|
|||
|
|
/// - 当前固件:2字节(00-01,第一位状态码,第二位注册状态)
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct DeviceRegistrationStatus
|
|||
|
|
{
|
|||
|
|
public byte Status; // 响应状态码(0x00=成功)
|
|||
|
|
public byte RegistrationState; // 0x00=否/未注册, 0x01=是/已注册
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 从数据字节解析
|
|||
|
|
/// 兼容1字节和2字节格式
|
|||
|
|
/// </summary>
|
|||
|
|
public static DeviceRegistrationStatus FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var status = new DeviceRegistrationStatus();
|
|||
|
|
if (data == null || data.Length == 0)
|
|||
|
|
return status;
|
|||
|
|
|
|||
|
|
if (data.Length == 1)
|
|||
|
|
{
|
|||
|
|
// 标准格式:1字节,直接是注册状态
|
|||
|
|
status.Status = 0x00; // 假设成功
|
|||
|
|
status.RegistrationState = data[0];
|
|||
|
|
}
|
|||
|
|
else if (data.Length >= 2)
|
|||
|
|
{
|
|||
|
|
// 当前固件格式:2字节(00-01)
|
|||
|
|
status.Status = data[0]; // 第一位:状态码
|
|||
|
|
status.RegistrationState = data[1]; // 第二位:注册状态
|
|||
|
|
}
|
|||
|
|
return status;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 是否已注册(0=否,1=是)
|
|||
|
|
/// </summary>
|
|||
|
|
public bool IsRegistered => RegistrationState == 0x01;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 设备注册请求
|
|||
|
|
/// 注意:根据协议,只发送用户名,不发送密码
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct DeviceRegisterRequest
|
|||
|
|
{
|
|||
|
|
public string Username; // 用户名 (最多16字节ASCII)
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 序列化为字节数组
|
|||
|
|
/// 只发送用户名(16字节),不发送密码
|
|||
|
|
/// </summary>
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
byte[] bytes = new byte[16]; // 仅用户名(16字节)
|
|||
|
|
|
|||
|
|
// 用户名 (16字节, 不足补0)
|
|||
|
|
byte[] usernameBytes = System.Text.Encoding.ASCII.GetBytes(Username ?? "");
|
|||
|
|
int usernameLen = System.Math.Min(usernameBytes.Length, 16);
|
|||
|
|
System.Buffer.BlockCopy(usernameBytes, 0, bytes, 0, usernameLen);
|
|||
|
|
|
|||
|
|
return bytes;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 注销用户请求
|
|||
|
|
/// 命令 0x08:注销指定用户
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct UnregisterUserRequest
|
|||
|
|
{
|
|||
|
|
public string Username; // 要注销的用户名 (最多16字节ASCII)
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 序列化为字节数组
|
|||
|
|
/// 用户名(16字节,不足补0)
|
|||
|
|
/// </summary>
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
byte[] bytes = new byte[16]; // 用户名(16字节)
|
|||
|
|
|
|||
|
|
// 用户名 (16字节, 不足补0)
|
|||
|
|
byte[] usernameBytes = System.Text.Encoding.ASCII.GetBytes(Username ?? "");
|
|||
|
|
int usernameLen = System.Math.Min(usernameBytes.Length, 16);
|
|||
|
|
System.Buffer.BlockCopy(usernameBytes, 0, bytes, 0, usernameLen);
|
|||
|
|
|
|||
|
|
return bytes;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 注销用户响应
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct UnregisterUserResponse
|
|||
|
|
{
|
|||
|
|
public byte Status; // 响应状态码(0x00=成功)
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 是否成功
|
|||
|
|
/// </summary>
|
|||
|
|
public bool IsSuccess => Status == 0x00;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 从数据字节解析
|
|||
|
|
/// </summary>
|
|||
|
|
public static UnregisterUserResponse FromBytes(byte status)
|
|||
|
|
{
|
|||
|
|
return new UnregisterUserResponse { Status = status };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 用户登录请求
|
|||
|
|
/// 命令 0x03:用户登录(密码)
|
|||
|
|
/// 数据格式:用户名(16字节) + 解锁状态(1字节),解锁状态固定为1
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct UserLoginRequest
|
|||
|
|
{
|
|||
|
|
public string Username; // 用户名 (最多16字节ASCII)
|
|||
|
|
public byte UnlockStatus; // 解锁状态,固定为1
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 序列化为字节数组
|
|||
|
|
/// 用户名(16字节,不足补0)+ 解锁状态(1字节,固定为1)
|
|||
|
|
/// </summary>
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
byte[] bytes = new byte[17]; // 用户名(16字节) + 解锁状态(1字节)
|
|||
|
|
|
|||
|
|
// 用户名 (16字节, 不足补0)
|
|||
|
|
byte[] usernameBytes = System.Text.Encoding.ASCII.GetBytes(Username ?? "");
|
|||
|
|
int usernameLen = System.Math.Min(usernameBytes.Length, 16);
|
|||
|
|
System.Buffer.BlockCopy(usernameBytes, 0, bytes, 0, usernameLen);
|
|||
|
|
|
|||
|
|
// 解锁状态固定为1
|
|||
|
|
bytes[16] = 0x01;
|
|||
|
|
|
|||
|
|
return bytes;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 用户登录响应
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct UserLoginResponse
|
|||
|
|
{
|
|||
|
|
public byte Status; // 响应状态码(0x00=成功)
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 是否成功
|
|||
|
|
/// </summary>
|
|||
|
|
public bool IsSuccess => Status == 0x00;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 从数据字节解析
|
|||
|
|
/// </summary>
|
|||
|
|
public static UserLoginResponse FromBytes(byte status)
|
|||
|
|
{
|
|||
|
|
return new UserLoginResponse { Status = status };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 指纹登录使能设置请求
|
|||
|
|
/// 命令 0x04:设置用户指纹登录使能状态
|
|||
|
|
/// 数据格式:用户名(16字节) + 使能状态(1字节)
|
|||
|
|
/// 使能状态:0x00=禁用, 0x01=使能
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct FingerprintEnableRequest
|
|||
|
|
{
|
|||
|
|
public string Username; // 用户名 (最多16字节ASCII)
|
|||
|
|
public bool Enable; // 使能状态:true=使能, false=禁用
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 序列化为字节数组
|
|||
|
|
/// 用户名(16字节,不足补0)+ 使能状态(1字节)
|
|||
|
|
/// </summary>
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
byte[] bytes = new byte[17]; // 用户名(16字节) + 使能状态(1字节)
|
|||
|
|
|
|||
|
|
// 用户名 (16字节, 不足补0)
|
|||
|
|
byte[] usernameBytes = System.Text.Encoding.ASCII.GetBytes(Username ?? "");
|
|||
|
|
int usernameLen = System.Math.Min(usernameBytes.Length, 16);
|
|||
|
|
System.Buffer.BlockCopy(usernameBytes, 0, bytes, 0, usernameLen);
|
|||
|
|
|
|||
|
|
// 使能状态
|
|||
|
|
bytes[16] = Enable ? (byte)0x01 : (byte)0x00;
|
|||
|
|
|
|||
|
|
return bytes;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 指纹登录使能设置响应
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct FingerprintEnableResponse
|
|||
|
|
{
|
|||
|
|
public byte Status; // 响应状态码(0x00=成功)
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 是否成功
|
|||
|
|
/// </summary>
|
|||
|
|
public bool IsSuccess => Status == 0x00;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 从数据字节解析
|
|||
|
|
/// </summary>
|
|||
|
|
public static FingerprintEnableResponse FromBytes(byte status)
|
|||
|
|
{
|
|||
|
|
return new FingerprintEnableResponse { Status = status };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 指纹录制请求
|
|||
|
|
/// 命令 0x05:启动指纹注册流程
|
|||
|
|
/// 数据格式:用户名(16字节)
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct FingerprintRecordRequest
|
|||
|
|
{
|
|||
|
|
public string Username; // 用户名 (最多16字节ASCII)
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 序列化为字节数组
|
|||
|
|
/// 用户名(16字节,不足补0)
|
|||
|
|
/// </summary>
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
byte[] bytes = new byte[16]; // 用户名(16字节)
|
|||
|
|
|
|||
|
|
// 用户名 (16字节, 不足补0)
|
|||
|
|
byte[] usernameBytes = System.Text.Encoding.ASCII.GetBytes(Username ?? "");
|
|||
|
|
int usernameLen = System.Math.Min(usernameBytes.Length, 16);
|
|||
|
|
System.Buffer.BlockCopy(usernameBytes, 0, bytes, 0, usernameLen);
|
|||
|
|
|
|||
|
|
return bytes;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 指纹录制响应
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct FingerprintRecordResponse
|
|||
|
|
{
|
|||
|
|
public byte Status; // 响应状态码(0x00=成功)
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 是否成功
|
|||
|
|
/// </summary>
|
|||
|
|
public bool IsSuccess => Status == 0x00;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 从数据字节解析
|
|||
|
|
/// </summary>
|
|||
|
|
public static FingerprintRecordResponse FromBytes(byte status)
|
|||
|
|
{
|
|||
|
|
return new FingerprintRecordResponse { Status = status };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 通用响应结构
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct BLEResponse
|
|||
|
|
{
|
|||
|
|
public byte Status; // 状态码(0=成功,其他=错误码)
|
|||
|
|
public byte[] Data; // 响应数据
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 是否成功(状态码0=成功,非0=失败)
|
|||
|
|
/// </summary>
|
|||
|
|
public bool IsSuccess => Status == 0x00;
|
|||
|
|
|
|||
|
|
public static BLEResponse FromFrame(BLEFrame frame)
|
|||
|
|
{
|
|||
|
|
var response = new BLEResponse();
|
|||
|
|
if (frame.Data != null && frame.Data.Length > 0)
|
|||
|
|
{
|
|||
|
|
response.Status = frame.Data[0];
|
|||
|
|
// 兼容返回2字节的情况,取剩余字节作为数据
|
|||
|
|
if (frame.Data.Length > 1)
|
|||
|
|
{
|
|||
|
|
response.Data = new byte[frame.Data.Length - 1];
|
|||
|
|
System.Buffer.BlockCopy(frame.Data, 1, response.Data, 0, frame.Data.Length - 1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return response;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 用户信息
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct UserInfo
|
|||
|
|
{
|
|||
|
|
public string Username; // 用户名
|
|||
|
|
public byte FingerprintId; // 指纹ID
|
|||
|
|
public bool HasFingerprint; // 是否有指纹
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 用户列表响应
|
|||
|
|
/// 命令 0x07 返回:
|
|||
|
|
/// - 文档标准:用户数量(1字节) + 每个用户:用户名(16字节) + 指纹ID(1字节) + 指纹ID有效标志(1字节)
|
|||
|
|
/// - 当前固件:用户数量(2字节,低字节在前) + 每个用户信息
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct UserListResponse
|
|||
|
|
{
|
|||
|
|
public byte Status; // 响应状态码(0x00=成功)
|
|||
|
|
public ushort UserCount; // 用户数量(兼容1字节和2字节)
|
|||
|
|
public UserInfo[] Users; // 用户列表
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 从数据字节解析
|
|||
|
|
/// 数据格式(不包含状态码,因为BLEResponse已经提取了):
|
|||
|
|
/// - 标准格式:用户数量(1) + [用户名(16) + 指纹ID(1) + 指纹有效标志(1)] * N
|
|||
|
|
/// - 当前固件:用户数量(2) + [用户名(16) + 指纹ID(1) + 指纹有效标志(1)] * N
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="status">响应状态码</param>
|
|||
|
|
/// <param name="data">数据内容(不包含状态码)</param>
|
|||
|
|
public static UserListResponse FromBytes(byte status, byte[] data)
|
|||
|
|
{
|
|||
|
|
var response = new UserListResponse();
|
|||
|
|
response.Status = status;
|
|||
|
|
if (data == null || data.Length < 1)
|
|||
|
|
return response;
|
|||
|
|
|
|||
|
|
// 注意:传入的data不包含状态码,因为BLEResponse.FromFrame已经提取了
|
|||
|
|
// 判断用户数量是1字节还是2字节
|
|||
|
|
int userSize = 16 + 1 + 1; // 用户名(16) + 指纹ID(1) + 指纹有效标志(1) = 18字节
|
|||
|
|
|
|||
|
|
// 尝试按1字节解析
|
|||
|
|
byte userCount1Byte = data[0];
|
|||
|
|
int expectedLen1Byte = 1 + userCount1Byte * userSize;
|
|||
|
|
|
|||
|
|
// 尝试按2字节解析(小端序)
|
|||
|
|
ushort userCount2Byte = 0;
|
|||
|
|
if (data.Length >= 2)
|
|||
|
|
{
|
|||
|
|
userCount2Byte = (ushort)(data[0] | (data[1] << 8));
|
|||
|
|
}
|
|||
|
|
int expectedLen2Byte = 2 + userCount2Byte * userSize;
|
|||
|
|
|
|||
|
|
int offset;
|
|||
|
|
if (data.Length == expectedLen1Byte)
|
|||
|
|
{
|
|||
|
|
// 1字节格式
|
|||
|
|
response.UserCount = userCount1Byte;
|
|||
|
|
offset = 1;
|
|||
|
|
}
|
|||
|
|
else if (data.Length == expectedLen2Byte)
|
|||
|
|
{
|
|||
|
|
// 2字节格式
|
|||
|
|
response.UserCount = userCount2Byte;
|
|||
|
|
offset = 2;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
// 默认按1字节解析
|
|||
|
|
response.UserCount = userCount1Byte;
|
|||
|
|
offset = 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (response.UserCount == 0 || data.Length < offset + response.UserCount * userSize)
|
|||
|
|
{
|
|||
|
|
response.Users = new UserInfo[0];
|
|||
|
|
return response;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
response.Users = new UserInfo[response.UserCount];
|
|||
|
|
|
|||
|
|
for (int i = 0; i < response.UserCount; i++)
|
|||
|
|
{
|
|||
|
|
// 用户名 (16字节)
|
|||
|
|
byte[] usernameBytes = new byte[16];
|
|||
|
|
Buffer.BlockCopy(data, offset, usernameBytes, 0, 16);
|
|||
|
|
// 去除末尾的0字节
|
|||
|
|
int nameLen = 0;
|
|||
|
|
while (nameLen < 16 && usernameBytes[nameLen] != 0)
|
|||
|
|
nameLen++;
|
|||
|
|
response.Users[i].Username = System.Text.Encoding.ASCII.GetString(usernameBytes, 0, nameLen);
|
|||
|
|
offset += 16;
|
|||
|
|
|
|||
|
|
// 指纹ID
|
|||
|
|
response.Users[i].FingerprintId = data[offset++];
|
|||
|
|
|
|||
|
|
// 指纹ID有效标志
|
|||
|
|
response.Users[i].HasFingerprint = data[offset++] == 0x01;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return response;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 是否成功
|
|||
|
|
/// </summary>
|
|||
|
|
public bool IsSuccess => Status == 0x00;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#region 设备设置类结构体 (0x10-0x1F)
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 语言设置
|
|||
|
|
/// 命令 0x10:0x00=英文, 0x01=中文
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct LanguageSetting
|
|||
|
|
{
|
|||
|
|
public byte Language; // 0x00=英文, 0x01=中文
|
|||
|
|
|
|||
|
|
public bool IsChinese => Language == 0x01;
|
|||
|
|
public bool IsEnglish => Language == 0x00;
|
|||
|
|
|
|||
|
|
public static LanguageSetting FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var setting = new LanguageSetting();
|
|||
|
|
if (data != null && data.Length > 0)
|
|||
|
|
{
|
|||
|
|
setting.Language = data[0];
|
|||
|
|
}
|
|||
|
|
return setting;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
return new byte[] { Language };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 时间设置
|
|||
|
|
/// 命令 0x11:年(1) + 月(1) + 日(1) + 时(1) + 分(1) + 秒(1)
|
|||
|
|
/// 年:相对于2000年的偏移(0-255对应2000-2255年)
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct TimeSetting
|
|||
|
|
{
|
|||
|
|
public byte Year; // 相对于2000年的偏移
|
|||
|
|
public byte Month; // 1-12
|
|||
|
|
public byte Day; // 1-31
|
|||
|
|
public byte Hour; // 0-23
|
|||
|
|
public byte Minute; // 0-59
|
|||
|
|
public byte Second; // 0-59
|
|||
|
|
|
|||
|
|
public int ActualYear => 2000 + Year;
|
|||
|
|
|
|||
|
|
public static TimeSetting FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var setting = new TimeSetting();
|
|||
|
|
if (data != null && data.Length >= 6)
|
|||
|
|
{
|
|||
|
|
setting.Year = data[0];
|
|||
|
|
setting.Month = data[1];
|
|||
|
|
setting.Day = data[2];
|
|||
|
|
setting.Hour = data[3];
|
|||
|
|
setting.Minute = data[4];
|
|||
|
|
setting.Second = data[5];
|
|||
|
|
}
|
|||
|
|
return setting;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
return new byte[] { Year, Month, Day, Hour, Minute, Second };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public override string ToString()
|
|||
|
|
{
|
|||
|
|
return $"{ActualYear}-{Month:D2}-{Day:D2} {Hour:D2}:{Minute:D2}:{Second:D2}";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 定时任务模式
|
|||
|
|
/// </summary>
|
|||
|
|
public enum ScheduleTaskMode : byte
|
|||
|
|
{
|
|||
|
|
Standby = 0x00, // 待机(用于定时关机)
|
|||
|
|
Scan = 0x01, // 扫描
|
|||
|
|
Sterilize = 0x02 // 消杀
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 定时任务
|
|||
|
|
/// 命令 0x12:任务ID(1) + 开关(1) + 开始小时(1) + 开始分钟(1) + 结束小时(1) + 结束分钟(1) + 模式(1) + 重复(1)
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct ScheduleTask
|
|||
|
|
{
|
|||
|
|
public byte TaskId; // 任务ID:0-4
|
|||
|
|
public bool Enabled; // 开关:true=启用, false=禁用
|
|||
|
|
public byte StartHour; // 开始小时:0-23
|
|||
|
|
public byte StartMinute; // 开始分钟:0-59
|
|||
|
|
public byte EndHour; // 结束小时:0-23
|
|||
|
|
public byte EndMinute; // 结束分钟:0-59
|
|||
|
|
public ScheduleTaskMode Mode; // 模式
|
|||
|
|
public byte Repeat; // 重复:按位表示星期几
|
|||
|
|
|
|||
|
|
// 重复位定义
|
|||
|
|
public const byte REPEAT_MONDAY = 0x01;
|
|||
|
|
public const byte REPEAT_TUESDAY = 0x02;
|
|||
|
|
public const byte REPEAT_WEDNESDAY = 0x04;
|
|||
|
|
public const byte REPEAT_THURSDAY = 0x08;
|
|||
|
|
public const byte REPEAT_FRIDAY = 0x10;
|
|||
|
|
public const byte REPEAT_SATURDAY = 0x20;
|
|||
|
|
public const byte REPEAT_SUNDAY = 0x40;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 从字节数组解析(写入格式,包含TaskId,8字节)
|
|||
|
|
/// 格式:任务ID(1) + 开关(1) + 开始小时(1) + 开始分钟(1) + 结束小时(1) + 结束分钟(1) + 模式(1) + 重复(1)
|
|||
|
|
/// </summary>
|
|||
|
|
public static ScheduleTask FromBytes(byte[] data, int offset = 0)
|
|||
|
|
{
|
|||
|
|
var task = new ScheduleTask();
|
|||
|
|
if (data != null && data.Length >= offset + 8)
|
|||
|
|
{
|
|||
|
|
task.TaskId = data[offset];
|
|||
|
|
task.Enabled = data[offset + 1] == 0x01;
|
|||
|
|
task.StartHour = data[offset + 2];
|
|||
|
|
task.StartMinute = data[offset + 3];
|
|||
|
|
task.EndHour = data[offset + 4];
|
|||
|
|
task.EndMinute = data[offset + 5];
|
|||
|
|
task.Mode = (ScheduleTaskMode)data[offset + 6];
|
|||
|
|
task.Repeat = data[offset + 7];
|
|||
|
|
}
|
|||
|
|
return task;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 从字节数组解析(读取格式,不包含TaskId,7字节)
|
|||
|
|
/// 格式:开关(1) + 开始小时(1) + 开始分钟(1) + 结束小时(1) + 结束分钟(1) + 模式(1) + 重复(1)
|
|||
|
|
/// </summary>
|
|||
|
|
public static ScheduleTask FromBytesRead(byte[] data, int offset, byte taskId)
|
|||
|
|
{
|
|||
|
|
var task = new ScheduleTask();
|
|||
|
|
if (data != null && data.Length >= offset + 7)
|
|||
|
|
{
|
|||
|
|
task.TaskId = taskId; // 读取时TaskId从数组索引推断
|
|||
|
|
task.Enabled = data[offset] == 0x01;
|
|||
|
|
task.StartHour = data[offset + 1];
|
|||
|
|
task.StartMinute = data[offset + 2];
|
|||
|
|
task.EndHour = data[offset + 3];
|
|||
|
|
task.EndMinute = data[offset + 4];
|
|||
|
|
task.Mode = (ScheduleTaskMode)data[offset + 5];
|
|||
|
|
task.Repeat = data[offset + 6];
|
|||
|
|
}
|
|||
|
|
return task;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
return new byte[]
|
|||
|
|
{
|
|||
|
|
TaskId,
|
|||
|
|
Enabled ? (byte)0x01 : (byte)0x00,
|
|||
|
|
StartHour,
|
|||
|
|
StartMinute,
|
|||
|
|
EndHour,
|
|||
|
|
EndMinute,
|
|||
|
|
(byte)Mode,
|
|||
|
|
Repeat
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool IsRepeatMonday => (Repeat & REPEAT_MONDAY) != 0;
|
|||
|
|
public bool IsRepeatTuesday => (Repeat & REPEAT_TUESDAY) != 0;
|
|||
|
|
public bool IsRepeatWednesday => (Repeat & REPEAT_WEDNESDAY) != 0;
|
|||
|
|
public bool IsRepeatThursday => (Repeat & REPEAT_THURSDAY) != 0;
|
|||
|
|
public bool IsRepeatFriday => (Repeat & REPEAT_FRIDAY) != 0;
|
|||
|
|
public bool IsRepeatSaturday => (Repeat & REPEAT_SATURDAY) != 0;
|
|||
|
|
public bool IsRepeatSunday => (Repeat & REPEAT_SUNDAY) != 0;
|
|||
|
|
|
|||
|
|
public string GetRepeatString()
|
|||
|
|
{
|
|||
|
|
if (Repeat == 0) return "不重复";
|
|||
|
|
var days = new System.Collections.Generic.List<string>();
|
|||
|
|
if (IsRepeatMonday) days.Add("周一");
|
|||
|
|
if (IsRepeatTuesday) days.Add("周二");
|
|||
|
|
if (IsRepeatWednesday) days.Add("周三");
|
|||
|
|
if (IsRepeatThursday) days.Add("周四");
|
|||
|
|
if (IsRepeatFriday) days.Add("周五");
|
|||
|
|
if (IsRepeatSaturday) days.Add("周六");
|
|||
|
|
if (IsRepeatSunday) days.Add("周日");
|
|||
|
|
return string.Join(",", days);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public override string ToString()
|
|||
|
|
{
|
|||
|
|
string modeStr = Mode switch
|
|||
|
|
{
|
|||
|
|
ScheduleTaskMode.Standby => "待机",
|
|||
|
|
ScheduleTaskMode.Scan => "扫描",
|
|||
|
|
ScheduleTaskMode.Sterilize => "消杀",
|
|||
|
|
_ => "未知"
|
|||
|
|
};
|
|||
|
|
return $"任务{TaskId}: {(Enabled ? "启用" : "禁用")} {StartHour:D2}:{StartMinute:D2}-{EndHour:D2}:{EndMinute:D2} {modeStr} [{GetRepeatString()}]";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 定时任务列表响应
|
|||
|
|
/// 读取返回格式:每个任务7字节(不包含TaskId)
|
|||
|
|
/// 开关(1) + 开始小时(1) + 开始分钟(1) + 结束小时(1) + 结束分钟(1) + 模式(1) + 重复(1)
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct ScheduleTaskListResponse
|
|||
|
|
{
|
|||
|
|
public byte Status; // 响应状态码
|
|||
|
|
public ScheduleTask[] Tasks; // 定时任务列表
|
|||
|
|
|
|||
|
|
public static ScheduleTaskListResponse FromBytes(byte status, byte[] data)
|
|||
|
|
{
|
|||
|
|
var response = new ScheduleTaskListResponse();
|
|||
|
|
response.Status = status;
|
|||
|
|
if (data == null || data.Length < 7)
|
|||
|
|
{
|
|||
|
|
response.Tasks = new ScheduleTask[0];
|
|||
|
|
return response;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
int taskCount = data.Length / 7; // 每个任务7字节(不包含TaskId)
|
|||
|
|
response.Tasks = new ScheduleTask[taskCount];
|
|||
|
|
for (int i = 0; i < taskCount; i++)
|
|||
|
|
{
|
|||
|
|
response.Tasks[i] = ScheduleTask.FromBytesRead(data, i * 7, (byte)i);
|
|||
|
|
}
|
|||
|
|
return response;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool IsSuccess => Status == 0x00;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 外设控制类结构体 (0x20-0x3F)
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// LCD休眠设置
|
|||
|
|
/// 命令 0x20:休眠开关(1) + 休眠时间(1)
|
|||
|
|
/// 休眠开关:0x00=关闭,0x01=开启
|
|||
|
|
/// 休眠时间:0x01=1分钟,0x05=5分钟,0x0A=10分钟,0x1E=30分钟
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct LCDSleepSetting
|
|||
|
|
{
|
|||
|
|
public bool Enable; // 休眠开关
|
|||
|
|
public byte SleepTime; // 休眠时间
|
|||
|
|
|
|||
|
|
public static LCDSleepSetting FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var setting = new LCDSleepSetting();
|
|||
|
|
if (data != null && data.Length >= 2)
|
|||
|
|
{
|
|||
|
|
setting.Enable = data[0] == 0x01;
|
|||
|
|
setting.SleepTime = data[1];
|
|||
|
|
}
|
|||
|
|
return setting;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
return new byte[] { Enable ? (byte)0x01 : (byte)0x00, SleepTime };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public string GetSleepTimeString()
|
|||
|
|
{
|
|||
|
|
return SleepTime switch
|
|||
|
|
{
|
|||
|
|
0x01 => "1分钟",
|
|||
|
|
0x05 => "5分钟",
|
|||
|
|
0x0A => "10分钟",
|
|||
|
|
0x1E => "30分钟",
|
|||
|
|
_ => "未知"
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// LCD亮度设置
|
|||
|
|
/// 命令 0x21:自适应开关(1) + 亮度值(1)
|
|||
|
|
/// 自适应开关:0x00=关闭,0x01=开启
|
|||
|
|
/// 亮度值:10-100%(仅在自适应关闭时有效)
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct LCDBrightnessSetting
|
|||
|
|
{
|
|||
|
|
public bool AutoBrightness; // 自适应开关
|
|||
|
|
public byte Brightness; // 亮度值(10-100%)
|
|||
|
|
|
|||
|
|
public static LCDBrightnessSetting FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var setting = new LCDBrightnessSetting();
|
|||
|
|
if (data != null && data.Length >= 2)
|
|||
|
|
{
|
|||
|
|
setting.AutoBrightness = data[0] == 0x01;
|
|||
|
|
setting.Brightness = data[1];
|
|||
|
|
}
|
|||
|
|
return setting;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
return new byte[] { AutoBrightness ? (byte)0x01 : (byte)0x00, Brightness };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// RGB指示灯效果模式
|
|||
|
|
/// </summary>
|
|||
|
|
public enum RGBEffectMode : byte
|
|||
|
|
{
|
|||
|
|
Off = 0x00, // 关闭
|
|||
|
|
Solid = 0x01, // 常亮
|
|||
|
|
Blinking = 0x02, // 闪烁
|
|||
|
|
Warning = 0x03 // 警示
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// RGB指示灯控制
|
|||
|
|
/// 命令 0x22:开关(1) + 红色(1) + 绿色(1) + 蓝色(1) + 效果模式(1)
|
|||
|
|
/// 开关:0x00=关闭,0x01=开启
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct RGBControl
|
|||
|
|
{
|
|||
|
|
public bool Enable; // 开关
|
|||
|
|
public byte Red; // 红色值(0-255)
|
|||
|
|
public byte Green; // 绿色值(0-255)
|
|||
|
|
public byte Blue; // 蓝色值(0-255)
|
|||
|
|
public RGBEffectMode Effect; // 效果模式
|
|||
|
|
|
|||
|
|
public static RGBControl FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var control = new RGBControl();
|
|||
|
|
if (data == null || data.Length == 0)
|
|||
|
|
return control;
|
|||
|
|
|
|||
|
|
// 兼容不同格式的数据返回
|
|||
|
|
// 完整格式: 开关(1) + R(1) + G(1) + B(1) + 效果(1) = 5字节
|
|||
|
|
// 简化格式: R(1) + G(1) + B(1) = 3字节 (没有状态码前缀的情况)
|
|||
|
|
// 带状态码: 状态码(1) + [开关 +] R + G + B [+ 效果]
|
|||
|
|
|
|||
|
|
int offset = 0;
|
|||
|
|
|
|||
|
|
// 如果数据长度大于3且第一个字节是0或1,可能是开关字节
|
|||
|
|
if (data.Length >= 5)
|
|||
|
|
{
|
|||
|
|
// 完整格式: 开关 + R + G + B + 效果
|
|||
|
|
control.Enable = data[0] == 0x01;
|
|||
|
|
control.Red = data[1];
|
|||
|
|
control.Green = data[2];
|
|||
|
|
control.Blue = data[3];
|
|||
|
|
control.Effect = (RGBEffectMode)data[4];
|
|||
|
|
}
|
|||
|
|
else if (data.Length >= 4)
|
|||
|
|
{
|
|||
|
|
// 可能是: 开关 + R + G + B (无效果模式)
|
|||
|
|
control.Enable = data[0] == 0x01;
|
|||
|
|
control.Red = data[1];
|
|||
|
|
control.Green = data[2];
|
|||
|
|
control.Blue = data[3];
|
|||
|
|
}
|
|||
|
|
else if (data.Length >= 3)
|
|||
|
|
{
|
|||
|
|
// 简化格式: 只有 R + G + B
|
|||
|
|
control.Enable = true; // 默认开启
|
|||
|
|
control.Red = data[0];
|
|||
|
|
control.Green = data[1];
|
|||
|
|
control.Blue = data[2];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return control;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
return new byte[]
|
|||
|
|
{
|
|||
|
|
Enable ? (byte)0x01 : (byte)0x00,
|
|||
|
|
Red,
|
|||
|
|
Green,
|
|||
|
|
Blue,
|
|||
|
|
(byte)Effect
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// WIFI控制
|
|||
|
|
/// 命令 0x23:
|
|||
|
|
/// 读操作:返回65字节(开关1字节 + SSID 32字节 + 密码32字节)
|
|||
|
|
/// 写操作:可变长度,至少1字节(开关),可选包含SSID和密码
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct WIFIControl
|
|||
|
|
{
|
|||
|
|
public bool Enable; // 开关
|
|||
|
|
public string SSID; // SSID
|
|||
|
|
public string Password; // 密码
|
|||
|
|
|
|||
|
|
public static WIFIControl FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var control = new WIFIControl();
|
|||
|
|
if (data != null && data.Length >= 1)
|
|||
|
|
{
|
|||
|
|
control.Enable = data[0] == 0x01;
|
|||
|
|
if (data.Length >= 33)
|
|||
|
|
{
|
|||
|
|
// SSID (32字节)
|
|||
|
|
byte[] ssidBytes = new byte[32];
|
|||
|
|
Buffer.BlockCopy(data, 1, ssidBytes, 0, 32);
|
|||
|
|
int ssidLen = 0;
|
|||
|
|
while (ssidLen < 32 && ssidBytes[ssidLen] != 0)
|
|||
|
|
ssidLen++;
|
|||
|
|
control.SSID = System.Text.Encoding.ASCII.GetString(ssidBytes, 0, ssidLen);
|
|||
|
|
}
|
|||
|
|
if (data.Length >= 65)
|
|||
|
|
{
|
|||
|
|
// 密码 (32字节)
|
|||
|
|
byte[] passwordBytes = new byte[32];
|
|||
|
|
Buffer.BlockCopy(data, 33, passwordBytes, 0, 32);
|
|||
|
|
int passwordLen = 0;
|
|||
|
|
while (passwordLen < 32 && passwordBytes[passwordLen] != 0)
|
|||
|
|
passwordLen++;
|
|||
|
|
control.Password = System.Text.Encoding.ASCII.GetString(passwordBytes, 0, passwordLen);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return control;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
// 计算长度:至少1字节(开关)
|
|||
|
|
int length = 1;
|
|||
|
|
if (!string.IsNullOrEmpty(SSID))
|
|||
|
|
length += SSID.Length + 1; // SSID + 结束符
|
|||
|
|
if (!string.IsNullOrEmpty(Password))
|
|||
|
|
length += Password.Length + 1; // 密码 + 结束符
|
|||
|
|
|
|||
|
|
byte[] bytes = new byte[length];
|
|||
|
|
bytes[0] = Enable ? (byte)0x01 : (byte)0x00;
|
|||
|
|
int offset = 1;
|
|||
|
|
|
|||
|
|
if (!string.IsNullOrEmpty(SSID))
|
|||
|
|
{
|
|||
|
|
byte[] ssidBytes = System.Text.Encoding.ASCII.GetBytes(SSID);
|
|||
|
|
int ssidLen = System.Math.Min(ssidBytes.Length, 32);
|
|||
|
|
Buffer.BlockCopy(ssidBytes, 0, bytes, offset, ssidLen);
|
|||
|
|
offset += ssidLen;
|
|||
|
|
bytes[offset++] = 0; // 结束符
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!string.IsNullOrEmpty(Password))
|
|||
|
|
{
|
|||
|
|
byte[] passwordBytes = System.Text.Encoding.ASCII.GetBytes(Password);
|
|||
|
|
int passwordLen = System.Math.Min(passwordBytes.Length, 32);
|
|||
|
|
Buffer.BlockCopy(passwordBytes, 0, bytes, offset, passwordLen);
|
|||
|
|
offset += passwordLen;
|
|||
|
|
bytes[offset++] = 0; // 结束符
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return bytes;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// BLE控制
|
|||
|
|
/// 命令 0x24:设置BLE广播名称
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct BLEControl
|
|||
|
|
{
|
|||
|
|
public string DeviceName; // 设备名称
|
|||
|
|
|
|||
|
|
public static BLEControl FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var control = new BLEControl();
|
|||
|
|
if (data != null && data.Length > 0)
|
|||
|
|
{
|
|||
|
|
// 去除末尾的0字节
|
|||
|
|
int nameLen = 0;
|
|||
|
|
while (nameLen < data.Length && data[nameLen] != 0)
|
|||
|
|
nameLen++;
|
|||
|
|
control.DeviceName = System.Text.Encoding.ASCII.GetString(data, 0, nameLen);
|
|||
|
|
}
|
|||
|
|
return control;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
byte[] nameBytes = System.Text.Encoding.ASCII.GetBytes(DeviceName ?? "");
|
|||
|
|
byte[] bytes = new byte[nameBytes.Length + 1]; // 名称 + 结束符
|
|||
|
|
Buffer.BlockCopy(nameBytes, 0, bytes, 0, nameBytes.Length);
|
|||
|
|
bytes[nameBytes.Length] = 0; // 结束符
|
|||
|
|
return bytes;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 音效类型
|
|||
|
|
/// </summary>
|
|||
|
|
public enum SoundEffectType : byte
|
|||
|
|
{
|
|||
|
|
Hello = 0x00, // Hello音效
|
|||
|
|
Goodbye = 0x01, // Goodbye音效
|
|||
|
|
Test = 0x02, // Test音效
|
|||
|
|
BIU = 0x03, // BIU音效
|
|||
|
|
Main = 0x04 // Main音效
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 多媒体控制
|
|||
|
|
/// 命令 0x25:
|
|||
|
|
/// 读操作:返回5字节(视频录制开关1 + 录制时长1 + 音效开关1 + 音效类型1 + 音量1)
|
|||
|
|
/// 写操作:可变长度,至少1字节(视频录制开关)
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct MultimediaControl
|
|||
|
|
{
|
|||
|
|
public bool VideoRecordEnable; // 视频录制开关
|
|||
|
|
public byte RecordDuration; // 录制时长(0x03=3秒, 0x05=5秒, 0x0A=10秒)
|
|||
|
|
public bool SoundEnable; // 音效开关
|
|||
|
|
public SoundEffectType SoundType; // 音效类型
|
|||
|
|
public byte Volume; // 音量(0-15)
|
|||
|
|
|
|||
|
|
public static MultimediaControl FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var control = new MultimediaControl();
|
|||
|
|
if (data != null && data.Length >= 1)
|
|||
|
|
{
|
|||
|
|
control.VideoRecordEnable = data[0] == 0x01;
|
|||
|
|
if (data.Length >= 2)
|
|||
|
|
control.RecordDuration = data[1];
|
|||
|
|
if (data.Length >= 3)
|
|||
|
|
control.SoundEnable = data[2] == 0x01;
|
|||
|
|
if (data.Length >= 4)
|
|||
|
|
control.SoundType = (SoundEffectType)data[3];
|
|||
|
|
if (data.Length >= 5)
|
|||
|
|
control.Volume = data[4];
|
|||
|
|
}
|
|||
|
|
return control;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
// 可变长度,至少1字节
|
|||
|
|
byte[] bytes = new byte[5]; // 完整数据
|
|||
|
|
bytes[0] = VideoRecordEnable ? (byte)0x01 : (byte)0x00;
|
|||
|
|
bytes[1] = RecordDuration;
|
|||
|
|
bytes[2] = SoundEnable ? (byte)0x01 : (byte)0x00;
|
|||
|
|
bytes[3] = (byte)SoundType;
|
|||
|
|
bytes[4] = Volume;
|
|||
|
|
return bytes;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public string GetDurationString()
|
|||
|
|
{
|
|||
|
|
return RecordDuration switch
|
|||
|
|
{
|
|||
|
|
0x03 => "3秒",
|
|||
|
|
0x05 => "5秒",
|
|||
|
|
0x0A => "10秒",
|
|||
|
|
_ => "未知"
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public string GetSoundTypeString()
|
|||
|
|
{
|
|||
|
|
return SoundType switch
|
|||
|
|
{
|
|||
|
|
SoundEffectType.Hello => "Hello",
|
|||
|
|
SoundEffectType.Goodbye => "Goodbye",
|
|||
|
|
SoundEffectType.Test => "Test",
|
|||
|
|
SoundEffectType.BIU => "BIU",
|
|||
|
|
SoundEffectType.Main => "Main",
|
|||
|
|
_ => "未知"
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 补光类型
|
|||
|
|
/// </summary>
|
|||
|
|
public enum FillLightType : byte
|
|||
|
|
{
|
|||
|
|
Infrared = 0x00, // 红外光
|
|||
|
|
White = 0x01 // 白光
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 光照强度
|
|||
|
|
/// </summary>
|
|||
|
|
public enum LightIntensity : byte
|
|||
|
|
{
|
|||
|
|
Low = 0x00, // 低
|
|||
|
|
Medium = 0x01, // 中
|
|||
|
|
High = 0x02 // 高
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 补光灯控制
|
|||
|
|
/// 命令 0x26:开关(1) + 补光类型(1) + 光照强度(1)
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct FillLightControl
|
|||
|
|
{
|
|||
|
|
public bool Enable; // 开关
|
|||
|
|
public FillLightType LightType; // 补光类型
|
|||
|
|
public LightIntensity Intensity; // 光照强度
|
|||
|
|
|
|||
|
|
public static FillLightControl FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var control = new FillLightControl();
|
|||
|
|
if (data != null && data.Length >= 3)
|
|||
|
|
{
|
|||
|
|
control.Enable = data[0] == 0x01;
|
|||
|
|
control.LightType = (FillLightType)data[1];
|
|||
|
|
control.Intensity = (LightIntensity)data[2];
|
|||
|
|
}
|
|||
|
|
return control;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
return new byte[]
|
|||
|
|
{
|
|||
|
|
Enable ? (byte)0x01 : (byte)0x00,
|
|||
|
|
(byte)LightType,
|
|||
|
|
(byte)Intensity
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public string GetLightTypeString()
|
|||
|
|
{
|
|||
|
|
return LightType == FillLightType.Infrared ? "红外光" : "白光";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public string GetIntensityString()
|
|||
|
|
{
|
|||
|
|
return Intensity switch
|
|||
|
|
{
|
|||
|
|
LightIntensity.Low => "低",
|
|||
|
|
LightIntensity.Medium => "中",
|
|||
|
|
LightIntensity.High => "高",
|
|||
|
|
_ => "未知"
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 补光灯连接状态
|
|||
|
|
/// 命令 0x27:返回连接状态(1字节)
|
|||
|
|
/// 0=未连接, 1=已连接
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct FillLightConnectionStatus
|
|||
|
|
{
|
|||
|
|
public bool IsConnected; // 连接状态
|
|||
|
|
|
|||
|
|
public static FillLightConnectionStatus FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var status = new FillLightConnectionStatus();
|
|||
|
|
if (data != null && data.Length >= 1)
|
|||
|
|
{
|
|||
|
|
status.IsConnected = data[0] == 0x01;
|
|||
|
|
}
|
|||
|
|
return status;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 可见光激光器控制
|
|||
|
|
/// 命令 0x28:开关(1)
|
|||
|
|
/// 开关:0x00=关闭,0x01=开启
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct VisibleLaserControl
|
|||
|
|
{
|
|||
|
|
public bool Enable; // 开关
|
|||
|
|
|
|||
|
|
public static VisibleLaserControl FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var control = new VisibleLaserControl();
|
|||
|
|
if (data != null && data.Length >= 1)
|
|||
|
|
{
|
|||
|
|
control.Enable = data[0] == 0x01;
|
|||
|
|
}
|
|||
|
|
return control;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
return new byte[] { Enable ? (byte)0x01 : (byte)0x00 };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 害虫消灭控制类结构体 (0x40-0x4F)
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 角度控制
|
|||
|
|
/// 命令 0x40:角度范围(2字节)
|
|||
|
|
/// 数据格式:2字节,小端序,单位:0.1度,范围:1-900(对应0.1度-90度)
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct AngleControl
|
|||
|
|
{
|
|||
|
|
public ushort AngleRange; // 角度范围(单位:0.1度,范围1-900)
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 实际角度值(度)
|
|||
|
|
/// </summary>
|
|||
|
|
public float ActualAngle => AngleRange * 0.1f;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 从字节数组解析(小端序)
|
|||
|
|
/// </summary>
|
|||
|
|
public static AngleControl FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var control = new AngleControl();
|
|||
|
|
if (data != null && data.Length >= 2)
|
|||
|
|
{
|
|||
|
|
// 小端序:低字节在前,高字节在后
|
|||
|
|
control.AngleRange = (ushort)(data[0] | (data[1] << 8));
|
|||
|
|
}
|
|||
|
|
return control;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 序列化为字节数组(小端序)
|
|||
|
|
/// </summary>
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
return new byte[]
|
|||
|
|
{
|
|||
|
|
(byte)(AngleRange & 0xFF), // 低字节
|
|||
|
|
(byte)((AngleRange >> 8) & 0xFF) // 高字节
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 设置实际角度值
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="angle">角度值(度),范围0.1-90.0</param>
|
|||
|
|
public void SetAngle(float angle)
|
|||
|
|
{
|
|||
|
|
AngleRange = (ushort)Mathf.Round(angle * 10);
|
|||
|
|
// 限制范围
|
|||
|
|
if (AngleRange < 1) AngleRange = 1;
|
|||
|
|
if (AngleRange > 900) AngleRange = 900;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public override string ToString()
|
|||
|
|
{
|
|||
|
|
return $"{ActualAngle:F1}度 ({AngleRange})";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 距离控制
|
|||
|
|
/// 命令 0x41:检测距离(2字节) + 瞄准距离(2字节)
|
|||
|
|
/// 数据格式:4字节,小端序
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct DistanceControl
|
|||
|
|
{
|
|||
|
|
public ushort DetectionDistance; // 检测距离(单位:米)
|
|||
|
|
public ushort AimDistance; // 瞄准距离(单位:米)
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 从字节数组解析(小端序)
|
|||
|
|
/// </summary>
|
|||
|
|
public static DistanceControl FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var control = new DistanceControl();
|
|||
|
|
if (data != null && data.Length >= 4)
|
|||
|
|
{
|
|||
|
|
// 小端序解析
|
|||
|
|
control.DetectionDistance = (ushort)(data[0] | (data[1] << 8));
|
|||
|
|
control.AimDistance = (ushort)(data[2] | (data[3] << 8));
|
|||
|
|
}
|
|||
|
|
return control;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 序列化为字节数组(小端序)
|
|||
|
|
/// </summary>
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
return new byte[]
|
|||
|
|
{
|
|||
|
|
(byte)(DetectionDistance & 0xFF),
|
|||
|
|
(byte)((DetectionDistance >> 8) & 0xFF),
|
|||
|
|
(byte)(AimDistance & 0xFF),
|
|||
|
|
(byte)((AimDistance >> 8) & 0xFF)
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public override string ToString()
|
|||
|
|
{
|
|||
|
|
return $"检测距离={DetectionDistance}米, 瞄准距离={AimDistance}米";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 安全设置类结构体 (0x50-0x5F)
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 灵敏度等级枚举
|
|||
|
|
/// </summary>
|
|||
|
|
public enum SensitivityLevel : byte
|
|||
|
|
{
|
|||
|
|
Low = 0x00, // 低灵敏度
|
|||
|
|
Medium = 0x01, // 中灵敏度
|
|||
|
|
High = 0x02 // 高灵敏度
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 毫米波雷达设置
|
|||
|
|
/// 命令 0x50:开关(1) + 灵敏度(1) + 安全距离(2)
|
|||
|
|
/// 数据格式:4字节,小端序
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct MillimeterWaveSetting
|
|||
|
|
{
|
|||
|
|
public bool Enable; // 开关
|
|||
|
|
public SensitivityLevel Sensitivity; // 灵敏度
|
|||
|
|
public ushort SafeDistance; // 安全距离(单位:厘米)
|
|||
|
|
|
|||
|
|
public static MillimeterWaveSetting FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var setting = new MillimeterWaveSetting();
|
|||
|
|
if (data != null && data.Length >= 4)
|
|||
|
|
{
|
|||
|
|
setting.Enable = data[0] == 0x01;
|
|||
|
|
setting.Sensitivity = (SensitivityLevel)data[1];
|
|||
|
|
setting.SafeDistance = (ushort)(data[2] | (data[3] << 8));
|
|||
|
|
}
|
|||
|
|
return setting;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
return new byte[]
|
|||
|
|
{
|
|||
|
|
(byte)(Enable ? 0x01 : 0x00),
|
|||
|
|
(byte)Sensitivity,
|
|||
|
|
(byte)(SafeDistance & 0xFF),
|
|||
|
|
(byte)((SafeDistance >> 8) & 0xFF)
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public override string ToString()
|
|||
|
|
{
|
|||
|
|
string sensStr = Sensitivity switch
|
|||
|
|
{
|
|||
|
|
SensitivityLevel.Low => "低",
|
|||
|
|
SensitivityLevel.Medium => "中",
|
|||
|
|
SensitivityLevel.High => "高",
|
|||
|
|
_ => "未知"
|
|||
|
|
};
|
|||
|
|
return $"开关={(Enable ? "开启" : "关闭")}, 灵敏度={sensStr}, 安全距离={SafeDistance}cm";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 视觉检测设置
|
|||
|
|
/// 命令 0x51:开关(1) + 灵敏度(1)
|
|||
|
|
/// 数据格式:2字节
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct VisualDetectionSetting
|
|||
|
|
{
|
|||
|
|
public bool Enable; // 开关
|
|||
|
|
public SensitivityLevel Sensitivity; // 灵敏度
|
|||
|
|
|
|||
|
|
public static VisualDetectionSetting FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var setting = new VisualDetectionSetting();
|
|||
|
|
if (data != null && data.Length >= 2)
|
|||
|
|
{
|
|||
|
|
setting.Enable = data[0] == 0x01;
|
|||
|
|
setting.Sensitivity = (SensitivityLevel)data[1];
|
|||
|
|
}
|
|||
|
|
return setting;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
return new byte[]
|
|||
|
|
{
|
|||
|
|
(byte)(Enable ? 0x01 : 0x00),
|
|||
|
|
(byte)Sensitivity
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public override string ToString()
|
|||
|
|
{
|
|||
|
|
string sensStr = Sensitivity switch
|
|||
|
|
{
|
|||
|
|
SensitivityLevel.Low => "低",
|
|||
|
|
SensitivityLevel.Medium => "中",
|
|||
|
|
SensitivityLevel.High => "高",
|
|||
|
|
_ => "未知"
|
|||
|
|
};
|
|||
|
|
return $"开关={(Enable ? "开启" : "关闭")}, 灵敏度={sensStr}";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 加速度传感器设置
|
|||
|
|
/// 命令 0x52:开关(1) + 灵敏度(1) + 振动阈值(1)
|
|||
|
|
/// 数据格式:3字节
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct AccelerometerSetting
|
|||
|
|
{
|
|||
|
|
public bool Enable; // 开关
|
|||
|
|
public SensitivityLevel Sensitivity; // 灵敏度
|
|||
|
|
public byte VibrationThreshold; // 振动阈值 (1-255)
|
|||
|
|
|
|||
|
|
public static AccelerometerSetting FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var setting = new AccelerometerSetting();
|
|||
|
|
if (data != null && data.Length >= 3)
|
|||
|
|
{
|
|||
|
|
setting.Enable = data[0] == 0x01;
|
|||
|
|
setting.Sensitivity = (SensitivityLevel)data[1];
|
|||
|
|
setting.VibrationThreshold = data[2];
|
|||
|
|
}
|
|||
|
|
return setting;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
return new byte[]
|
|||
|
|
{
|
|||
|
|
(byte)(Enable ? 0x01 : 0x00),
|
|||
|
|
(byte)Sensitivity,
|
|||
|
|
VibrationThreshold
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public override string ToString()
|
|||
|
|
{
|
|||
|
|
string sensStr = Sensitivity switch
|
|||
|
|
{
|
|||
|
|
SensitivityLevel.Low => "低",
|
|||
|
|
SensitivityLevel.Medium => "中",
|
|||
|
|
SensitivityLevel.High => "高",
|
|||
|
|
_ => "未知"
|
|||
|
|
};
|
|||
|
|
return $"开关={(Enable ? "开启" : "关闭")}, 灵敏度={sensStr}, 振动阈值={VibrationThreshold}";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 加速度传感器数据
|
|||
|
|
/// 命令 0x53:X轴(2) + Y轴(2) + Z轴(2)
|
|||
|
|
/// 数据格式:6字节,小端序,有符号整数,单位:mg
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct AccelerometerData
|
|||
|
|
{
|
|||
|
|
public short X; // X轴加速度 (mg)
|
|||
|
|
public short Y; // Y轴加速度 (mg)
|
|||
|
|
public short Z; // Z轴加速度 (mg)
|
|||
|
|
|
|||
|
|
public static AccelerometerData FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var accel = new AccelerometerData();
|
|||
|
|
if (data != null && data.Length >= 6)
|
|||
|
|
{
|
|||
|
|
accel.X = (short)(data[0] | (data[1] << 8));
|
|||
|
|
accel.Y = (short)(data[2] | (data[3] << 8));
|
|||
|
|
accel.Z = (short)(data[4] | (data[5] << 8));
|
|||
|
|
}
|
|||
|
|
return accel;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
return new byte[]
|
|||
|
|
{
|
|||
|
|
(byte)(X & 0xFF), (byte)((X >> 8) & 0xFF),
|
|||
|
|
(byte)(Y & 0xFF), (byte)((Y >> 8) & 0xFF),
|
|||
|
|
(byte)(Z & 0xFF), (byte)((Z >> 8) & 0xFF)
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public override string ToString()
|
|||
|
|
{
|
|||
|
|
return $"X={X}mg, Y={Y}mg, Z={Z}mg";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 温度监控设置
|
|||
|
|
/// 命令 0x55:告警阈值(1) + 停止阈值(1) + 开关(1)
|
|||
|
|
/// 数据格式:3字节
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct TemperatureMonitorSetting
|
|||
|
|
{
|
|||
|
|
public byte AlarmThreshold; // 告警阈值 (摄氏度)
|
|||
|
|
public byte StopThreshold; // 停止阈值 (摄氏度)
|
|||
|
|
public bool Enable; // 开关
|
|||
|
|
|
|||
|
|
public static TemperatureMonitorSetting FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var setting = new TemperatureMonitorSetting();
|
|||
|
|
if (data != null && data.Length >= 3)
|
|||
|
|
{
|
|||
|
|
setting.AlarmThreshold = data[0];
|
|||
|
|
setting.StopThreshold = data[1];
|
|||
|
|
setting.Enable = data[2] == 0x01;
|
|||
|
|
}
|
|||
|
|
return setting;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
return new byte[]
|
|||
|
|
{
|
|||
|
|
AlarmThreshold,
|
|||
|
|
StopThreshold,
|
|||
|
|
(byte)(Enable ? 0x01 : 0x00)
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public override string ToString()
|
|||
|
|
{
|
|||
|
|
return $"开关={(Enable ? "开启" : "关闭")}, 告警阈值={AlarmThreshold}°C, 停止阈值={StopThreshold}°C";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 环境变化检测和试射测试
|
|||
|
|
/// 命令 0x56:读操作返回1字节环境变化状态,写操作触发激光试射测试
|
|||
|
|
/// 数据格式:1字节
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct EnvironmentChangeTest
|
|||
|
|
{
|
|||
|
|
public bool EnvironmentChanged; // 环境变化状态(读操作返回)
|
|||
|
|
public bool TriggerTest; // 触发测试(写操作发送0x01)
|
|||
|
|
|
|||
|
|
public static EnvironmentChangeTest FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var test = new EnvironmentChangeTest();
|
|||
|
|
if (data != null && data.Length >= 1)
|
|||
|
|
{
|
|||
|
|
test.EnvironmentChanged = data[0] == 0x01;
|
|||
|
|
}
|
|||
|
|
return test;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
return new byte[] { (byte)(TriggerTest ? 0x01 : 0x00) };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public override string ToString()
|
|||
|
|
{
|
|||
|
|
return $"环境变化={(EnvironmentChanged ? "发生变化" : "没有变化")}";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region 状态查询类结构体 (0xA0-0xBF)
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 工作模式枚举
|
|||
|
|
/// </summary>
|
|||
|
|
public enum WorkMode : byte
|
|||
|
|
{
|
|||
|
|
Standby = 0x00, // 待机模式
|
|||
|
|
Scan = 0x01, // 扫描模式
|
|||
|
|
Eliminate = 0x02 // 消杀模式
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 工作模式设置
|
|||
|
|
/// 命令 0xA0:1字节工作模式
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct WorkModeSetting
|
|||
|
|
{
|
|||
|
|
public WorkMode Mode;
|
|||
|
|
|
|||
|
|
public static WorkModeSetting FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var setting = new WorkModeSetting();
|
|||
|
|
if (data != null && data.Length >= 1)
|
|||
|
|
{
|
|||
|
|
setting.Mode = (WorkMode)data[0];
|
|||
|
|
}
|
|||
|
|
return setting;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
return new byte[] { (byte)Mode };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public override string ToString()
|
|||
|
|
{
|
|||
|
|
string modeStr = Mode switch
|
|||
|
|
{
|
|||
|
|
WorkMode.Standby => "待机",
|
|||
|
|
WorkMode.Scan => "扫描",
|
|||
|
|
WorkMode.Eliminate => "消杀",
|
|||
|
|
_ => "未知"
|
|||
|
|
};
|
|||
|
|
return $"工作模式={modeStr}";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 硬件状态
|
|||
|
|
/// 命令 0xA1:返回7字节硬件错误状态
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct HardwareStatus
|
|||
|
|
{
|
|||
|
|
public byte TemperatureError; // 温度错误 (0=无, 1=过高, 2=过低)
|
|||
|
|
public byte MotorError; // 电机错误 (0=无, 1=温度, 2=反馈)
|
|||
|
|
public byte LaserError; // 激光器错误 (0=无, 1=温度, 2=电压低, 3=电压高)
|
|||
|
|
public byte VisionError; // 视觉错误 (0=无, 1=通信)
|
|||
|
|
public byte AccelerometerError; // 加速度错误 (0=无, 1=通信)
|
|||
|
|
public byte MillimeterWaveError; // 毫米波雷达错误 (0=无, 1=通信)
|
|||
|
|
public byte LidarError; // 激光雷达错误 (0=无, 1=通信)
|
|||
|
|
|
|||
|
|
public static HardwareStatus FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var status = new HardwareStatus();
|
|||
|
|
if (data != null && data.Length >= 7)
|
|||
|
|
{
|
|||
|
|
status.TemperatureError = data[0];
|
|||
|
|
status.MotorError = data[1];
|
|||
|
|
status.LaserError = data[2];
|
|||
|
|
status.VisionError = data[3];
|
|||
|
|
status.AccelerometerError = data[4];
|
|||
|
|
status.MillimeterWaveError = data[5];
|
|||
|
|
status.LidarError = data[6];
|
|||
|
|
}
|
|||
|
|
return status;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public bool HasError => TemperatureError != 0 || MotorError != 0 || LaserError != 0 ||
|
|||
|
|
VisionError != 0 || AccelerometerError != 0 || MillimeterWaveError != 0 || LidarError != 0;
|
|||
|
|
|
|||
|
|
public override string ToString()
|
|||
|
|
{
|
|||
|
|
if (!HasError) return "所有硬件正常";
|
|||
|
|
var errors = new System.Collections.Generic.List<string>();
|
|||
|
|
if (TemperatureError != 0) errors.Add($"温度错误({TemperatureError})");
|
|||
|
|
if (MotorError != 0) errors.Add($"电机错误({MotorError})");
|
|||
|
|
if (LaserError != 0) errors.Add($"激光器错误({LaserError})");
|
|||
|
|
if (VisionError != 0) errors.Add($"视觉错误({VisionError})");
|
|||
|
|
if (AccelerometerError != 0) errors.Add($"加速度错误({AccelerometerError})");
|
|||
|
|
if (MillimeterWaveError != 0) errors.Add($"毫米波错误({MillimeterWaveError})");
|
|||
|
|
if (LidarError != 0) errors.Add($"激光雷达错误({LidarError})");
|
|||
|
|
return string.Join(", ", errors);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 设备信息
|
|||
|
|
/// 命令 0xA2:返回76字节设备信息
|
|||
|
|
/// 数据格式(76字节,小端序):
|
|||
|
|
/// 字节0-15:设备序列号(16字节,ASCII编码)
|
|||
|
|
/// 字节16-31:设备型号(16字节,ASCII编码)
|
|||
|
|
/// 字节32-47:设备ID(16字节,ASCII编码)
|
|||
|
|
/// 字节48-53:BLE MAC地址(6字节)
|
|||
|
|
/// 字节54-59:WiFi MAC地址(6字节)
|
|||
|
|
/// 字节60-63:固件版本(uint32_t,4字节小端序)
|
|||
|
|
/// 字节64-67:硬件版本(uint32_t,4字节小端序)
|
|||
|
|
/// 字节68-71:OTA版本(uint32_t,4字节小端序)
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct DeviceInfo
|
|||
|
|
{
|
|||
|
|
public byte[] SerialNumber; // 设备序列号 (16字节)
|
|||
|
|
public byte[] DeviceModel; // 设备型号 (16字节)
|
|||
|
|
public byte[] DeviceId; // 设备ID (16字节)
|
|||
|
|
public byte[] BleMacAddress; // BLE MAC地址 (6字节)
|
|||
|
|
public byte[] WifiMacAddress; // WiFi MAC地址 (6字节)
|
|||
|
|
public uint FirmwareVersion; // 固件版本 (4字节)
|
|||
|
|
public uint HardwareVersion; // 硬件版本 (4字节)
|
|||
|
|
public uint OtaVersion; // OTA版本 (4字节)
|
|||
|
|
|
|||
|
|
public static DeviceInfo FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var info = new DeviceInfo();
|
|||
|
|
// 始终初始化数组,避免null引用
|
|||
|
|
info.SerialNumber = new byte[16];
|
|||
|
|
info.DeviceModel = new byte[16];
|
|||
|
|
info.DeviceId = new byte[16];
|
|||
|
|
info.BleMacAddress = new byte[6];
|
|||
|
|
info.WifiMacAddress = new byte[6];
|
|||
|
|
|
|||
|
|
if (data != null)
|
|||
|
|
{
|
|||
|
|
// 字节0-15:设备序列号
|
|||
|
|
if (data.Length >= 16)
|
|||
|
|
System.Buffer.BlockCopy(data, 0, info.SerialNumber, 0, 16);
|
|||
|
|
// 字节16-31:设备型号
|
|||
|
|
if (data.Length >= 32)
|
|||
|
|
System.Buffer.BlockCopy(data, 16, info.DeviceModel, 0, 16);
|
|||
|
|
// 字节32-47:设备ID
|
|||
|
|
if (data.Length >= 48)
|
|||
|
|
System.Buffer.BlockCopy(data, 32, info.DeviceId, 0, 16);
|
|||
|
|
// 字节48-53:BLE MAC地址
|
|||
|
|
if (data.Length >= 54)
|
|||
|
|
System.Buffer.BlockCopy(data, 48, info.BleMacAddress, 0, 6);
|
|||
|
|
// 字节54-59:WiFi MAC地址
|
|||
|
|
if (data.Length >= 60)
|
|||
|
|
System.Buffer.BlockCopy(data, 54, info.WifiMacAddress, 0, 6);
|
|||
|
|
// 字节60-63:固件版本(4字节小端序,每位代表一个数字,如 1.0.2.3)
|
|||
|
|
if (data.Length >= 64)
|
|||
|
|
info.FirmwareVersion = BitConverter.ToUInt32(data, 60);
|
|||
|
|
// 字节64-67:硬件版本(4字节小端序,每位代表一个数字,如 0.2.0.0)
|
|||
|
|
if (data.Length >= 68)
|
|||
|
|
info.HardwareVersion = BitConverter.ToUInt32(data, 64);
|
|||
|
|
// 字节68-71:OTA版本(4字节小端序,每位代表一个数字)
|
|||
|
|
if (data.Length >= 72)
|
|||
|
|
info.OtaVersion = BitConverter.ToUInt32(data, 68);
|
|||
|
|
}
|
|||
|
|
return info;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public string GetSerialNumberString() => SerialNumber != null ? System.Text.Encoding.ASCII.GetString(SerialNumber).TrimEnd('\0') : "N/A";
|
|||
|
|
public string GetDeviceModelString() => DeviceModel != null ? System.Text.Encoding.ASCII.GetString(DeviceModel).TrimEnd('\0') : "N/A";
|
|||
|
|
public string GetDeviceIdString() => DeviceId != null ? System.Text.Encoding.ASCII.GetString(DeviceId).TrimEnd('\0') : "N/A";
|
|||
|
|
public string GetBleMacAddressString() => BleMacAddress != null ? BitConverter.ToString(BleMacAddress).Replace("-", ":") : "N/A";
|
|||
|
|
public string GetWifiMacAddressString() => WifiMacAddress != null ? BitConverter.ToString(WifiMacAddress).Replace("-", ":") : "N/A";
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 将版本号转换为 x.x.x.x 格式字符串
|
|||
|
|
/// </summary>
|
|||
|
|
private string GetVersionString(uint version)
|
|||
|
|
{
|
|||
|
|
byte v1 = (byte)((version >> 24) & 0xFF);
|
|||
|
|
byte v2 = (byte)((version >> 16) & 0xFF);
|
|||
|
|
byte v3 = (byte)((version >> 8) & 0xFF);
|
|||
|
|
byte v4 = (byte)(version & 0xFF);
|
|||
|
|
return $"{v1}.{v2}.{v3}.{v4}";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public string GetFirmwareVersionString() => GetVersionString(FirmwareVersion);
|
|||
|
|
public string GetHardwareVersionString() => GetVersionString(HardwareVersion);
|
|||
|
|
public string GetOtaVersionString() => GetVersionString(OtaVersion);
|
|||
|
|
|
|||
|
|
public override string ToString()
|
|||
|
|
{
|
|||
|
|
return $"序列号={GetSerialNumberString()}, 型号={GetDeviceModelString()}, ID={GetDeviceIdString()}, BLE_MAC={GetBleMacAddressString()}, WiFi_MAC={GetWifiMacAddressString()}, 固件={GetFirmwareVersionString()}, 硬件={GetHardwareVersionString()}, OTA={GetOtaVersionString()}";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 设备信息写操作
|
|||
|
|
/// 命令 0xA2 写操作:接收56字节设备信息
|
|||
|
|
/// 数据格式(56字节,小端序):
|
|||
|
|
/// 字节0-15:设备序列号(16字节,ASCII编码)
|
|||
|
|
/// 字节16-31:设备型号(16字节,ASCII编码)
|
|||
|
|
/// 字节32-47:设备ID(16字节,ASCII编码)
|
|||
|
|
/// 字节48-51:固件版本(uint32_t,4字节小端序)
|
|||
|
|
/// 字节52-55:硬件版本(uint32_t,4字节小端序)
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct DeviceInfoWrite
|
|||
|
|
{
|
|||
|
|
public byte[] SerialNumber; // 设备序列号 (16字节)
|
|||
|
|
public byte[] DeviceModel; // 设备型号 (16字节)
|
|||
|
|
public byte[] DeviceId; // 设备ID (16字节)
|
|||
|
|
public uint FirmwareVersion; // 固件版本 (4字节)
|
|||
|
|
public uint HardwareVersion; // 硬件版本 (4字节)
|
|||
|
|
|
|||
|
|
public DeviceInfoWrite(string serialNumber = "", string deviceModel = "", string deviceId = "", uint firmwareVersion = 0, uint hardwareVersion = 0)
|
|||
|
|
{
|
|||
|
|
SerialNumber = new byte[16];
|
|||
|
|
DeviceModel = new byte[16];
|
|||
|
|
DeviceId = new byte[16];
|
|||
|
|
|
|||
|
|
// 填充字符串数据
|
|||
|
|
if (!string.IsNullOrEmpty(serialNumber))
|
|||
|
|
{
|
|||
|
|
byte[] serialBytes = System.Text.Encoding.ASCII.GetBytes(serialNumber);
|
|||
|
|
System.Buffer.BlockCopy(serialBytes, 0, SerialNumber, 0, Mathf.Min(serialBytes.Length, 16));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!string.IsNullOrEmpty(deviceModel))
|
|||
|
|
{
|
|||
|
|
byte[] modelBytes = System.Text.Encoding.ASCII.GetBytes(deviceModel);
|
|||
|
|
System.Buffer.BlockCopy(modelBytes, 0, DeviceModel, 0, Mathf.Min(modelBytes.Length, 16));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!string.IsNullOrEmpty(deviceId))
|
|||
|
|
{
|
|||
|
|
byte[] idBytes = System.Text.Encoding.ASCII.GetBytes(deviceId);
|
|||
|
|
System.Buffer.BlockCopy(idBytes, 0, DeviceId, 0, Mathf.Min(idBytes.Length, 16));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
FirmwareVersion = firmwareVersion;
|
|||
|
|
HardwareVersion = hardwareVersion;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
byte[] data = new byte[56];
|
|||
|
|
|
|||
|
|
// 字节0-15:设备序列号
|
|||
|
|
System.Buffer.BlockCopy(SerialNumber, 0, data, 0, 16);
|
|||
|
|
|
|||
|
|
// 字节16-31:设备型号
|
|||
|
|
System.Buffer.BlockCopy(DeviceModel, 0, data, 16, 16);
|
|||
|
|
|
|||
|
|
// 字节32-47:设备ID
|
|||
|
|
System.Buffer.BlockCopy(DeviceId, 0, data, 32, 16);
|
|||
|
|
|
|||
|
|
// 字节48-51:固件版本(小端序)
|
|||
|
|
data[48] = (byte)(FirmwareVersion & 0xFF);
|
|||
|
|
data[49] = (byte)((FirmwareVersion >> 8) & 0xFF);
|
|||
|
|
data[50] = (byte)((FirmwareVersion >> 16) & 0xFF);
|
|||
|
|
data[51] = (byte)((FirmwareVersion >> 24) & 0xFF);
|
|||
|
|
|
|||
|
|
// 字节52-55:硬件版本(小端序)
|
|||
|
|
data[52] = (byte)(HardwareVersion & 0xFF);
|
|||
|
|
data[53] = (byte)((HardwareVersion >> 8) & 0xFF);
|
|||
|
|
data[54] = (byte)((HardwareVersion >> 16) & 0xFF);
|
|||
|
|
data[55] = (byte)((HardwareVersion >> 24) & 0xFF);
|
|||
|
|
|
|||
|
|
return data;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public string GetSerialNumberString() => System.Text.Encoding.ASCII.GetString(SerialNumber).TrimEnd('\0');
|
|||
|
|
public string GetDeviceModelString() => System.Text.Encoding.ASCII.GetString(DeviceModel).TrimEnd('\0');
|
|||
|
|
public string GetDeviceIdString() => System.Text.Encoding.ASCII.GetString(DeviceId).TrimEnd('\0');
|
|||
|
|
|
|||
|
|
public override string ToString()
|
|||
|
|
{
|
|||
|
|
return $"序列号={GetSerialNumberString()}, 型号={GetDeviceModelString()}, ID={GetDeviceIdString()}, 固件版本={FirmwareVersion}, 硬件版本={HardwareVersion}";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 统计数据
|
|||
|
|
/// 命令 0xA3:返回32字节统计数据
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct StatisticsData
|
|||
|
|
{
|
|||
|
|
public uint EliminateCount; // 灭虫数量
|
|||
|
|
public uint TotalWorkTime; // 总工作时间(秒)
|
|||
|
|
public uint TodayWorkTime; // 今日工作时间(秒)
|
|||
|
|
public uint ScanCount; // 扫描次数
|
|||
|
|
public uint LaserFireCount; // 激光发射次数
|
|||
|
|
public uint MosquitoDataCount; // 蚊虫数据数量
|
|||
|
|
public uint DatalogCount; // datalog日志数量
|
|||
|
|
|
|||
|
|
public static StatisticsData FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var stats = new StatisticsData();
|
|||
|
|
if (data != null && data.Length >= 28)
|
|||
|
|
{
|
|||
|
|
stats.EliminateCount = (uint)(data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24));
|
|||
|
|
stats.TotalWorkTime = (uint)(data[4] | (data[5] << 8) | (data[6] << 16) | (data[7] << 24));
|
|||
|
|
stats.TodayWorkTime = (uint)(data[8] | (data[9] << 8) | (data[10] << 16) | (data[11] << 24));
|
|||
|
|
stats.ScanCount = (uint)(data[12] | (data[13] << 8) | (data[14] << 16) | (data[15] << 24));
|
|||
|
|
stats.LaserFireCount = (uint)(data[16] | (data[17] << 8) | (data[18] << 16) | (data[19] << 24));
|
|||
|
|
stats.MosquitoDataCount = (uint)(data[20] | (data[21] << 8) | (data[22] << 16) | (data[23] << 24));
|
|||
|
|
stats.DatalogCount = (uint)(data[24] | (data[25] << 8) | (data[26] << 16) | (data[27] << 24));
|
|||
|
|
}
|
|||
|
|
return stats;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public override string ToString()
|
|||
|
|
{
|
|||
|
|
return $"灭虫={EliminateCount}, 总工时={TotalWorkTime/3600}h, 今日={TodayWorkTime/60}min, 扫描={ScanCount}, 激光={LaserFireCount}";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 蚊虫数据查询
|
|||
|
|
/// 命令 0xA4:写操作接收8字节(start_index + end_index)
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct MosquitoDataQuery
|
|||
|
|
{
|
|||
|
|
public uint StartIndex; // 起始索引
|
|||
|
|
public uint EndIndex; // 结束索引
|
|||
|
|
|
|||
|
|
public static MosquitoDataQuery FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var query = new MosquitoDataQuery();
|
|||
|
|
if (data != null && data.Length >= 8)
|
|||
|
|
{
|
|||
|
|
query.StartIndex = (uint)(data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24));
|
|||
|
|
query.EndIndex = (uint)(data[4] | (data[5] << 8) | (data[6] << 16) | (data[7] << 24));
|
|||
|
|
}
|
|||
|
|
return query;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public byte[] ToBytes()
|
|||
|
|
{
|
|||
|
|
return new byte[]
|
|||
|
|
{
|
|||
|
|
(byte)(StartIndex & 0xFF), (byte)((StartIndex >> 8) & 0xFF),
|
|||
|
|
(byte)((StartIndex >> 16) & 0xFF), (byte)((StartIndex >> 24) & 0xFF),
|
|||
|
|
(byte)(EndIndex & 0xFF), (byte)((EndIndex >> 8) & 0xFF),
|
|||
|
|
(byte)((EndIndex >> 16) & 0xFF), (byte)((EndIndex >> 24) & 0xFF)
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public override string ToString()
|
|||
|
|
{
|
|||
|
|
return $"索引范围[{StartIndex}-{EndIndex}]";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 传感器数据
|
|||
|
|
/// 命令 0xA5:返回10字节传感器数据
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct SensorData
|
|||
|
|
{
|
|||
|
|
public ushort Temperature; // 温度 (单位: 0.1°C)
|
|||
|
|
public short AccelX; // X轴加速度 (mg)
|
|||
|
|
public short AccelY; // Y轴加速度 (mg)
|
|||
|
|
public short AccelZ; // Z轴加速度 (mg)
|
|||
|
|
public ushort CapacitorVoltage; // 电容电压 (单位: 0.01V)
|
|||
|
|
|
|||
|
|
public static SensorData FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var sensor = new SensorData();
|
|||
|
|
if (data != null && data.Length >= 10)
|
|||
|
|
{
|
|||
|
|
sensor.Temperature = (ushort)(data[0] | (data[1] << 8));
|
|||
|
|
sensor.AccelX = (short)(data[2] | (data[3] << 8));
|
|||
|
|
sensor.AccelY = (short)(data[4] | (data[5] << 8));
|
|||
|
|
sensor.AccelZ = (short)(data[6] | (data[7] << 8));
|
|||
|
|
sensor.CapacitorVoltage = (ushort)(data[8] | (data[9] << 8));
|
|||
|
|
}
|
|||
|
|
return sensor;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public float GetTemperatureCelsius() => Temperature * 0.1f;
|
|||
|
|
public float GetCapacitorVoltage() => CapacitorVoltage * 0.01f;
|
|||
|
|
|
|||
|
|
public override string ToString()
|
|||
|
|
{
|
|||
|
|
return $"温度={GetTemperatureCelsius():F1}°C, 加速度=({AccelX},{AccelY},{AccelZ})mg, 电容={GetCapacitorVoltage():F2}V";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 蚊虫数据
|
|||
|
|
/// 通知类型 0x0B:22字节 = 索引(4字节) + 蚊虫数据(18字节)
|
|||
|
|
/// 蚊虫数据结构:角度(4字节,*1000度) + 距离(2字节,毫米) + 大小(2字节,毫米) + 时间(6字节) + 保留(4字节)
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct MosquitoData
|
|||
|
|
{
|
|||
|
|
public uint Index; // 数据索引
|
|||
|
|
public int Angle; // 角度 (*1000度,支持负数,如90000表示90.000度)
|
|||
|
|
public ushort Distance; // 距离 (毫米)
|
|||
|
|
public ushort Size; // 大小 (毫米)
|
|||
|
|
public byte Year; // 年 (减去2000后的值)
|
|||
|
|
public byte Month; // 月
|
|||
|
|
public byte Day; // 日
|
|||
|
|
public byte Hour; // 时
|
|||
|
|
public byte Minute; // 分
|
|||
|
|
public byte Second; // 秒
|
|||
|
|
public byte[] Reserved; // 保留字节 (4字节)
|
|||
|
|
|
|||
|
|
public static MosquitoData FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var mosquito = new MosquitoData();
|
|||
|
|
if (data != null && data.Length >= 22)
|
|||
|
|
{
|
|||
|
|
// 索引 (4字节小端序)
|
|||
|
|
mosquito.Index = (uint)(data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24));
|
|||
|
|
|
|||
|
|
// 角度 (4字节有符号整数,小端序)
|
|||
|
|
mosquito.Angle = data[4] | (data[5] << 8) | (data[6] << 16) | (data[7] << 24);
|
|||
|
|
|
|||
|
|
// 距离 (2字节小端序)
|
|||
|
|
mosquito.Distance = (ushort)(data[8] | (data[9] << 8));
|
|||
|
|
|
|||
|
|
// 大小 (2字节小端序)
|
|||
|
|
mosquito.Size = (ushort)(data[10] | (data[11] << 8));
|
|||
|
|
|
|||
|
|
// 时间 (6字节)
|
|||
|
|
mosquito.Year = data[12];
|
|||
|
|
mosquito.Month = data[13];
|
|||
|
|
mosquito.Day = data[14];
|
|||
|
|
mosquito.Hour = data[15];
|
|||
|
|
mosquito.Minute = data[16];
|
|||
|
|
mosquito.Second = data[17];
|
|||
|
|
|
|||
|
|
// 保留字节 (4字节)
|
|||
|
|
mosquito.Reserved = new byte[4];
|
|||
|
|
System.Buffer.BlockCopy(data, 18, mosquito.Reserved, 0, 4);
|
|||
|
|
}
|
|||
|
|
return mosquito;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 获取实际角度值(度)
|
|||
|
|
/// </summary>
|
|||
|
|
public float GetActualAngle() => Angle / 1000.0f;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 获取完整年份
|
|||
|
|
/// </summary>
|
|||
|
|
public int GetFullYear() => 2000 + Year;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 获取时间字符串
|
|||
|
|
/// </summary>
|
|||
|
|
public string GetTimeString() => $"{GetFullYear():D4}-{Month:D2}-{Day:D2} {Hour:D2}:{Minute:D2}:{Second:D2}";
|
|||
|
|
|
|||
|
|
public override string ToString()
|
|||
|
|
{
|
|||
|
|
return $"[{Index}] 角度={GetActualAngle():F3}°, 距离={Distance}mm, 大小={Size}mm, 时间={GetTimeString()}";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 蚊虫数据数量查询结果
|
|||
|
|
/// 命令 0xA4 读操作返回:4字节(设备本地数据条数,小端序)
|
|||
|
|
/// </summary>
|
|||
|
|
[Serializable]
|
|||
|
|
public struct MosquitoDataCount
|
|||
|
|
{
|
|||
|
|
public uint Count; // 数据条数
|
|||
|
|
|
|||
|
|
public static MosquitoDataCount FromBytes(byte[] data)
|
|||
|
|
{
|
|||
|
|
var count = new MosquitoDataCount();
|
|||
|
|
if (data != null && data.Length >= 4)
|
|||
|
|
{
|
|||
|
|
count.Count = (uint)(data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24));
|
|||
|
|
}
|
|||
|
|
return count;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public override string ToString()
|
|||
|
|
{
|
|||
|
|
return $"蚊虫数据条数: {Count}";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endregion
|
|||
|
|
}
|
|||
|
|
|