openipc_core/channel.rs
1/// OpenIPC's observed default link id from the reference browser receiver.
2///
3/// The Zig project notes this as the SHA1-derived id for
4/// `link_domain = "default"`.
5pub const DEFAULT_LINK_ID: u32 = 7_669_206;
6
7/// Low-byte port selector inside an OpenIPC/WFB channel id.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub enum RadioPort {
10 /// Air-unit to ground-station video RTP downlink.
11 Video,
12 /// Air-unit to ground-station telemetry downlink.
13 ///
14 /// OpenIPC commonly carries MAVLink or MSP/OSD-style telemetry here,
15 /// depending on the transmitter-side telemetry router.
16 TelemetryRx,
17 /// Ground-station to air-unit telemetry uplink.
18 TelemetryTx,
19 /// Air-unit to ground-station tunnel/data downlink.
20 TunnelRx,
21 /// Ground-station to air-unit tunnel/data uplink.
22 ///
23 /// Adaptive-link feedback is sent over this path in aviateur, PixelPilot,
24 /// and current OpenIPC firmware setups.
25 TunnelTx,
26 /// Air-unit to ground-station audio profile downlink.
27 AudioRx,
28 /// Ground-station to air-unit audio profile uplink.
29 AudioTx,
30 /// Legacy alias for [`RadioPort::TelemetryRx`].
31 #[deprecated(note = "use RadioPort::TelemetryRx; port 0x10 is telemetry, not always MAVLink")]
32 MavlinkRx,
33 /// Legacy alias for [`RadioPort::TunnelTx`].
34 ///
35 /// Earlier openipc-rs builds used this for adaptive-link feedback. The
36 /// wire value remains `0xa0`, but the accurate OpenIPC name is tunnel/data
37 /// uplink. Use [`RadioPort::TelemetryTx`] when you mean telemetry port
38 /// `0x90`.
39 #[deprecated(
40 note = "use RadioPort::TunnelTx for adaptive-link or RadioPort::TelemetryTx for telemetry uplink"
41 )]
42 MavlinkTx,
43 /// Legacy alias for [`RadioPort::TunnelRx`].
44 #[deprecated(note = "use RadioPort::TunnelRx")]
45 DataRx,
46 /// Caller-defined radio port for custom payload channels.
47 Custom(u8),
48}
49
50impl RadioPort {
51 /// Return the low byte used in an OpenIPC/WFB channel id.
52 #[allow(deprecated)]
53 pub const fn as_u8(self) -> u8 {
54 match self {
55 Self::Video => 0,
56 Self::TelemetryRx | Self::MavlinkRx => 0x10,
57 Self::TelemetryTx => 0x90,
58 Self::TunnelRx | Self::DataRx => 0x20,
59 Self::TunnelTx | Self::MavlinkTx => 0xa0,
60 Self::AudioRx => 0x30,
61 Self::AudioTx => 0xb0,
62 Self::Custom(value) => value,
63 }
64 }
65}
66
67/// OpenIPC/WFB logical channel id.
68///
69/// The high 24 bits are the link id and the low byte is the radio port.
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
71pub struct ChannelId(u32);
72
73impl ChannelId {
74 /// Wrap a raw 32-bit channel id.
75 pub const fn new(raw: u32) -> Self {
76 Self(raw)
77 }
78
79 /// Build a channel id from a link id and radio port.
80 pub const fn from_link_port(link_id: u32, port: RadioPort) -> Self {
81 Self((link_id << 8) | port.as_u8() as u32)
82 }
83
84 /// Return the default OpenIPC video channel.
85 pub const fn default_video() -> Self {
86 Self::from_link_port(DEFAULT_LINK_ID, RadioPort::Video)
87 }
88
89 /// Return the raw big-endian channel id value.
90 pub const fn raw(self) -> u32 {
91 self.0
92 }
93
94 /// Return the raw channel id encoded for 802.11 address fields.
95 pub const fn to_be_bytes(self) -> [u8; 4] {
96 self.0.to_be_bytes()
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn default_video_channel_matches_reference_value() {
106 let id = ChannelId::default_video();
107 assert_eq!(id.raw(), (DEFAULT_LINK_ID << 8));
108 assert_eq!(id.to_be_bytes(), id.raw().to_be_bytes());
109 }
110
111 #[test]
112 fn radio_ports_match_openipc_ground_station_conventions() {
113 assert_eq!(RadioPort::Video.as_u8(), 0x00);
114 assert_eq!(RadioPort::TelemetryRx.as_u8(), 0x10);
115 assert_eq!(RadioPort::TunnelRx.as_u8(), 0x20);
116 assert_eq!(RadioPort::AudioRx.as_u8(), 0x30);
117 assert_eq!(RadioPort::TelemetryTx.as_u8(), 0x90);
118 assert_eq!(RadioPort::TunnelTx.as_u8(), 0xa0);
119 assert_eq!(RadioPort::AudioTx.as_u8(), 0xb0);
120 }
121}