Skip to main content

banc_host/
node.rs

1//! A banc node: anything speaking banc-icd over postcard-rpc, whether a
2//! board on USB or a rig daemon across the network.
3//!
4//! `Node` wraps discovery (nusb enumeration filtered by the rig config, or
5//! an authenticated TCP connect), the identify handshake, and access to the
6//! underlying `HostClient`. Consumers with their own ICDs call `client()`
7//! and use their endpoint types directly — banc never needs to know them.
8
9use crate::config::AssistantConfig;
10use banc_icd::node::Identity;
11use banc_icd::{IdentifyEndpoint, PROTOCOL_VERSION};
12use postcard_rpc::header::VarSeqKind;
13use postcard_rpc::host_client::HostClient;
14use postcard_rpc::standard_icd::{WireError, ERROR_PATH};
15
16pub struct Node {
17    client: HostClient<WireError>,
18    pub identity: Identity,
19}
20
21impl Node {
22    /// Connect per the config entry: network when `addr` is set, USB
23    /// enumeration otherwise. `token` must be pre-resolved by the caller
24    /// (the rig knows the config's base directory; this module does not).
25    pub async fn connect(cfg: &AssistantConfig, token: Option<&str>) -> anyhow::Result<Node> {
26        if let Some(addr) = &cfg.addr {
27            let token = token.ok_or_else(|| {
28                anyhow::anyhow!("node '{}' has addr but no token was resolved", cfg.name)
29            })?;
30            let client = crate::net::connect_node(addr, token).await?;
31            return Self::identify(client, &cfg.name).await;
32        }
33        let serial = cfg.serial.clone();
34        let product = cfg.product.clone();
35        let name = cfg.name.clone();
36        let client = HostClient::try_new_raw_nusb(
37            move |d| {
38                let serial_ok = serial
39                    .as_deref()
40                    .is_none_or(|want| d.serial_number() == Some(want));
41                let product_ok = product
42                    .as_deref()
43                    .is_none_or(|want| d.product_string() == Some(want));
44                serial_ok && product_ok
45            },
46            ERROR_PATH,
47            8,
48            VarSeqKind::Seq2,
49        )
50        .map_err(|e| anyhow::anyhow!("connecting to node '{name}': {e}"))?;
51        Self::identify(client, &cfg.name).await
52    }
53
54    async fn identify(client: HostClient<WireError>, name: &str) -> anyhow::Result<Node> {
55        let identity = client
56            .send_resp::<IdentifyEndpoint>(&())
57            .await
58            .map_err(|e| anyhow::anyhow!("identify on node '{name}': {e:?}"))?;
59        anyhow::ensure!(
60            identity.protocol_version == PROTOCOL_VERSION,
61            "node '{name}' speaks banc protocol v{}, host expects v{PROTOCOL_VERSION}",
62            identity.protocol_version,
63        );
64        Ok(Node { client, identity })
65    }
66
67    /// The raw postcard-rpc client, for consumer-defined endpoints/topics.
68    pub fn client(&self) -> &HostClient<WireError> {
69        &self.client
70    }
71
72    /// Ask the node to reset itself (firmware replies first, then reboots).
73    pub async fn reset(&self) -> anyhow::Result<()> {
74        self.client
75            .send_resp::<banc_icd::ResetEndpoint>(&())
76            .await
77            .map_err(|e| anyhow::anyhow!("reset: {e:?}"))?;
78        Ok(())
79    }
80}