chalkydri/
main.rs

1//!
2//! # Chalkydri
3//!
4//! This crate contains Chalkydri itself.
5//!
6//! This code runs on the vision coprocessor(s) and does all the heavy lifting.
7//!
8
9#![feature(duration_millis_float)]
10#![allow(unreachable_code)]
11
12// These deps are needed no matter what
13#[macro_use]
14extern crate tracing;
15#[macro_use]
16extern crate serde;
17extern crate tokio;
18extern crate minint;
19
20// Web server and OpenAPI documentation generator
21#[cfg(feature = "web")]
22extern crate actix_web;
23#[cfg(feature = "web")]
24extern crate utoipa as utopia;
25
26// Apriltag stuff
27#[cfg(feature = "capriltags")]
28extern crate apriltag;
29#[cfg(feature = "apriltags")]
30extern crate chalkydri_apriltags;
31
32#[cfg(feature = "python")]
33extern crate pyo3;
34
35#[cfg(feature = "ml")]
36extern crate tfledge;
37
38#[cfg(feature = "web")]
39mod api;
40mod calibration;
41mod cameras;
42mod config;
43mod error;
44mod pose;
45mod subsystems;
46mod utils;
47
48#[cfg(feature = "web")]
49use api::run_api;
50use cameras::CameraManager;
51use config::Config;
52use mimalloc::MiMalloc;
53use minint::NtConn;
54use once_cell::sync::Lazy;
55#[cfg(feature = "rerun")]
56use re_sdk::{MemoryLimit, RecordingStream};
57#[cfg(feature = "rerun_web_viewer")]
58use re_web_viewer_server::WebViewerServerPort;
59#[cfg(feature = "rerun")]
60use re_ws_comms::RerunServerPort;
61use std::{error::Error, net::Ipv4Addr, path::Path, sync::Arc};
62use tokio::sync::{RwLock, mpsc};
63use tracing_subscriber::{EnvFilter, Layer, layer::SubscriberExt, util::SubscriberInitExt};
64
65// mimalloc is a very good general purpose allocator
66#[global_allocator]
67static GLOBAL: MiMalloc = MiMalloc;
68
69use utils::gen_team_ip;
70
71/// Chalkydri's loaded, active configuration
72#[allow(non_upper_case_globals)]
73static Cfg: Lazy<Arc<RwLock<Config>>> = Lazy::new(|| {
74    // Try a few different paths for the config file, exiting early if we find one that exists
75    let mut path = Path::new("/boot/chalkydri.toml");
76    if !path.exists() {
77        path = Path::new("/etc/chalkydri.toml");
78        if !path.exists() {
79            path = Path::new("./chalkydri.toml");
80        }
81    }
82
83    // If all else fails, we'll just use a default configuration
84    Arc::new(RwLock::new(Config::load(path).unwrap_or_default()))
85});
86
87#[cfg(feature = "rerun")]
88#[allow(non_upper_case_globals)]
89static Rerun: Lazy<RecordingStream> = Lazy::new(|| {
90    #[cfg(feature = "rerun_web_viewer")]
91    re_sdk::RecordingStreamBuilder::new("chalkydri")
92        .serve_web(
93            "0.0.0.0",
94            WebViewerServerPort(8080),
95            RerunServerPort(6969),
96            MemoryLimit::from_bytes(10_000_000),
97            true,
98        )
99        .unwrap()
100        .into()
101});
102
103#[allow(non_upper_case_globals)]
104static Nt: Lazy<NtConn> = Lazy::new(|| {
105    futures_executor::block_on(async {
106        // Come up with an IP address for the roboRIO based on the team number or specified IP
107        let roborio_ip = {
108            let Config {
109                ntables_ip,
110                team_number,
111                ..
112            } = &*Cfg.read().await;
113
114            ntables_ip
115                .clone()
116                .map(|s| {
117                    s.parse::<Ipv4Addr>()
118                        .expect("failed to parse ip address")
119                        .octets()
120                })
121                .unwrap_or_else(|| gen_team_ip(*team_number).expect("failed to generate team ip"))
122        };
123
124        // Get the device's name or generate one if not set
125        let dev_name = if let Some(dev_name) = (*Cfg.read().await).device_name.clone() {
126            dev_name
127        } else {
128            warn!("device name not set! generating one...");
129
130            // Generate & save it
131            let dev_name = String::from("chalkydri");
132            (*Cfg.write().await).device_name = Some(dev_name.clone());
133
134            dev_name
135        };
136
137        let nt: NtConn;
138
139        match NtConn::new(Ipv4Addr::from(roborio_ip).to_string(), dev_name.clone()).await {
140            Ok(conn) => {
141                nt = conn;
142            }
143            Err(err) => {
144                panic!("Error connecting to NT server: {err:?}");
145            }
146        }
147
148        info!("Connected to NT server at {roborio_ip:?} successfully!");
149
150        nt
151    })
152});
153
154#[tokio::main(worker_threads = 16)]
155async fn main() -> Result<(), Box<dyn Error>> {
156    println!(
157        r#"
158    ))       ((       ___       __               _    __  ___
159   / /       \ \     |    |  | |  | |   | / \ / | \  |  |  |
160  / \\   _   / /\    |    |__| |__| |   |/   V  |  | |__|  |
161 / / \__/6\>_/ \ \   |    |  | |  | |   |\   |  |  | | \   |
162(  __          __ )  |___ |  | |  | |__ | \  |  |_/  |  \ _|_
163 \_____     _____/        
164      //////\             High-performance vision system
165      UUUUUUU                FRC Team 4533 - Phoenix
166"#
167    );
168
169    // Set up logging
170    let filter = EnvFilter::from_default_env();
171    let layer = tracing_subscriber::fmt::layer().with_filter(filter);
172    tracing_subscriber::registry().with(layer).init();
173
174    info!("starting up...");
175
176    // Disable BS kernel modules
177    let _ = rustix::system::delete_module(c"rpivid_hevc", 0);
178    let _ = rustix::system::delete_module(c"pisp_be", 0);
179
180    // Initialize GStreamer
181    gstreamer::init().unwrap();
182    debug!("initialized gstreamer");
183
184    // Create the shutdown channel
185    let (tx, mut rx) = mpsc::channel::<()>(1);
186    // Spawn the camera manager
187    let cam_man = CameraManager::new(Nt.clone(), tx).await;
188    // Spawn the web server
189    let api = tokio::spawn(run_api(cam_man.clone()));
190
191    // Poll the API server future until the end of time, ctrl+c, or a message on the shutdown channel
192    tokio::select!(
193        _ = api => {},
194        _ = tokio::signal::ctrl_c() => {},
195        _ = rx.recv() => {},
196    );
197
198    Ok(())
199}