hostdemo: full parameter sweep + periodic RDM status polling in session_loop
After zero-pre-baked discovery, session_loop now sweeps each fixture (DEVICE_INFO with E1.20 PD decode + raw hex, DMX_START_ADDRESS, DEVICE_LABEL / MODEL_DESCRIPTION / SOFTWARE_VERSION / MANUFACTURER_LABEL) with a 2-round retry for the flaky RDM window arm, prints a per-fixture parameter summary, then enters a 6s status-poll cycle: one compact line per fixture per cycle, address/footprint changes reported as alarms, and playback follows a changed DMX address automatically. Duplicate uplinks from the flag=00/01 pair copies are deduped; the playback address list is kept per-UID (no more duplicate control-address entries).
This commit is contained in:
parent
ac6ea29db9
commit
0a183e449c
163
src/hostdemo.rs
163
src/hostdemo.rs
|
|
@ -1691,7 +1691,6 @@ fn session_loop(sock: &UdpSocket, dst: &str, target: &str, rng: &mut Rng, b12: u
|
||||||
];
|
];
|
||||||
let mut buf = [0u8; 4096];
|
let mut buf = [0u8; 4096];
|
||||||
let mut fixtures: Vec<[u8; 6]> = Vec::new();
|
let mut fixtures: Vec<[u8; 6]> = Vec::new();
|
||||||
let mut addr_list: Vec<u16> = Vec::new();
|
|
||||||
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;
|
||||||
|
|
@ -1701,7 +1700,33 @@ fn session_loop(sock: &UdpSocket, dst: &str, target: &str, rng: &mut Rng, b12: u
|
||||||
let v = uid_u64(u);
|
let v = uid_u64(u);
|
||||||
stack.push((v - 1, v + 1));
|
stack.push((v - 1, v + 1));
|
||||||
}
|
}
|
||||||
let mut get_queue: Vec<[u8; 6]> = Vec::new();
|
/// 一台灯具的参数快照 (参数扫描汇总用)
|
||||||
|
struct DevInfo {
|
||||||
|
uid: [u8; 6],
|
||||||
|
addr: u16,
|
||||||
|
footprint: u16,
|
||||||
|
proto_major: u8,
|
||||||
|
proto_minor: u8,
|
||||||
|
model: u16,
|
||||||
|
sw: [u8; 4],
|
||||||
|
sensors: u16,
|
||||||
|
label: Option<String>,
|
||||||
|
model_desc: Option<String>,
|
||||||
|
sw_label: Option<String>,
|
||||||
|
manufacturer: Option<String>,
|
||||||
|
}
|
||||||
|
let mut get_queue: Vec<([u8; 6], u16)> = Vec::new();
|
||||||
|
let mut devinfo: Vec<DevInfo> = Vec::new();
|
||||||
|
let mut param_sweep_started = false;
|
||||||
|
let mut param_summary_done = false;
|
||||||
|
let mut sweep_round: u32 = 0;
|
||||||
|
let mut answered: Vec<([u8; 6], u16)> = Vec::new();
|
||||||
|
let mut last_sweep_rx = Instant::now();
|
||||||
|
let mut addr_by_uid: Vec<([u8; 6], u16)> = Vec::new();
|
||||||
|
let mut last_reply: Option<([u8; 6], u16, Instant)> = None;
|
||||||
|
let mut status_poll_count: u32 = 0;
|
||||||
|
let mut next_status_poll = Instant::now();
|
||||||
|
const STATUS_POLL_INTERVAL: Duration = Duration::from_secs(6);
|
||||||
let mut muted: Vec<[u8; 6]> = Vec::new();
|
let mut muted: Vec<[u8; 6]> = Vec::new();
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let mut last_hb = Instant::now();
|
let mut last_hb = Instant::now();
|
||||||
|
|
@ -1749,7 +1774,7 @@ fn session_loop(sock: &UdpSocket, dst: &str, target: &str, rng: &mut Rng, b12: u
|
||||||
let uid = found[0];
|
let uid = found[0];
|
||||||
println!(" [S] ★发现灯具 {}", uid_s(&uid));
|
println!(" [S] ★发现灯具 {}", uid_s(&uid));
|
||||||
if !fixtures.contains(&uid) { fixtures.push(uid); }
|
if !fixtures.contains(&uid) { fixtures.push(uid); }
|
||||||
get_queue.push(uid);
|
// 参数扫描队列在发现完成后按 PID 填充 (见下)
|
||||||
// DISC_MUTE 该灯具
|
// DISC_MUTE 该灯具
|
||||||
tn = tn.wrapping_add(1);
|
tn = tn.wrapping_add(1);
|
||||||
let f = build_rdm_frame(&uid, tn, qseq, 0x10, 0x0002, &[]);
|
let f = build_rdm_frame(&uid, tn, qseq, 0x10, 0x0002, &[]);
|
||||||
|
|
@ -1836,34 +1861,145 @@ fn session_loop(sock: &UdpSocket, dst: &str, target: &str, rng: &mut Rng, b12: u
|
||||||
None => collision = true,
|
None => collision = true,
|
||||||
}
|
}
|
||||||
} else if !is_pulse && d.len() >= 0x18 + 19 && d[0] == 0xCC && d[0x15..0x17] == [0x00, 0x60] {
|
} else if !is_pulse && d.len() >= 0x18 + 19 && d[0] == 0xCC && d[0x15..0x17] == [0x00, 0x60] {
|
||||||
|
let ruid = [d[9], d[10], d[11], d[12], d[13], d[14]];
|
||||||
let addr = u16::from_be_bytes([d[0x18 + 14], d[0x18 + 15]]);
|
let addr = u16::from_be_bytes([d[0x18 + 14], d[0x18 + 15]]);
|
||||||
let footprint = u16::from_be_bytes([d[0x18 + 10], d[0x18 + 11]]);
|
let footprint = u16::from_be_bytes([d[0x18 + 10], d[0x18 + 11]]);
|
||||||
println!(" [S] ★{} → DMX地址 {} / {}通道", uid_s(&[d[9], d[10], d[11], d[12], d[13], d[14]]), addr, footprint);
|
// 成对副本去重 (flag=00/01 各上行一份, 背靠背到达)
|
||||||
addr_list.push(addr);
|
if let Some((lu, lp, lt)) = last_reply {
|
||||||
|
if lu == ruid && lp == 0x0060 && lt.elapsed() < Duration::from_millis(200) { continue; }
|
||||||
|
}
|
||||||
|
last_reply = Some((ruid, 0x0060, Instant::now()));
|
||||||
|
if !answered.contains(&(ruid, 0x0060)) { answered.push((ruid, 0x0060)); }
|
||||||
|
last_sweep_rx = Instant::now();
|
||||||
|
if let Some(e) = devinfo.iter_mut().find(|x| x.uid == ruid) {
|
||||||
|
if e.addr != addr { println!(" [S] ★{} DMX地址变更: {} → {}", uid_s(&ruid), e.addr, addr); }
|
||||||
|
if e.footprint != footprint { println!(" [S] ★{} 通道数变更: {} → {}", uid_s(&ruid), e.footprint, footprint); }
|
||||||
|
e.addr = addr; e.footprint = footprint;
|
||||||
|
}
|
||||||
|
if let Some(ae) = addr_by_uid.iter_mut().find(|(u, _)| *u == ruid) { ae.1 = addr; }
|
||||||
|
else { addr_by_uid.push((ruid, addr)); }
|
||||||
|
if param_summary_done {
|
||||||
|
// 周期状态轮询: 每台一行紧凑状态
|
||||||
|
println!(" [S] 轮询#{} {} @DMX{} {}ch", status_poll_count, uid_s(&ruid), addr, footprint);
|
||||||
|
} else {
|
||||||
|
// E1.20 §10.4.1 DEVICE_INFO PD: [0]协议主 [1]协议次 [2..4]型号 [4..6]产品类
|
||||||
|
// [6..10]软件版 [10..12]footprint [12]personality [13..15]子设备数 [15..17]传感器数
|
||||||
|
let pmaj = d[0x18]; let pmin = d[0x18 + 1];
|
||||||
|
let model = u16::from_be_bytes([d[0x18 + 2], d[0x18 + 3]]);
|
||||||
|
let pers = d[0x18 + 12];
|
||||||
|
let subdev = u16::from_be_bytes([d[0x18 + 13], d[0x18 + 14]]);
|
||||||
|
let sensors = u16::from_be_bytes([d[0x18 + 15], d[0x18 + 16]]);
|
||||||
|
println!(" [S] ★{} DEVICE_INFO: 协议{}.{}/模型{:04x}/固件{:02x}{:02x}{:02x}{:02x} footprint={} personality=0x{:02x} 子设备={} 传感器={}",
|
||||||
|
uid_s(&ruid), pmaj, pmin, model, d[0x18+6], d[0x18+7], d[0x18+8], d[0x18+9],
|
||||||
|
footprint, pers, subdev, sensors);
|
||||||
|
println!(" [S] PD({:02x}): {} @addr={}", d.len() - 0x18, hexs(&d[0x18..]), addr);
|
||||||
|
let sw = [d[0x18 + 6], d[0x18 + 7], d[0x18 + 8], d[0x18 + 9]];
|
||||||
|
if let Some(e) = devinfo.iter_mut().find(|x| x.uid == ruid) {
|
||||||
|
e.proto_major = pmaj; e.proto_minor = pmin; e.model = model; e.sw = sw; e.sensors = sensors;
|
||||||
|
} else {
|
||||||
|
devinfo.push(DevInfo { uid: ruid, addr, footprint, proto_major: pmaj, proto_minor: pmin,
|
||||||
|
model, sw, sensors, label: None, model_desc: None, sw_label: None, manufacturer: None });
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if !is_pulse && d.len() >= 0x18 + 2 && d[0] == 0xCC && d[0x15..0x17] == [0x00, 0xF0] {
|
} else if !is_pulse && d.len() >= 0x18 + 2 && d[0] == 0xCC && d[0x15..0x17] == [0x00, 0xF0] {
|
||||||
|
let ruid = [d[9], d[10], d[11], d[12], d[13], d[14]];
|
||||||
let addr = u16::from_be_bytes([d[0x18], d[0x19]]);
|
let addr = u16::from_be_bytes([d[0x18], d[0x19]]);
|
||||||
println!(" [S] ★DMX_START_ADDRESS 应答: {}", addr);
|
if let Some((lu, lp, lt)) = last_reply {
|
||||||
addr_list.push(addr);
|
if lu == ruid && lp == 0x00F0 && lt.elapsed() < Duration::from_millis(200) { continue; }
|
||||||
|
}
|
||||||
|
last_reply = Some((ruid, 0x00F0, Instant::now()));
|
||||||
|
if !answered.contains(&(ruid, 0x00F0)) { answered.push((ruid, 0x00F0)); }
|
||||||
|
last_sweep_rx = Instant::now();
|
||||||
|
println!(" [S] ★{} DMX_START_ADDRESS = {}", uid_s(&ruid), addr);
|
||||||
|
if let Some(e) = devinfo.iter_mut().find(|x| x.uid == ruid) { e.addr = addr; }
|
||||||
|
if let Some(ae) = addr_by_uid.iter_mut().find(|(u, _)| *u == ruid) { ae.1 = addr; }
|
||||||
|
else { addr_by_uid.push((ruid, addr)); }
|
||||||
|
} else if !is_pulse && d.len() >= 0x18 && d[0] == 0xCC
|
||||||
|
&& matches!(g16(d, 0x15), 0x0081 | 0x0082 | 0x00C0 | 0x0080) {
|
||||||
|
let ruid = [d[9], d[10], d[11], d[12], d[13], d[14]];
|
||||||
|
let pid = g16(d, 0x15);
|
||||||
|
if let Some((lu, lp, lt)) = last_reply {
|
||||||
|
if lu == ruid && lp == pid && lt.elapsed() < Duration::from_millis(200) { continue; }
|
||||||
|
}
|
||||||
|
last_reply = Some((ruid, pid, Instant::now()));
|
||||||
|
if !answered.contains(&(ruid, pid)) { answered.push((ruid, pid)); }
|
||||||
|
last_sweep_rx = Instant::now();
|
||||||
|
let (name, field) = match pid {
|
||||||
|
0x0081 => ("DEVICE_LABEL", 0usize),
|
||||||
|
0x0082 => ("MODEL_DESCRIPTION", 1usize),
|
||||||
|
0x00C0 => ("SOFTWARE_VERSION", 2usize),
|
||||||
|
_ => ("MANUFACTURER_LABEL", 3usize),
|
||||||
|
};
|
||||||
|
if d.len() > 0x18 {
|
||||||
|
let txt: String = d[0x18..].iter().map(|&c| if (0x20..0x7f).contains(&c) { c as char } else { '.' }).collect();
|
||||||
|
println!(" [S] ★{} {} = '{}'", uid_s(&ruid), name, txt);
|
||||||
|
if let Some(e) = devinfo.iter_mut().find(|x| x.uid == ruid) {
|
||||||
|
let owned = txt.trim_end_matches('.').to_string();
|
||||||
|
match field { 0 => e.label = Some(owned), 1 => e.model_desc = Some(owned),
|
||||||
|
2 => e.sw_label = Some(owned), _ => e.manufacturer = Some(owned) }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!(" [S] ★{} {} = (无 PD, PDL=0x{:02x})", uid_s(&ruid), name, d[0x17]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- GET 队列 (发现后逐台读参数) ---
|
// --- 参数扫描 (发现完成后, 带 2 轮重试) + 周期 RDM 状态轮询 ---
|
||||||
|
const SWEEP_PIDS: [u16; 6] = [0x0060, 0x00F0, 0x0081, 0x0082, 0x00C0, 0x0080];
|
||||||
|
if !discovering && get_queue.is_empty() && !fixtures.is_empty() && !param_sweep_started {
|
||||||
|
param_sweep_started = true;
|
||||||
|
for uid in fixtures.iter() {
|
||||||
|
for pid in SWEEP_PIDS { get_queue.push((*uid, pid)); }
|
||||||
|
}
|
||||||
|
println!("[S] 参数扫描开始: {} 台 × {} PID", fixtures.len(), SWEEP_PIDS.len());
|
||||||
|
}
|
||||||
if !discovering && !get_queue.is_empty() && last_rdm.elapsed() >= Duration::from_millis(120) {
|
if !discovering && !get_queue.is_empty() && last_rdm.elapsed() >= Duration::from_millis(120) {
|
||||||
last_rdm = Instant::now();
|
last_rdm = Instant::now();
|
||||||
let uid = get_queue.remove(0);
|
let (uid, pid) = get_queue.remove(0);
|
||||||
tn = tn.wrapping_add(1);
|
tn = tn.wrapping_add(1);
|
||||||
let f = build_rdm_frame(&uid, tn, qseq, 0x20, 0x0060, &[]);
|
let f = build_rdm_frame(&uid, tn, qseq, 0x20, pid, &[]);
|
||||||
qseq += 1;
|
qseq += 1;
|
||||||
let _ = sock.send_to(&{ let mut f = f; f[0x1A..0x1E].copy_from_slice(&seq.to_le_bytes()); seq += 1; f }, dst);
|
let _ = sock.send_to(&{ let mut f = f; f[0x1A..0x1E].copy_from_slice(&seq.to_le_bytes()); seq += 1; f }, dst);
|
||||||
let _ = muted;
|
let _ = muted;
|
||||||
|
} else if param_sweep_started && get_queue.is_empty() {
|
||||||
|
// 队列排空: 要么重试未应答项, 要么出汇总, 要么进入周期状态轮询
|
||||||
|
let unanswered: Vec<([u8; 6], u16)> = fixtures.iter()
|
||||||
|
.flat_map(|u| SWEEP_PIDS.iter().map(move |p| (*u, *p)))
|
||||||
|
.filter(|key| !answered.contains(key))
|
||||||
|
.collect();
|
||||||
|
if !param_summary_done {
|
||||||
|
if !unanswered.is_empty() && sweep_round < 2 {
|
||||||
|
sweep_round += 1;
|
||||||
|
for key in unanswered.iter() { get_queue.push(*key); }
|
||||||
|
println!("[S] 参数扫描重试 第{}轮 ({} 项未应答)", sweep_round, unanswered.len());
|
||||||
|
} else if unanswered.is_empty() || last_sweep_rx.elapsed() >= Duration::from_millis(2500) {
|
||||||
|
param_summary_done = true;
|
||||||
|
next_status_poll = Instant::now() + Duration::from_secs(5);
|
||||||
|
println!("[S] ===== 参数汇总 =====");
|
||||||
|
for f in devinfo.iter() {
|
||||||
|
println!(" [S] {} @DMX{} {}ch 协议{}.{}/{:04x} 固件{:02x}{:02x}{:02x}{:02x} 型号{} 厂商{} 名称{} SW:{}",
|
||||||
|
uid_s(&f.uid), f.addr, f.footprint, f.proto_major, f.proto_minor, f.model,
|
||||||
|
f.sw[0], f.sw[1], f.sw[2], f.sw[3],
|
||||||
|
f.model_desc.as_deref().unwrap_or("-"),
|
||||||
|
f.manufacturer.as_deref().unwrap_or("-"),
|
||||||
|
f.label.as_deref().unwrap_or("-"),
|
||||||
|
f.sw_label.as_deref().unwrap_or("-"));
|
||||||
|
}
|
||||||
|
println!("[S] ==================== (每 {}s 轮询一次 RDM 状态)", STATUS_POLL_INTERVAL.as_secs());
|
||||||
|
}
|
||||||
|
} else if Instant::now() >= next_status_poll && !fixtures.is_empty() {
|
||||||
|
status_poll_count += 1;
|
||||||
|
for uid in fixtures.iter() { get_queue.push((*uid, 0x0060)); }
|
||||||
|
next_status_poll = Instant::now() + STATUS_POLL_INTERVAL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- DMX 帧 @30fps ---
|
// --- DMX 帧 @30fps ---
|
||||||
let mut p1 = [0u8; 512];
|
let mut p1 = [0u8; 512];
|
||||||
let mut p2 = [0u8; 512];
|
let mut p2 = [0u8; 512];
|
||||||
if !addr_list.is_empty() {
|
if !addr_by_uid.is_empty() {
|
||||||
for (k, &a) in addr_list.iter().enumerate() {
|
for (k, &(_, a)) in addr_by_uid.iter().enumerate() {
|
||||||
let phase = t * 1.2 + k as f32 * 2.1;
|
let phase = t * 1.2 + k as f32 * 2.1;
|
||||||
let v = (((phase.sin() * 0.5 + 0.5) * 255.0) * (t * 0.5).min(1.0)) as u8; // 淡入
|
let v = (((phase.sin() * 0.5 + 0.5) * 255.0) * (t * 0.5).min(1.0)) as u8; // 淡入
|
||||||
let ch = a.max(1) - 1;
|
let ch = a.max(1) - 1;
|
||||||
|
|
@ -1884,7 +2020,8 @@ fn session_loop(sock: &UdpSocket, dst: &str, target: &str, rng: &mut Rng, b12: u
|
||||||
sent += 1;
|
sent += 1;
|
||||||
std::thread::sleep(Duration::from_millis(33));
|
std::thread::sleep(Duration::from_millis(33));
|
||||||
}
|
}
|
||||||
println!("[S] 会话环结束: DMX {} 帧, 发现灯具 {} 台, 控制地址 {:?}", sent, fixtures.len(), addr_list);
|
println!("[S] 会话环结束: DMX {} 帧, 发现灯具 {} 台, 控制地址 {:?}", sent, fixtures.len(),
|
||||||
|
addr_by_uid.iter().map(|(u, a)| format!("{}@{}", uid_s(u), a)).collect::<Vec<_>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ★UDP 31772 通用中继 (udprelay 子命令): 跨网段主机验证用
|
/// ★UDP 31772 通用中继 (udprelay 子命令): 跨网段主机验证用
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user