killapp/Assets/Scripts/BLEProtocol/BLEProtocolStructs.cs
2026-03-30 16:25:00 +08:00

2173 lines
76 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>
/// 语言设置
/// 命令 0x100x00=英文, 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; // 任务ID0-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>
/// 从字节数组解析写入格式包含TaskId8字节
/// 格式任务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>
/// 从字节数组解析读取格式不包含TaskId7字节
/// 格式:开关(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>
/// 加速度传感器数据
/// 命令 0x53X轴(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>
/// 工作模式设置
/// 命令 0xA01字节工作模式
/// </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设备ID16字节ASCII编码
/// 字节48-53BLE MAC地址6字节
/// 字节54-59WiFi MAC地址6字节
/// 字节60-63固件版本uint32_t4字节小端序
/// 字节64-67硬件版本uint32_t4字节小端序
/// 字节68-71OTA版本uint32_t4字节小端序
/// </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-53BLE MAC地址
if (data.Length >= 54)
System.Buffer.BlockCopy(data, 48, info.BleMacAddress, 0, 6);
// 字节54-59WiFi 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-71OTA版本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设备ID16字节ASCII编码
/// 字节48-51固件版本uint32_t4字节小端序
/// 字节52-55硬件版本uint32_t4字节小端序
/// </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>
/// 蚊虫数据
/// 通知类型 0x0B22字节 = 索引(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
}