chalkydri/
main.rs

1//!
2//! Chalkydri core
3//!
4
5#![feature(duration_millis_float)]
6// Unsafe code is NOT allowed in Chalkydri core.
7// If unsafe code is required, it should be part of a different crate.
8//#![forbid(unsafe_code)]
9#![allow(unreachable_code)]
10
11#[macro_use]
12extern crate log;
13#[cfg(feature = "web")]
14extern crate actix_web;
15extern crate env_logger;
16extern crate minint;
17extern crate tokio;
18#[cfg(feature = "web")]
19extern crate utoipa as utopia;
20#[macro_use]
21extern crate serde;
22#[cfg(feature = "capriltags")]
23extern crate apriltag;
24#[cfg(feature = "apriltags")]
25extern crate chalkydri_apriltags;
26#[cfg(feature = "python")]
27extern crate pyo3;
28#[cfg(feature = "ml")]
29extern crate tfledge;
30
31#[cfg(feature = "web")]
32mod api;
33mod calibration;
34mod cameras;
35mod config;
36mod error;
37mod logger;
38mod subsys;
39mod subsystem;
40mod utils;
41
42#[cfg(feature = "web")]
43use api::run_api;
44use cameras::CameraManager;
45use config::Config;
46use logger::Logger;
47use mimalloc::MiMalloc;
48use minint::NtConn;
49use once_cell::sync::Lazy;
50#[cfg(feature = "rerun")]
51use re_sdk::{MemoryLimit, RecordingStream};
52#[cfg(feature = "rerun_web_viewer")]
53use re_web_viewer_server::WebViewerServerPort;
54#[cfg(feature = "rerun")]
55use re_ws_comms::RerunServerPort;
56use std::{
57    error::Error, ffi::CStr, fs::File, io::Write, net::Ipv4Addr, path::Path, sync::Arc,
58    time::Duration,
59};
60use tokio::sync::RwLock;
61
62// mimalloc is a very good general purpose allocator
63#[global_allocator]
64static GLOBAL: MiMalloc = MiMalloc;
65
66use utils::gen_team_ip;
67
68use subsystem::Subsystem;
69
70#[allow(non_upper_case_globals)]
71static Cfg: Lazy<Arc<RwLock<Config>>> = Lazy::new(|| {
72    let mut path = Path::new("/boot/chalkydri.toml");
73    if !path.exists() {
74        path = Path::new("/etc/chalkydri.toml");
75        if !path.exists() {
76            path = Path::new("./chalkydri.toml");
77        }
78    }
79
80    Arc::new(RwLock::new(Config::load(path).unwrap()))
81});
82
83#[cfg(feature = "rerun")]
84#[allow(non_upper_case_globals)]
85static Rerun: Lazy<RecordingStream> = Lazy::new(|| {
86    #[cfg(feature = "rerun_web_viewer")]
87    re_sdk::RecordingStreamBuilder::new("chalkydri")
88        .serve_web(
89            "0.0.0.0",
90            WebViewerServerPort(8080),
91            RerunServerPort(6969),
92            MemoryLimit::from_bytes(10_000_000),
93            true,
94        )
95        .unwrap()
96        .into()
97});
98
99#[allow(non_upper_case_globals)]
100static Nt: Lazy<NtConn> = Lazy::new(|| {
101    futures_executor::block_on(async {
102        // Come up with an IP address for the roboRIO based on the team number or specified IP
103        let roborio_ip = {
104            let Config {
105                ntables_ip,
106                team_number,
107                ..
108            } = &*Cfg.read().await;
109
110            ntables_ip
111                .clone()
112                .map(|s| {
113                    s.parse::<Ipv4Addr>()
114                        .expect("failed to parse ip address")
115                        .octets()
116                })
117                .unwrap_or_else(|| gen_team_ip(*team_number).expect("failed to generate team ip"))
118        };
119
120        // Get the device's name or generate one if not set
121        let dev_name = if let Some(dev_name) = (*Cfg.read().await).device_name.clone() {
122            dev_name
123        } else {
124            warn!("device name not set! generating one...");
125
126            // Generate & save it
127            let dev_name = String::from("chalkydri");
128            (*Cfg.write().await).device_name = Some(dev_name.clone());
129
130            dev_name
131        };
132
133        // Attempt to connect to the NT server, retrying until successful
134
135        let nt: NtConn;
136
137        let mut retry = false;
138
139        loop {
140            match NtConn::new(roborio_ip, dev_name.clone()).await {
141                Ok(conn) => {
142                    nt = conn;
143                    break;
144                }
145                Err(err) => {
146                    if !retry {
147                        error!("Error connecting to NT server: {err:?}");
148                        retry = true;
149                    }
150                    tokio::time::sleep(Duration::from_millis(5)).await;
151                }
152            }
153        }
154
155        info!("Connected to NT server at {roborio_ip:?} successfully!");
156
157        nt
158    })
159});
160
161#[tokio::main(worker_threads = 16)]
162async fn main() -> Result<(), Box<dyn Error>> {
163    println!(
164        r#"
165    ))       ((       ___       __               _    __  ___
166   / /       \ \     |    |  | |  | |   | / \ / | \  |  |  |
167  / \\   _   / /\    |    |__| |__| |   |/   V  |  | |__|  |
168 / / \__/6\>_/ \ \   |    |  | |  | |   |\   |  |  | | \   |
169(  __          __ )  |___ |  | |  | |__ | \  |  |_/  |  \ _|_
170 \_____     _____/        
171      //////\             High-performance vision system
172      UUUUUUU                FRC Team 4533 - Phoenix
173"#
174    );
175
176    Logger::new().with_path_prefix("logs/handler").init()?;
177
178    info!("starting up...");
179
180    //rustix::fs::statfs(path).unwrap();
181
182    // Disable BS kernel modules
183    let _ = rustix::system::delete_module(c"rpivid_hevc", 0);
184    let _ = rustix::system::delete_module(c"pisp_be", 0);
185
186    gstreamer::init().unwrap();
187    debug!("initialized gstreamer");
188
189    let cam_man = CameraManager::new(Nt.clone()).await;
190    let api = tokio::spawn(run_api(cam_man.clone()));
191
192    let cam_man_ = cam_man.clone();
193    std::thread::spawn(move || {
194        //cam_man_.start();
195        //cam_man_.run().unwrap();
196    });
197
198    #[cfg(not(feature = "web"))]
199    local.await;
200
201    // Have to let NT topics get dropped before calling nt.stop()
202    #[cfg(feature = "web")]
203    {
204        tokio::select!(
205            //_ = local => {},
206            _ = api => {},
207            _ = tokio::signal::ctrl_c() => {
208                let mut f = File::create("chalkydri.toml").unwrap();
209                let toml_cfgg = toml::to_string_pretty(&*Cfg.read().await).unwrap();
210                f.write_all(toml_cfgg.as_bytes()).unwrap();
211                f.flush().unwrap();
212            },
213        );
214    }
215
216    Ok(())
217}