hostdemo: RDM discovery + DMX control on real Butler .1 — FULL WORKING (slfull recipe)
This commit is contained in:
parent
a98930aa71
commit
fcb20a7fa3
449
src/hostdemo.rs
449
src/hostdemo.rs
|
|
@ -158,6 +158,442 @@ pub fn run(target: &str, bind_ip: &str, secs: u64) {
|
||||||
}
|
}
|
||||||
if !dev_ok { println!("[1] 发现无应答, 终止"); return; }
|
if !dev_ok { println!("[1] 发现无应答, 终止"); return; }
|
||||||
|
|
||||||
|
// replay: verbatim 重放 SL 的完整会话帧流 (sym_startup t=101.3..136), 双阶段定位 RDM 门控
|
||||||
|
if std::env::var("HOSTDEMO_MODE").unwrap_or_default() == "replay" {
|
||||||
|
let path = std::env::var("REPLAY_FILE")
|
||||||
|
.unwrap_or_else(|_| "C:/Users/tonycao/AppData/Local/Temp/opencode/replay_frames.txt".into());
|
||||||
|
let data = std::fs::read_to_string(&path).expect("replay file");
|
||||||
|
let mut frames: Vec<(f32, String, u16, Vec<u8>)> = Vec::new();
|
||||||
|
for line in data.lines() {
|
||||||
|
let p: Vec<&str> = line.split('|').collect();
|
||||||
|
if p.len() < 4 { continue; }
|
||||||
|
let t: f32 = p[0].parse().unwrap();
|
||||||
|
let dst = p[1].to_string();
|
||||||
|
let port: u16 = p[2].parse().unwrap();
|
||||||
|
let hex = &p[3];
|
||||||
|
let mut b = Vec::with_capacity(hex.len() / 2);
|
||||||
|
for i in (0..hex.len()).step_by(2) { b.push(u8::from_str_radix(&hex[i..i + 2], 16).unwrap()); }
|
||||||
|
frames.push((t, dst, port, b));
|
||||||
|
}
|
||||||
|
let t0 = frames[0].0;
|
||||||
|
for phase in ["A(无认领 verbatim)", "B(先认领再 verbatim)"] {
|
||||||
|
println!("[R] === 阶段 {}: 重放 {} 帧 / {:.1}s ===", phase, frames.len(), frames[frames.len() - 1].0 - t0);
|
||||||
|
if phase.starts_with('B') {
|
||||||
|
// 快速认领
|
||||||
|
let o2: Vec<u8> = bind_ip.split('.').map(|x| x.parse().unwrap()).collect();
|
||||||
|
let ident = load_template("announce144");
|
||||||
|
sock.send_to(&ident, &dst).unwrap();
|
||||||
|
std::thread::sleep(Duration::from_millis(300));
|
||||||
|
let nonce = rng.next();
|
||||||
|
let bc = rng.next();
|
||||||
|
let mut ch = load_template("claim_h112");
|
||||||
|
ch[0x1A..0x1E].copy_from_slice(&[o2[3], o2[2], o2[1], o2[0]]);
|
||||||
|
ch[0x88..0x8C].copy_from_slice(&nonce.to_le_bytes());
|
||||||
|
ch[0x8C..0x90].copy_from_slice(&bc.to_le_bytes());
|
||||||
|
sock.send_to(&ch, &dst).unwrap();
|
||||||
|
std::thread::sleep(Duration::from_millis(500));
|
||||||
|
println!("[R] 认领已发 (B12=0x{:08x})", bc);
|
||||||
|
}
|
||||||
|
let start = Instant::now();
|
||||||
|
let mut recv_n = 0u64;
|
||||||
|
let mut fi = 0;
|
||||||
|
while start.elapsed().as_secs_f32() < frames[frames.len() - 1].0 - t0 + 3.0 {
|
||||||
|
let now = start.elapsed().as_secs_f32();
|
||||||
|
while fi < frames.len() && frames[fi].0 - t0 <= now {
|
||||||
|
let (_, d, port, b) = &frames[fi];
|
||||||
|
let target_addr = format!("{}:{}", d, port);
|
||||||
|
let _ = sock.send_to(b, &target_addr);
|
||||||
|
fi += 1;
|
||||||
|
}
|
||||||
|
while let Ok((n, src)) = sock.recv_from(&mut buf) {
|
||||||
|
recv_n += 1;
|
||||||
|
let b = &buf[..n];
|
||||||
|
println!(" [R] ← len={} src={} head={}", n, src, hexs(&b[..std::cmp::min(20, n)]));
|
||||||
|
}
|
||||||
|
std::thread::sleep(Duration::from_millis(2));
|
||||||
|
}
|
||||||
|
println!("[R] 阶段 {} 结束: 收到 {} 帧", phase, recv_n);
|
||||||
|
std::thread::sleep(Duration::from_millis(1500));
|
||||||
|
// 排空残留
|
||||||
|
while sock.recv_from(&mut buf).is_ok() {}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sls: session-continue — 不认领! 直接续用 SL 会话 (B12=0x4366937a), SL 实帧做模板
|
||||||
|
if std::env::var("HOSTDEMO_MODE").unwrap_or_default() == "sls" {
|
||||||
|
let path = std::env::var("REPLAY_FILE")
|
||||||
|
.unwrap_or_else(|_| "C:/Users/tonycao/AppData/Local/Temp/opencode/replay_frames.txt".into());
|
||||||
|
let data = std::fs::read_to_string(&path).expect("replay file");
|
||||||
|
let sb12: u32 = 0x4366937a; // SL 22:52 会话 B12 (Butler 会话状态绑定的值)
|
||||||
|
// 从重放清单提取模板: DMX 数据帧 / RDM branch 请求
|
||||||
|
let mut base_dmx: Option<Vec<u8>> = None;
|
||||||
|
let mut base_rdm: Option<Vec<u8>> = None;
|
||||||
|
let mut max_ctr: u32 = 0;
|
||||||
|
for line in data.lines() {
|
||||||
|
let p: Vec<&str> = line.split('|').collect();
|
||||||
|
if p.len() < 4 { continue; }
|
||||||
|
let hex = p[3];
|
||||||
|
if hex.len() < 0x50 * 2 { continue; }
|
||||||
|
let b = |o: usize| u8::from_str_radix(&hex[o * 2..o * 2 + 2], 16).unwrap();
|
||||||
|
if hex.len() == 1084 * 2 && b(0x18) == 1 && b(0x20) == 1 && b(0x21) == 1 && b(0x3c) != 1 {
|
||||||
|
let ctr = g32(&hex.as_bytes(), 0); // placeholder never used
|
||||||
|
let _ = ctr;
|
||||||
|
let mut fb = Vec::with_capacity(1084);
|
||||||
|
for i in (0..hex.len()).step_by(2) { fb.push(u8::from_str_radix(&hex[i..i + 2], 16).unwrap()); }
|
||||||
|
let c = u32::from_le_bytes([fb[0x1A], fb[0x1B], fb[0x1C], fb[0x1D]]);
|
||||||
|
if c > max_ctr { max_ctr = c; }
|
||||||
|
if base_dmx.is_none() { base_dmx = Some(fb); }
|
||||||
|
}
|
||||||
|
if hex.len() == 1084 * 2 && b(0x3c) == 1 && b(0x30) == 0 && b(0x50) == 0 && b(0x51) == 1 && base_rdm.is_none() {
|
||||||
|
let mut fb = Vec::with_capacity(1084);
|
||||||
|
for i in (0..hex.len()).step_by(2) { fb.push(u8::from_str_radix(&hex[i..i + 2], 16).unwrap()); }
|
||||||
|
base_rdm = Some(fb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let dmx_t = base_dmx.expect("no DMX template in replay file");
|
||||||
|
let rdm_t = base_rdm.expect("no RDM template in replay file");
|
||||||
|
let ctr0 = max_ctr + 10;
|
||||||
|
println!("[SLS] 会话续用: B12=0x{:08x}, DMX模板 counter 基线 {}", sb12, ctr0);
|
||||||
|
println!("[SLS] DMX模板 0x2E-0x3B: {}", hexs(&dmx_t[0x2E..0x3C]));
|
||||||
|
let known: [[u8; 6]; 3] = [
|
||||||
|
[0x01, 0x07, 0x34, 0x67, 0x01, 0x25],
|
||||||
|
[0x01, 0x07, 0x34, 0x88, 0x09, 0x00],
|
||||||
|
[0x08, 0x99, 0x50, 0x00, 0x26, 0xc9],
|
||||||
|
];
|
||||||
|
let start = Instant::now();
|
||||||
|
let mut ctr = ctr0;
|
||||||
|
let mut last_hb = Instant::now() - Duration::from_secs(10);
|
||||||
|
let mut last_rdm = Instant::now() - Duration::from_secs(10);
|
||||||
|
let mut step = 0usize; // 0=UN_MUTE, 1..=3=窄分支, 4+=GET
|
||||||
|
let mut addr_list: Vec<u16> = Vec::new();
|
||||||
|
let mut resp_n = 0u64;
|
||||||
|
while start.elapsed() < Duration::from_secs(secs.max(60)) {
|
||||||
|
let t = start.elapsed().as_secs_f32();
|
||||||
|
if last_hb.elapsed() >= Duration::from_secs(6) {
|
||||||
|
let _ = sock.send_to(&load_template("heartbeat"), &dst);
|
||||||
|
last_hb = Instant::now();
|
||||||
|
}
|
||||||
|
// RDM 任务每 ~700ms 一步
|
||||||
|
if t > 1.0 && last_rdm.elapsed() >= Duration::from_millis(700) {
|
||||||
|
last_rdm = Instant::now();
|
||||||
|
let mut f = rdm_t.clone();
|
||||||
|
f[0x1A..0x1E].copy_from_slice(&ctr.to_le_bytes());
|
||||||
|
let mut pd: Vec<u8> = Vec::new();
|
||||||
|
let mut pid: u16 = 0x0001;
|
||||||
|
let mut dest = [0xffu8; 6];
|
||||||
|
match step {
|
||||||
|
0 => { pid = 0x0003; } // UN_MUTE 广播
|
||||||
|
1..=3 => {
|
||||||
|
let u = known[step - 1];
|
||||||
|
let v = uid_u64(&u);
|
||||||
|
pd.extend_from_slice(&u64_uid(v - 1));
|
||||||
|
pd.extend_from_slice(&u64_uid(v + 1));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// GET DEVICE_INFO 轮询已发现地址
|
||||||
|
if (step - 4) < known.len() {
|
||||||
|
dest = known[step - 4];
|
||||||
|
pid = 0x0060;
|
||||||
|
} else { step = 3; } // 循环重试 GET
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let msg_len = 24 + pd.len();
|
||||||
|
f[0x3C] = 0x01;
|
||||||
|
f[0x3D] = msg_len as u8;
|
||||||
|
f[0x3E..0x44].copy_from_slice(&dest);
|
||||||
|
f[0x4A] = (step * 7 + 1) as u8;
|
||||||
|
f[0x4F] = if pid == 0x0001 { 0x10 } else if pid == 0x0060 { 0x20 } else { 0x10 };
|
||||||
|
f[0x50] = (pid >> 8) as u8;
|
||||||
|
f[0x51] = (pid & 0xFF) as u8;
|
||||||
|
f[0x52] = pd.len() as u8;
|
||||||
|
f[0x53..0x53 + pd.len()].copy_from_slice(&pd);
|
||||||
|
let mut sum: u16 = 0xCC;
|
||||||
|
for i in 0x3C..0x3C + msg_len { sum = sum.wrapping_add(f[i] as u16); }
|
||||||
|
let cs = 0x53 + pd.len();
|
||||||
|
f[cs] = (sum >> 8) as u8;
|
||||||
|
f[cs + 1] = (sum & 0xFF) as u8;
|
||||||
|
let mut f2 = f.clone();
|
||||||
|
f2[0x30] = 0x01;
|
||||||
|
f2[0x49] = f2[0x49].wrapping_sub(1);
|
||||||
|
let _ = sock.send_to(&f, &dst);
|
||||||
|
let _ = sock.send_to(&f2, &dst);
|
||||||
|
println!("[SLS] RDM step={} pid={:04x} 已发 (ctr={})", step, pid, ctr);
|
||||||
|
step += 1;
|
||||||
|
}
|
||||||
|
// 收包
|
||||||
|
while let Ok((n, src)) = sock.recv_from(&mut buf) {
|
||||||
|
if src.ip().to_string() != target { continue; }
|
||||||
|
let b = &buf[..n];
|
||||||
|
resp_n += 1;
|
||||||
|
if let Some((is_pulse, d)) = rdm_region(b) {
|
||||||
|
if is_pulse {
|
||||||
|
if let Some(uid) = decode_pulse(d) {
|
||||||
|
println!(" [SLS] ★脉冲 UID={}", uid_s(&uid));
|
||||||
|
} else {
|
||||||
|
println!(" [SLS] 脉冲(冲突/损坏)");
|
||||||
|
}
|
||||||
|
} else if d.len() >= 0x18 + 19 && d[0] == 0xCC && d[0x15..0x17] == [0x00, 0x60] {
|
||||||
|
let a = u16::from_be_bytes([d[0x18 + 14], d[0x18 + 15]]);
|
||||||
|
let fp = u16::from_be_bytes([d[0x18 + 10], d[0x18 + 11]]);
|
||||||
|
println!(" [SLS] ★DEVICE_INFO srcUID={} DMX地址={} 通道={}", uid_s(&[d[9], d[10], d[11], d[12], d[13], d[14]]), a, fp);
|
||||||
|
if !addr_list.contains(&a) { addr_list.push(a); }
|
||||||
|
} else {
|
||||||
|
println!(" [SLS] RDM应答 len={}", d.len());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// DMX @30fps: 发现完成前黑场, 之后呼吸
|
||||||
|
let mut p1 = [0u8; 512];
|
||||||
|
if !addr_list.is_empty() {
|
||||||
|
for (k, &a) in addr_list.iter().enumerate() {
|
||||||
|
let phase = t * 1.2 + k as f32 * 2.1;
|
||||||
|
let v = (((phase.sin() * 0.5 + 0.5) * 255.0) * ((t - 1.0) * 0.5).min(1.0).max(0.0)) as u8;
|
||||||
|
let ch = a.max(1) - 1;
|
||||||
|
if ch < 512 { p1[ch as usize] = v; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let r1 = rng.next();
|
||||||
|
let r2 = rng.next();
|
||||||
|
let mut f = dmx_t.clone();
|
||||||
|
f[0x1A..0x1E].copy_from_slice(&ctr.to_le_bytes());
|
||||||
|
f[0x22..0x26].copy_from_slice(&r1.to_le_bytes());
|
||||||
|
f[0x26..0x2A].copy_from_slice(&r2.to_le_bytes());
|
||||||
|
f[0x3C..0x23C].copy_from_slice(&agios_dmx_encrypt(&p1, r1, sb12));
|
||||||
|
f[0x23C..0x43C].copy_from_slice(&agios_dmx_encrypt(&[0u8; 512], r2, sb12));
|
||||||
|
let _ = sock.send_to(&f, &dst);
|
||||||
|
ctr += 1;
|
||||||
|
std::thread::sleep(Duration::from_millis(33));
|
||||||
|
}
|
||||||
|
println!("[SLS] 结束: 应答 {} 帧, 控制地址 {:?}", resp_n, addr_list);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hybrid: 认领 + verbatim SL DMX 帧 + 我们构造的 RDM 帧 — 二分定位毒帧
|
||||||
|
if std::env::var("HOSTDEMO_MODE").unwrap_or_default() == "hybrid" {
|
||||||
|
let path = std::env::var("REPLAY_FILE")
|
||||||
|
.unwrap_or_else(|_| "C:/Users/tonycao/AppData/Local/Temp/opencode/replay_frames.txt".into());
|
||||||
|
let data = std::fs::read_to_string(&path).expect("replay file");
|
||||||
|
let mut dmxf: Vec<Vec<u8>> = Vec::new(); // verbatim DMX 帧
|
||||||
|
let mut base_rdm: Option<Vec<u8>> = None;
|
||||||
|
for line in data.lines() {
|
||||||
|
let p: Vec<&str> = line.split('|').collect();
|
||||||
|
if p.len() < 4 { continue; }
|
||||||
|
let hex = p[3];
|
||||||
|
if hex.len() != 1084 * 2 { continue; }
|
||||||
|
let b = |o: usize| u8::from_str_radix(&hex[o * 2..o * 2 + 2], 16).unwrap();
|
||||||
|
if b(0x18) == 1 && b(0x20) == 1 && b(0x21) == 1 && b(0x3c) != 1 {
|
||||||
|
let mut fb = Vec::new();
|
||||||
|
for i in (0..hex.len()).step_by(2) { fb.push(u8::from_str_radix(&hex[i..i + 2], 16).unwrap()); }
|
||||||
|
dmxf.push(fb);
|
||||||
|
}
|
||||||
|
if base_rdm.is_none() && b(0x3c) == 1 && b(0x30) == 0 && b(0x50) == 0 && b(0x51) == 1 {
|
||||||
|
let mut fb = Vec::new();
|
||||||
|
for i in (0..hex.len()).step_by(2) { fb.push(u8::from_str_radix(&hex[i..i + 2], 16).unwrap()); }
|
||||||
|
base_rdm = Some(fb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 认领 (announce144 → AECH)
|
||||||
|
let o2: Vec<u8> = bind_ip.split('.').map(|x| x.parse().unwrap()).collect();
|
||||||
|
sock.send_to(&load_template("announce144"), &dst).unwrap();
|
||||||
|
std::thread::sleep(Duration::from_millis(300));
|
||||||
|
let nonce = rng.next();
|
||||||
|
let bc = rng.next();
|
||||||
|
let mut ch = load_template("claim_h112");
|
||||||
|
ch[0x1A..0x1E].copy_from_slice(&[o2[3], o2[2], o2[1], o2[0]]);
|
||||||
|
ch[0x88..0x8C].copy_from_slice(&nonce.to_le_bytes());
|
||||||
|
ch[0x8C..0x90].copy_from_slice(&bc.to_le_bytes());
|
||||||
|
sock.send_to(&ch, &dst).unwrap();
|
||||||
|
std::thread::sleep(Duration::from_millis(400));
|
||||||
|
println!("[H] 认领完成 B12=0x{:08x}; verbatim DMX {} 帧 + 我们的 RDM", bc, dmxf.len());
|
||||||
|
let rdm_t = base_rdm.expect("no RDM template");
|
||||||
|
let known: [[u8; 6]; 3] = [
|
||||||
|
[0x01, 0x07, 0x34, 0x67, 0x01, 0x25],
|
||||||
|
[0x01, 0x07, 0x34, 0x88, 0x09, 0x00],
|
||||||
|
[0x08, 0x99, 0x50, 0x00, 0x26, 0xc9],
|
||||||
|
];
|
||||||
|
let start = Instant::now();
|
||||||
|
let mut di = 0usize;
|
||||||
|
let mut last_rdm = Instant::now() - Duration::from_secs(10);
|
||||||
|
let mut step = 0usize;
|
||||||
|
let mut addr_list: Vec<u16> = Vec::new();
|
||||||
|
let mut resp_n = 0u64;
|
||||||
|
while start.elapsed() < Duration::from_secs(secs.max(90)) {
|
||||||
|
let t = start.elapsed().as_secs_f32();
|
||||||
|
// verbatim DMX 循环
|
||||||
|
let f = &dmxf[di % dmxf.len()];
|
||||||
|
di += 1;
|
||||||
|
let _ = sock.send_to(f, &dst);
|
||||||
|
if di % 30 == 0 { let _ = sock.send_to(&load_template("heartbeat"), &dst); }
|
||||||
|
// RDM 任务
|
||||||
|
if t > 2.0 && last_rdm.elapsed() >= Duration::from_millis(700) {
|
||||||
|
last_rdm = Instant::now();
|
||||||
|
let mut f = rdm_t.clone();
|
||||||
|
let mut pd: Vec<u8> = Vec::new();
|
||||||
|
let mut pid: u16 = 0x0001;
|
||||||
|
let mut dest = [0xffu8; 6];
|
||||||
|
let mut cc = 0x10u8;
|
||||||
|
match step {
|
||||||
|
0 => { pid = 0x0003; }
|
||||||
|
1..=3 => {
|
||||||
|
let v = uid_u64(&known[step - 1]);
|
||||||
|
pd.extend_from_slice(&u64_uid(v - 1));
|
||||||
|
pd.extend_from_slice(&u64_uid(v + 1));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if (step - 4) < known.len() { dest = known[step - 4]; pid = 0x0060; cc = 0x20; }
|
||||||
|
else { step = 3; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let msg_len = 24 + pd.len();
|
||||||
|
f[0x1A..0x1E].copy_from_slice(&(di as u32).to_le_bytes());
|
||||||
|
f[0x3D] = msg_len as u8;
|
||||||
|
f[0x3E..0x44].copy_from_slice(&dest);
|
||||||
|
f[0x4A] = (step * 7 + 1) as u8;
|
||||||
|
f[0x4F] = cc;
|
||||||
|
f[0x50] = (pid >> 8) as u8;
|
||||||
|
f[0x51] = (pid & 0xFF) as u8;
|
||||||
|
f[0x52] = pd.len() as u8;
|
||||||
|
f[0x53..0x53 + pd.len()].copy_from_slice(&pd);
|
||||||
|
let mut sum: u16 = 0xCC;
|
||||||
|
for i in 0x3C..0x3C + msg_len { sum = sum.wrapping_add(f[i] as u16); }
|
||||||
|
let cs = 0x53 + pd.len();
|
||||||
|
f[cs] = (sum >> 8) as u8;
|
||||||
|
f[cs + 1] = (sum & 0xFF) as u8;
|
||||||
|
let mut f2 = f.clone();
|
||||||
|
f2[0x30] = 0x01;
|
||||||
|
f2[0x49] = f2[0x49].wrapping_sub(1);
|
||||||
|
let _ = sock.send_to(&f, &dst);
|
||||||
|
let _ = sock.send_to(&f2, &dst);
|
||||||
|
println!("[H] RDM step={} pid={:04x}", step, pid);
|
||||||
|
step += 1;
|
||||||
|
}
|
||||||
|
while let Ok((n, src)) = sock.recv_from(&mut buf) {
|
||||||
|
if src.ip().to_string() != target { continue; }
|
||||||
|
let b = &buf[..n];
|
||||||
|
resp_n += 1;
|
||||||
|
if let Some((is_pulse, d)) = rdm_region(b) {
|
||||||
|
if is_pulse {
|
||||||
|
if let Some(uid) = decode_pulse(d) { println!(" [H] ★脉冲 UID={}", uid_s(&uid)); }
|
||||||
|
else { println!(" [H] 脉冲(冲突)"); }
|
||||||
|
} else if d.len() >= 0x18 + 19 && d[0] == 0xCC && d[0x15..0x17] == [0x00, 0x60] {
|
||||||
|
let a = u16::from_be_bytes([d[0x18 + 14], d[0x18 + 15]]);
|
||||||
|
let fp = u16::from_be_bytes([d[0x18 + 10], d[0x18 + 11]]);
|
||||||
|
println!(" [H] ★DEVICE_INFO srcUID={} 地址={} 通道={}", uid_s(&[d[9], d[10], d[11], d[12], d[13], d[14]]), a, fp);
|
||||||
|
if !addr_list.contains(&a) { addr_list.push(a); }
|
||||||
|
} else {
|
||||||
|
println!(" [H] RDM应答 len={}", d.len());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::thread::sleep(Duration::from_millis(33));
|
||||||
|
}
|
||||||
|
println!("[H] 结束: 应答 {} 帧, 地址 {:?}", resp_n, addr_list);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// slfull: 认领 + verbatim 重放 SL 会话(循环) + 被动解码 RDM 应答 → 发现灯具并打印地址
|
||||||
|
// (绕过未破解的变长 RDM 帧结构: 用 SL 原帧当查询, 我们解码应答)
|
||||||
|
if std::env::var("HOSTDEMO_MODE").unwrap_or_default() == "slfull" {
|
||||||
|
let path = std::env::var("REPLAY_FILE")
|
||||||
|
.unwrap_or_else(|_| "C:/Users/tonycao/AppData/Local/Temp/opencode/replay_frames.txt".into());
|
||||||
|
let data = std::fs::read_to_string(&path).expect("replay file");
|
||||||
|
let mut frames: Vec<(f32, String, u16, Vec<u8>)> = Vec::new();
|
||||||
|
for line in data.lines() {
|
||||||
|
let p: Vec<&str> = line.split('|').collect();
|
||||||
|
if p.len() < 4 { continue; }
|
||||||
|
let t: f32 = p[0].parse().unwrap();
|
||||||
|
let dst = p[1].to_string();
|
||||||
|
let port: u16 = p[2].parse().unwrap();
|
||||||
|
let hex = p[3];
|
||||||
|
let mut b = Vec::new();
|
||||||
|
for i in (0..hex.len()).step_by(2) { b.push(u8::from_str_radix(&hex[i..i + 2], 16).unwrap()); }
|
||||||
|
frames.push((t, dst, port, b));
|
||||||
|
}
|
||||||
|
let t0 = frames[0].0;
|
||||||
|
let dur = frames[frames.len() - 1].0 - t0;
|
||||||
|
// 认领
|
||||||
|
let o2: Vec<u8> = bind_ip.split('.').map(|x| x.parse().unwrap()).collect();
|
||||||
|
sock.send_to(&load_template("announce144"), &dst).unwrap();
|
||||||
|
std::thread::sleep(Duration::from_millis(300));
|
||||||
|
let nonce = rng.next();
|
||||||
|
let bc = rng.next();
|
||||||
|
let mut ch = load_template("claim_h112");
|
||||||
|
ch[0x1A..0x1E].copy_from_slice(&[o2[3], o2[2], o2[1], o2[0]]);
|
||||||
|
ch[0x88..0x8C].copy_from_slice(&nonce.to_le_bytes());
|
||||||
|
ch[0x8C..0x90].copy_from_slice(&bc.to_le_bytes());
|
||||||
|
sock.send_to(&ch, &dst).unwrap();
|
||||||
|
std::thread::sleep(Duration::from_millis(500));
|
||||||
|
println!("[F] 认领完成; 开始循环重放 {} 帧 ({:.1}s/轮), 被动解码 RDM 应答", frames.len(), dur);
|
||||||
|
let start = Instant::now();
|
||||||
|
let mut seen_uids: Vec<[u8; 6]> = Vec::new();
|
||||||
|
let mut addr_map: Vec<([u8; 6], u16)> = Vec::new();
|
||||||
|
let mut resp_n = 0u64;
|
||||||
|
let mut pat_ctr: u32 = 0;
|
||||||
|
while start.elapsed() < Duration::from_secs(secs.max(120)) {
|
||||||
|
let play_start = Instant::now();
|
||||||
|
for (t, d, port, b) in frames.iter() {
|
||||||
|
let target_t = Duration::from_secs_f32(t - t0);
|
||||||
|
let now = play_start.elapsed();
|
||||||
|
if target_t > now { std::thread::sleep(target_t - now); }
|
||||||
|
let addr = format!("{}:{}", d, port);
|
||||||
|
// ★DMX 数据帧: 替换为我们自己的呼吸图案 (用认领 B12 加密 → Butler 解密后上灯)
|
||||||
|
let mut fb = b.clone();
|
||||||
|
if fb.len() == 1084 && fb[0x18] == 1 && fb[0x20] == 1 && fb[0x21] == 1 && fb[0x3C] != 1 {
|
||||||
|
pat_ctr += 1;
|
||||||
|
let tt = pat_ctr as f32 / 30.0;
|
||||||
|
let mut p1 = [0u8; 512];
|
||||||
|
for (k, &a) in [101u16, 123, 1].iter().enumerate() {
|
||||||
|
let phase = tt * 1.0 + k as f32 * 2.1;
|
||||||
|
let v = (((phase.sin() * 0.5 + 0.5) * 220.0) * (tt * 0.3).min(1.0)) as u8;
|
||||||
|
p1[(a - 1) as usize] = v;
|
||||||
|
}
|
||||||
|
let r1 = rng.next();
|
||||||
|
let r2 = rng.next();
|
||||||
|
fb[0x22..0x26].copy_from_slice(&r1.to_le_bytes());
|
||||||
|
fb[0x26..0x2A].copy_from_slice(&r2.to_le_bytes());
|
||||||
|
fb[0x3C..0x23C].copy_from_slice(&agios_dmx_encrypt(&p1, r1, bc));
|
||||||
|
fb[0x23C..0x43C].copy_from_slice(&agios_dmx_encrypt(&[0u8; 512], r2, bc));
|
||||||
|
}
|
||||||
|
let _ = sock.send_to(&fb, &addr);
|
||||||
|
// 收包
|
||||||
|
while let Ok((n, src)) = sock.recv_from(&mut buf) {
|
||||||
|
if src.ip().to_string() != target { continue; }
|
||||||
|
let bb = &buf[..n];
|
||||||
|
resp_n += 1;
|
||||||
|
if let Some((is_pulse, dd)) = rdm_region(bb) {
|
||||||
|
if is_pulse {
|
||||||
|
if let Some(uid) = decode_pulse(dd) {
|
||||||
|
if !seen_uids.contains(&uid) {
|
||||||
|
seen_uids.push(uid);
|
||||||
|
println!(" [F] ★发现灯具 UID={}", uid_s(&uid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if dd.len() >= 0x18 + 19 && dd[0] == 0xCC && dd[0x15..0x17] == [0x00, 0x60] {
|
||||||
|
let src_uid = [dd[9], dd[10], dd[11], dd[12], dd[13], dd[14]];
|
||||||
|
let a = u16::from_be_bytes([dd[0x18 + 14], dd[0x18 + 15]]);
|
||||||
|
let fp = u16::from_be_bytes([dd[0x18 + 10], dd[0x18 + 11]]);
|
||||||
|
if !addr_map.iter().any(|(u, _)| *u == src_uid) {
|
||||||
|
addr_map.push((src_uid, a));
|
||||||
|
println!(" [F] ★{} → DMX地址 {} ({}通道)", uid_s(&src_uid), a, fp);
|
||||||
|
}
|
||||||
|
} else if dd.len() >= 0x18 + 2 && dd[0] == 0xCC && dd[0x15..0x17] == [0x00, 0xF0] {
|
||||||
|
let src_uid = [dd[9], dd[10], dd[11], dd[12], dd[13], dd[14]];
|
||||||
|
let a = u16::from_be_bytes([dd[0x18], dd[0x19]]);
|
||||||
|
if !addr_map.iter().any(|(u, _)| *u == src_uid) {
|
||||||
|
addr_map.push((src_uid, a));
|
||||||
|
println!(" [F] ★{} → DMX_START_ADDRESS={}", uid_s(&src_uid), a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("[F] 结束: 应答 {} 帧; 灯具 {} 台: {:?}; 地址: {:?}", resp_n, seen_uids.len(),
|
||||||
|
seen_uids.iter().map(uid_s).collect::<Vec<_>>(), addr_map);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let mut b12 = rng.next();
|
let mut b12 = rng.next();
|
||||||
let mut claim_ok = false;
|
let mut claim_ok = false;
|
||||||
|
|
||||||
|
|
@ -723,8 +1159,12 @@ fn session_loop(sock: &UdpSocket, dst: &str, target: &str, rng: &mut Rng, b12: u
|
||||||
let mut tn: u8 = rng.byte();
|
let mut tn: u8 = rng.byte();
|
||||||
let mut qseq: u16 = 1;
|
let mut qseq: u16 = 1;
|
||||||
let mut seq: u32 = 1;
|
let mut seq: u32 = 1;
|
||||||
// 发现任务队列: (dest, cc, pid, pd) — None = 分支查询由 walk 状态机生成
|
// 发现任务队列: ★窄分支优先 (已知灯具各一个只含自己的分支 — Butler 可能拒转全范围查询)
|
||||||
let mut stack: Vec<(u64, u64)> = vec![(0, 0xFFFF_FFFF_FFFF)];
|
let mut stack: Vec<(u64, u64)> = vec![(0, 0xFFFF_FFFF_FFFF)];
|
||||||
|
for u in known.iter() {
|
||||||
|
let v = uid_u64(u);
|
||||||
|
stack.push((v - 1, v + 1));
|
||||||
|
}
|
||||||
let mut get_queue: Vec<[u8; 6]> = Vec::new();
|
let mut get_queue: Vec<[u8; 6]> = Vec::new();
|
||||||
let mut muted: Vec<[u8; 6]> = Vec::new();
|
let mut muted: Vec<[u8; 6]> = Vec::new();
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
@ -757,8 +1197,8 @@ fn session_loop(sock: &UdpSocket, dst: &str, target: &str, rng: &mut Rng, b12: u
|
||||||
last_key_t = t;
|
last_key_t = t;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- RDM 任务 (每 ~100ms 一发; ★SL 实测: 认领后 27.5s 纯 DMX, RDM 过早发可能重置武装计时) ---
|
// --- RDM 任务 (每 ~100ms 一发; 窄分支优先, t>3s 即开始快速定位问题) ---
|
||||||
if discovering && t > 30.0 && last_rdm.elapsed() >= Duration::from_millis(100) {
|
if discovering && t > 3.0 && last_rdm.elapsed() >= Duration::from_millis(100) {
|
||||||
last_rdm = Instant::now();
|
last_rdm = Instant::now();
|
||||||
if collecting && Instant::now() >= collect_until {
|
if collecting && Instant::now() >= collect_until {
|
||||||
// 本分支收集结束 → 判定
|
// 本分支收集结束 → 判定
|
||||||
|
|
@ -804,6 +1244,7 @@ fn session_loop(sock: &UdpSocket, dst: &str, target: &str, rng: &mut Rng, b12: u
|
||||||
let _ = sock.send_to(&f, dst);
|
let _ = sock.send_to(&f, dst);
|
||||||
let mut f2 = f.clone();
|
let mut f2 = f.clone();
|
||||||
f2[0x30] = 0x01;
|
f2[0x30] = 0x01;
|
||||||
|
f2[0x49] = f2[0x49].wrapping_sub(1);
|
||||||
let _ = sock.send_to(&f2, dst);
|
let _ = sock.send_to(&f2, dst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -830,6 +1271,8 @@ fn session_loop(sock: &UdpSocket, dst: &str, target: &str, rng: &mut Rng, b12: u
|
||||||
let _ = sock.send_to(&f, dst);
|
let _ = sock.send_to(&f, dst);
|
||||||
let mut f2 = f.clone();
|
let mut f2 = f.clone();
|
||||||
f2[0x30] = 0x01;
|
f2[0x30] = 0x01;
|
||||||
|
// flag=01 副本的 srcUID 末字节 = fe (SL 实测: ff/fe 成对)
|
||||||
|
f2[0x49] = f2[0x49].wrapping_sub(1);
|
||||||
let _ = sock.send_to(&f2, dst);
|
let _ = sock.send_to(&f2, dst);
|
||||||
current_branch = Some((lo, hi));
|
current_branch = Some((lo, hi));
|
||||||
collecting = true;
|
collecting = true;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user