using System;
using System.Collections.Generic;
namespace AutoFocusTool.Serial
{
///
/// 下位机串口协议帧构造 + 校验。完全依据原工程 Commander.cs 实测确证:
/// 帧: [0]帧头0x5E [1]命令码 [2]序号0x00 [3]整帧长 [4..n-2]辅助码+参数 [n-1]累加和校验
/// 校验 = 前 n-1 字节逐字节相加(byte溢出截断)放末字节。无独立帧尾。
/// 电机绝对运动脉冲值: 32位int, 大端写入帧(高字节在前)。
///
public static class Protocol
{
public const byte ST = 0x5E; // 帧头
public const byte CMD_MOTOR = 0x05; // 电机控制
public const byte CMD_IO = 0x09; // 设IO(含LED)
public const byte CMD_READ_MOTOR = 0x18; // 读电机位置
public const byte CMD_SHAKE = 0x01; // 握手
public const byte CMD_READ_EEPROM = 0x11; // 读EEPROM(回复10字节)
/// 累加和校验:前 n-1 字节求和写入末字节
public static byte[] WithChecksum(byte[] frame)
{
byte sum = 0;
for (int i = 0; i < frame.Length - 1; i++) sum += frame[i];
frame[frame.Length - 1] = sum;
return frame;
}
/// 校验回复帧(末字节==前面累加和)
public static bool CheckChecksum(byte[] frame)
{
if (frame == null || frame.Length < 2) return false;
byte sum = 0;
for (int i = 0; i < frame.Length - 1; i++) sum += frame[i];
return frame[frame.Length - 1] == sum;
}
///
/// 根据命令码返回下位机回复的固定字节数(依据 Commander.CustomProtocolLength)。
/// 接收端按这个长度收帧,不靠帧尾。
///
public static int ReplyLength(byte cmd)
{
switch (cmd)
{
case 0x01: return 6; // 握手
case 0x02: return 6; // 自检
case 0x04: return 7; // 设目标温度
case 0x05: return 6; // 电机控制
case 0x06: return 9; // 读传感器
case 0x08: return 9;
case 0x09: return 6; // 设IO(LED开关)
case 0x10: return 7;
case 0x11: return 10; // 读EEPROM
case 0x12: return 6;
case 0x18: return 10; // 读电机位置
case 0x19: return 6;
case 0x20: return 12;
default: return 6;
}
}
// ── 握手:5E 01 00 05 00 + 校验 ──
public static byte[] ShakeHands()
=> WithChecksum(new byte[] { ST, 0x01, 0x00, 0x05, 0x00 });
// ── 读舱室绑定的相机序列号 CCDSN(读EEPROM,原工程 GetModule)──
// 5E 11 00 09 00 00 10 04 00 + 校验,回复10字节,[4..7]拼int32
public static byte[] GetCCDSN()
=> WithChecksum(new byte[] { ST, 0x11, 0x00, 0x09, 0x00, 0x00, 0x10, 0x04, 0x00 });
// ── 读EEPROM灯光亮度(只读,无写命令)──
// 5E 11 00 09 00 05 34 04 00 + 校验
public static byte[] ReadLightBrightness()
=> WithChecksum(new byte[] { ST, 0x11, 0x00, 0x09, 0x00, 0x05, 0x34, 0x04, 0x00 });
///
/// 读第 wellNum(1-16) 个well的水平电机位置(旋转脉冲,存EEPROM)。
/// 培养皿16个well圆形布局,转动培养皿让well对准相机。地址表来自原工程 Commander.cs:284。
/// 命令: 5E 11 00 09 00 04 [地址低] 04 00 + 校验,回复10字节,位置在[4..7]。
///
public static byte[] ReadWellHorizontalPos(int wellNum)
{
// wellNum → EEPROM 地址低字节(确证自源码)
byte addr;
switch (wellNum)
{
case 1: addr = 0x00; break;
case 2: addr = 0x50; break;
case 3: addr = 0x54; break;
case 4: addr = 0x58; break;
case 5: addr = 0x5C; break;
case 6: addr = 0x60; break;
case 7: addr = 0x64; break;
case 8: addr = 0x68; break;
case 9: addr = 0x6C; break;
case 10: addr = 0x70; break;
case 11: addr = 0x74; break;
case 12: addr = 0x78; break;
case 13: addr = 0x7C; break;
case 14: addr = 0x80; break;
case 15: addr = 0x84; break;
case 16: addr = 0x04; break;
default: addr = 0x00; break;
}
return WithChecksum(new byte[] { ST, 0x11, 0x00, 0x09, 0x00, 0x04, addr, 0x04, 0x00 });
}
///
/// 读第 wellNum(1-16) 个well的垂直电机焦准脉冲数(Z对焦零点,存EEPROM)。
/// 地址表来自原工程 Commander.cs:530。well1=0x08, 步进4, well16=0x44。
///
public static byte[] ReadWellFocusZero(int wellNum)
{
byte addr;
switch (wellNum)
{
case 1: addr = 0x08; break;
case 2: addr = 0x0C; break;
case 3: addr = 0x10; break;
case 4: addr = 0x14; break;
case 5: addr = 0x18; break;
case 6: addr = 0x1C; break;
case 7: addr = 0x20; break;
case 8: addr = 0x24; break;
case 9: addr = 0x28; break;
case 10: addr = 0x2C; break;
case 11: addr = 0x30; break;
case 12: addr = 0x34; break;
case 13: addr = 0x38; break;
case 14: addr = 0x3C; break;
case 15: addr = 0x40; break;
case 16: addr = 0x44; break;
default: addr = 0x08; break;
}
return WithChecksum(new byte[] { ST, 0x11, 0x00, 0x09, 0x00, 0x04, addr, 0x04, 0x00 });
}
/// 读垂直电机扫描间隔脉冲(每层Z步距,存EEPROM)。Commander.cs:668。
public static byte[] ReadScanStep()
=> WithChecksum(new byte[] { ST, 0x11, 0x00, 0x09, 0x00, 0x04, 0x48, 0x04, 0x00 });
// ── LED 开/关(固定字节,原工程实测)──
public static byte[] OpenLED()
=> new byte[] { 0x5E, 0x09, 0x00, 0x07, 0x00, 0x01, 0x6F };
public static byte[] CloseLED()
=> new byte[] { 0x5E, 0x09, 0x00, 0x07, 0x00, 0x00, 0x6E };
// ── 读电机位置:5E 18 00 06 [轴] 00 + 校验。轴: 0x01水平 0x02垂直 ──
public static byte[] ReadHorizontalMotor()
=> WithChecksum(new byte[] { ST, 0x18, 0x00, 0x06, 0x01, 0x00 });
public static byte[] ReadVerticalMotor()
=> WithChecksum(new byte[] { ST, 0x18, 0x00, 0x06, 0x02, 0x00 });
// ── 电机复位(固定字节,原工程实测)──
// 水平复位 5E 05 00 0B 13 00 00 00 0B B8 44
public static byte[] HorizontalReset()
=> new byte[] { 0x5E, 0x05, 0x00, 0x0B, 0x13, 0x00, 0x00, 0x00, 0x0B, 0xB8, 0x44 };
// 垂直复位 5E 05 00 0B 23 00 00 00 07 D0 68
public static byte[] VerticalReset()
=> new byte[] { 0x5E, 0x05, 0x00, 0x0B, 0x23, 0x00, 0x00, 0x00, 0x07, 0xD0, 0x68 };
// ── 电机绝对运动:11字节帧,辅助码 0x14水平 0x24垂直,脉冲32位大端 ──
public static byte[] HorizontalAbsolute(int pulse) => MotorAbsolute(0x14, pulse);
public static byte[] VerticalAbsolute(int pulse) => MotorAbsolute(0x24, pulse);
// ── 电机相对正/反转:辅助码 水平0x10/0x11,垂直0x20/0x21 ──
public static byte[] HorizontalForward(int pulse) => MotorAbsolute(0x10, pulse);
public static byte[] HorizontalBackward(int pulse) => MotorAbsolute(0x11, pulse);
public static byte[] VerticalForward(int pulse) => MotorAbsolute(0x20, pulse);
public static byte[] VerticalBackward(int pulse) => MotorAbsolute(0x21, pulse);
///
/// 构造电机运动帧:5E 05 00 0B [辅助码] [p3 p2 p1 p0 大端] 00 [校验]
///
private static byte[] MotorAbsolute(byte auxCode, int pulse)
{
byte[] p = BitConverter.GetBytes(pulse); // 小端: p[0]低..p[3]高
var cmd = new List
{
ST, CMD_MOTOR, 0x00, 0x0B, auxCode,
p[3], p[2], p[1], p[0], // 大端写入:高字节在前
0x00, // 参数5
0x00 // 校验占位
};
return WithChecksum(cmd.ToArray());
}
/// 解析读电机位置回复:字节[4..7]拼 int32(帧内大端)。失败 -1。
public static int ParseMotorPosition(byte[] reply)
{
if (reply == null || reply.Length < 8) return -1;
byte[] t = { reply[7], reply[6], reply[5], reply[4] };
if (!BitConverter.IsLittleEndian) Array.Reverse(t);
return BitConverter.ToInt32(t, 0);
}
/// 解析握手回复:字节[2]即模块号/houseSn。
public static int ParseShakeHands(byte[] reply)
{
if (reply == null || reply.Length < 3) return -1;
return reply[2];
}
/// 解析读EEPROM回复:字节[4..7]拼 int32(用于 CCDSN / 亮度)。失败 -1。
public static int ParseEepromInt(byte[] reply)
{
if (reply == null || reply.Length < 8) return -1;
byte[] t = { reply[4], reply[5], reply[6], reply[7] };
if (!BitConverter.IsLittleEndian) Array.Reverse(t);
return BitConverter.ToInt32(t, 0);
}
}
}