fix: dual-port DMX output in show mode; AERE claim verification; SHOW_DIAG mode
- Root cause of 'steady white': fixture on Butler port2, our data only went to port1 (port data regions are independent: port1 @0x3C, port2 @0x23C) - show mode now mirrors pattern to both ports; SHOW_DIAG=1 gives 3s off/on blink test - Claim now verifies AERE response and warns on failure - Docs updated with port layout note; README updated
This commit is contained in:
parent
9be449fda8
commit
8938b15691
|
|
@ -66,7 +66,7 @@ $env:HOSTDEMO_MODE = "show"
|
||||||
.\target\release\enet-validator.exe hostdemo 192.168.123.1 600
|
.\target\release\enet-validator.exe hostdemo 192.168.123.1 600
|
||||||
```
|
```
|
||||||
|
|
||||||
- DMX:地址 1-32 彩虹呼吸渐变(HMKJ 32 通道灯具),**零模板纯自构造帧**(`build_dmx_frame`)
|
- DMX:地址 1-32 彩虹呼吸渐变(HMKJ 32 通道灯具),**零模板纯自构造帧**(`build_dmx_frame`),**port1+port2 双口同时输出**(灯具接任一口均可控)
|
||||||
- RDM:每 700ms 轮询 3 台已知 UID,实时打印 `DEVICE_INFO` 与 `DMX_START_ADDRESS`,**纯自构造请求帧**(`build_rdm_frame2`)
|
- RDM:每 700ms 轮询 3 台已知 UID,实时打印 `DEVICE_INFO` 与 `DMX_START_ADDRESS`,**纯自构造请求帧**(`build_rdm_frame2`)
|
||||||
- 运行无需任何抓包/replay 文件;DMX/RDM 帧全字段已破解(见 docs/enet_protocol_current.md)
|
- 运行无需任何抓包/replay 文件;DMX/RDM 帧全字段已破解(见 docs/enet_protocol_current.md)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,10 @@
|
||||||
| 0x22-0x25 | r1 u32 LE | 随机数,port1 加密种子 |
|
| 0x22-0x25 | r1 u32 LE | 随机数,port1 加密种子 |
|
||||||
| 0x26-0x29 | r2 u32 LE | 随机数,port2 加密种子 |
|
| 0x26-0x29 | r2 u32 LE | 随机数,port2 加密种子 |
|
||||||
| 0x2A-0x2D | 00 00 00 00 | 常量 |
|
| 0x2A-0x2D | 00 00 00 00 | 常量 |
|
||||||
| 0x2E-0x2F | 01 00 | 常量(实测 02 也被接受 → Butler 不校验) |
|
| 0x2E-0x2F | 01 00 | 输出口对序号(SL 抓包=01;实测 01/02 均被接受) |
|
||||||
|
|
||||||
|
> ⚠ **两个 DMX 口数据区独立**:port1 @0x3C、port2 @0x23C。灯具接在哪个口就吃哪个口的数据。
|
||||||
|
> 实测灯具在 port2 时只发 port1 会导致灯具保持旧值/兜底白色——**show 模式已改为双口同时输出**。
|
||||||
| 0x30-0x31 | 00 00 | 常量 |
|
| 0x30-0x31 | 00 00 | 常量 |
|
||||||
| 0x32-0x33 | 01 00 | 常量 |
|
| 0x32-0x33 | 01 00 | 常量 |
|
||||||
| 0x34-0x35 | 00 02 | port1 数据长度 = 512(u16 LE) |
|
| 0x34-0x35 | 00 02 | port1 数据长度 = 512(u16 LE) |
|
||||||
|
|
|
||||||
|
|
@ -210,6 +210,34 @@ pub fn run(args: &[String]) {
|
||||||
"hdr" => hdr(),
|
"hdr" => hdr(),
|
||||||
"rdm" => rdm(),
|
"rdm" => rdm(),
|
||||||
"ctr" => ctr(),
|
"ctr" => ctr(),
|
||||||
|
// gendmx: 同输入生成 从零构造 vs dmx_a模板补丁 两版帧并逐字节 diff
|
||||||
|
"gendmx" => {
|
||||||
|
let p1 = [0x11u8; 512];
|
||||||
|
let p2 = [0u8; 512];
|
||||||
|
let r1: u32 = 0x15198c1a;
|
||||||
|
let r2: u32 = 0x1676d663;
|
||||||
|
let b12: u32 = 0x11223344;
|
||||||
|
let ctr: u32 = 0x197;
|
||||||
|
let gen = crate::hostdemo::build_dmx_frame(&p1, &p2, r1, r2, b12, ctr);
|
||||||
|
// 模板版: dmx_a + 同样的补丁 (复刻已验证工作版本)
|
||||||
|
let mut tpl = load_hex("samples/dmx_a_1126.hex");
|
||||||
|
tpl[0x1A..0x1E].copy_from_slice(&ctr.to_le_bytes());
|
||||||
|
tpl[0x2E] = 2; tpl[0x2F] = 0;
|
||||||
|
tpl[0x22..0x26].copy_from_slice(&r1.to_le_bytes());
|
||||||
|
tpl[0x26..0x2A].copy_from_slice(&r2.to_le_bytes());
|
||||||
|
let enc1 = crate::aech::agios_dmx_encrypt(&p1, r1, b12);
|
||||||
|
let enc2 = crate::aech::agios_dmx_encrypt(&p2, r2, b12);
|
||||||
|
tpl[0x3C..0x23C].copy_from_slice(&enc1);
|
||||||
|
tpl[0x23C..0x43C].copy_from_slice(&enc2);
|
||||||
|
println!("gen len={} tpl len={}", gen.len(), tpl.len());
|
||||||
|
let n = gen.len().min(tpl.len());
|
||||||
|
let diffs: Vec<usize> = (0..n).filter(|&i| gen[i] != tpl[i]).collect();
|
||||||
|
println!("差异 {} 处:", diffs.len());
|
||||||
|
for i in &diffs {
|
||||||
|
println!(" 0x{:03x}: gen={:02x} tpl={:02x}", i, gen[*i], tpl[*i]);
|
||||||
|
}
|
||||||
|
if diffs.is_empty() { println!("两版逐字节一致"); }
|
||||||
|
}
|
||||||
_ => println!("未知 analyze 子命令 {}", args[0]),
|
_ => println!("未知 analyze 子命令 {}", args[0]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ fn build_rdm_frame(dest: &[u8; 6], tn: u8, qseq: u16, cc: u8, pid: u16, pd: &[u8
|
||||||
|
|
||||||
/// ★从零构造 DMX 数据帧 (1084B, 全字段已破解 — analyze hdr 实证):
|
/// ★从零构造 DMX 数据帧 (1084B, 全字段已破解 — analyze hdr 实证):
|
||||||
/// 帧头全部常量已知, 仅 0x1A 计数器(+1/帧) 与 0x22/0x26 r1/r2(加密随机) 变化
|
/// 帧头全部常量已知, 仅 0x1A 计数器(+1/帧) 与 0x22/0x26 r1/r2(加密随机) 变化
|
||||||
fn build_dmx_frame(p1: &[u8; 512], p2: &[u8; 512], r1: u32, r2: u32, b12: u32, ctr: u32) -> Vec<u8> {
|
pub fn build_dmx_frame(p1: &[u8; 512], p2: &[u8; 512], r1: u32, r2: u32, b12: u32, ctr: u32) -> Vec<u8> {
|
||||||
let mut f = vec![0u8; 1084];
|
let mut f = vec![0u8; 1084];
|
||||||
f[0..4].copy_from_slice(b"ENet");
|
f[0..4].copy_from_slice(b"ENet");
|
||||||
f[4] = 4; // type=4
|
f[4] = 4; // type=4
|
||||||
|
|
@ -116,7 +116,7 @@ fn build_dmx_frame(p1: &[u8; 512], p2: &[u8; 512], r1: u32, r2: u32, b12: u32, c
|
||||||
f[0x20] = 1; f[0x21] = 1; // 端口使能 mask: port1+port2
|
f[0x20] = 1; f[0x21] = 1; // 端口使能 mask: port1+port2
|
||||||
f[0x22..0x26].copy_from_slice(&r1.to_le_bytes());
|
f[0x22..0x26].copy_from_slice(&r1.to_le_bytes());
|
||||||
f[0x26..0x2A].copy_from_slice(&r2.to_le_bytes());
|
f[0x26..0x2A].copy_from_slice(&r2.to_le_bytes());
|
||||||
f[0x2E] = 1; // 常量 (实测 01; 02 也能被接受)
|
f[0x2E] = 2; // 输出口对序号 (SL 抓包=01, 本机实测 01/02 均可; 关键在数据区按端口分开)
|
||||||
f[0x32] = 1; // 常量
|
f[0x32] = 1; // 常量
|
||||||
f[0x35] = 2; f[0x37] = 2; // port1/port2 数据长度 = 512 (u16 LE ×2)
|
f[0x35] = 2; f[0x37] = 2; // port1/port2 数据长度 = 512 (u16 LE ×2)
|
||||||
f[0x38] = 1; // 常量 01 00 00 00
|
f[0x38] = 1; // 常量 01 00 00 00
|
||||||
|
|
@ -482,7 +482,22 @@ pub fn run(target: &str, bind_ip: &str, secs: u64) {
|
||||||
ch[0x88..0x8C].copy_from_slice(&nonce.to_le_bytes());
|
ch[0x88..0x8C].copy_from_slice(&nonce.to_le_bytes());
|
||||||
ch[0x8C..0x90].copy_from_slice(&bc.to_le_bytes());
|
ch[0x8C..0x90].copy_from_slice(&bc.to_le_bytes());
|
||||||
sock.send_to(&ch, &dst).unwrap();
|
sock.send_to(&ch, &dst).unwrap();
|
||||||
std::thread::sleep(Duration::from_millis(400));
|
// ★验证 AERE 应答 (RDM 不加密, 认领失败 RDM 照样通但 DMX 解密失败 → 灯白色兜底)
|
||||||
|
let mut aere_ok = false;
|
||||||
|
let w = Instant::now();
|
||||||
|
while w.elapsed() < Duration::from_millis(700) {
|
||||||
|
if let Ok((n, _)) = sock.recv_from(&mut buf) {
|
||||||
|
let b = &buf[..n];
|
||||||
|
if n > 0xC4 && b.windows(4).any(|x| x == b"AERE") {
|
||||||
|
let resp = g32(b, 0xC0);
|
||||||
|
let want = agios_g(nonce);
|
||||||
|
aere_ok = resp == want;
|
||||||
|
println!("[{}] AERE resp=0x{:08x} want=0x{:08x} {}", label, resp, want, if aere_ok { "★PASS" } else { "FAIL" });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !aere_ok { println!("[{}] ⚠ 未收到有效 AERE — 认领可能失败, DMX 将无法解密!", label); }
|
||||||
println!("[{}] 认领完成 B12=0x{:08x}; {}", label, bc,
|
println!("[{}] 认领完成 B12=0x{:08x}; {}", label, bc,
|
||||||
if is_show { "地址1-32 炫彩呼吸 (零模板纯自构造 DMX+RDM)" } else { "verbatim DMX + 模板 RDM" });
|
if is_show { "地址1-32 炫彩呼吸 (零模板纯自构造 DMX+RDM)" } else { "verbatim DMX + 模板 RDM" });
|
||||||
let rdm_t: Option<Vec<u8>> = base_rdm;
|
let rdm_t: Option<Vec<u8>> = base_rdm;
|
||||||
|
|
@ -515,7 +530,11 @@ pub fn run(target: &str, bind_ip: &str, secs: u64) {
|
||||||
// verbatim DMX 循环
|
// verbatim DMX 循环
|
||||||
// DMX 数据替换
|
// DMX 数据替换
|
||||||
let mut p1 = [0u8; 512];
|
let mut p1 = [0u8; 512];
|
||||||
if is_show {
|
if std::env::var("SHOW_DIAG").unwrap_or_default() == "1" {
|
||||||
|
// 诊断: ch1-32 全灭3s / 全亮3s 交替 — 验证 DMX 是否到达灯具
|
||||||
|
let v = if (t as u32 / 3) % 2 == 0 { 0u8 } else { 255u8 };
|
||||||
|
for i in 0..32 { p1[i] = v; }
|
||||||
|
} else if is_show {
|
||||||
// show: 地址 1-32 炫彩呼吸波 (周期放慢, 更舒缓)
|
// show: 地址 1-32 炫彩呼吸波 (周期放慢, 更舒缓)
|
||||||
let breathe = (t * 0.45).sin() * 0.5 + 0.5;
|
let breathe = (t * 0.45).sin() * 0.5 + 0.5;
|
||||||
for i in 0..32usize {
|
for i in 0..32usize {
|
||||||
|
|
@ -531,9 +550,12 @@ pub fn run(target: &str, bind_ip: &str, secs: u64) {
|
||||||
}
|
}
|
||||||
let rr1 = rng.next();
|
let rr1 = rng.next();
|
||||||
let rr2 = rng.next();
|
let rr2 = rng.next();
|
||||||
|
// show 模式: 图案同时输出到 port1+port2 (灯具可能在任一口, 实测 port2 出灯)
|
||||||
|
let mut p2 = [0u8; 512];
|
||||||
|
if is_show || std::env::var("SHOW_PORT2").unwrap_or_default() == "1" { p2 = p1; }
|
||||||
let f = if is_show {
|
let f = if is_show {
|
||||||
// ★纯自构造 DMX 帧 (零模板)
|
// ★纯自构造 DMX 帧 (零模板)
|
||||||
build_dmx_frame(&p1, &[0u8; 512], rr1, rr2, bc, di as u32)
|
build_dmx_frame(&p1, &p2, rr1, rr2, bc, di as u32)
|
||||||
} else {
|
} else {
|
||||||
let mut fx = dmxf[di % dmxf.len()].clone();
|
let mut fx = dmxf[di % dmxf.len()].clone();
|
||||||
fx[0x1A..0x1E].copy_from_slice(&(di as u32).to_le_bytes());
|
fx[0x1A..0x1E].copy_from_slice(&(di as u32).to_le_bytes());
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user