microfetch.rs
1 use alloc::string::String as AllocString; 2 use core::fmt::Write; 3 use embassy_time::Instant; 4 use esp_hal::{clock, efuse, system, system::Cpu}; 5 6 use crate::{ 7 config::{app, board}, 8 console::icons, 9 hardware, 10 services::{identity, system as system_service}, 11 }; 12 13 unsafe extern "C" { 14 #[link_name = "esp_app_desc"] 15 static ESP_APP_DESC: esp_bootloader_esp_idf::EspAppDesc; 16 } 17 18 // TODO(perf): The 30+ row!() invocations below are repetitive but each 19 // has different format arguments, so a data-driven array of tuples would 20 // require pre-formatting each value into an AllocString (one heap 21 // allocation per row). On PSRAM that's tolerable but wasteful. The 22 // current approach writes directly to a single output buffer with zero 23 // intermediate allocations. Refactor only if readability becomes a 24 // maintenance burden — the format arguments are type-checked at compile 25 // time, which a data-driven approach would lose. 26 macro_rules! row { 27 ($out:expr, $color:expr, $icon:expr, $label:expr, $($val:tt)*) => {{ 28 let _ = write!($out, " \x1b[1;{}m{} {:<14}\x1b[0m ", $color, $icon, $label); 29 let _ = write!($out, $($val)*); 30 let _ = write!($out, "\r\n"); 31 }}; 32 } 33 34 pub fn run() -> AllocString { 35 let mut out = AllocString::new(); 36 let secs = Instant::now().as_secs(); 37 let system_snapshot = system_service::snapshot(); 38 let sensor_inventory = &system_snapshot.sensors.inventory; 39 let carbon_dioxide = system_snapshot.sensors.carbon_dioxide; 40 let i2c_status = hardware::i2c::snapshot(); 41 let chip_rev = efuse::chip_revision(); 42 let mac = efuse::base_mac_address(); 43 let cpu_freq_mhz = clock::cpu_clock().as_mhz(); 44 45 let heap_used = esp_alloc::HEAP.used(); 46 let heap_free = esp_alloc::HEAP.free(); 47 let heap_total = heap_used + heap_free; 48 let heap_pct = (heap_used * 100) / heap_total; 49 50 let _ = write!(out, "\r\n"); 51 52 let _ = write!( 53 out, 54 " \x1b[1;32m{}\x1b[0m\x1b[2m@\x1b[0m\x1b[1;36m{}\x1b[0m\r\n", 55 identity::ssh_user(), 56 identity::hostname() 57 ); 58 let sep_len = identity::ssh_user().len() + 1 + identity::hostname().len(); 59 let _ = write!(out, " \x1b[2m"); 60 for _ in 0..sep_len { 61 out.push(icons::BOX_HORIZONTAL); 62 } 63 let _ = write!(out, "\x1b[0m\r\n"); 64 65 let app_desc = unsafe { &ESP_APP_DESC }; 66 row!( 67 out, 68 "33", 69 "", 70 "OS", 71 "\x1b[1m{}\x1b[0m {} ({})", 72 app_desc.project_name(), 73 app_desc.version(), 74 board::PLATFORM 75 ); 76 row!( 77 out, 78 "35", 79 "", 80 "Host", 81 "\x1b[1mESP32-S3\x1b[0m (rev {}.{})", 82 chip_rev.major, 83 chip_rev.minor 84 ); 85 row!( 86 out, 87 "35", 88 "", 89 "Chassis", 90 "\x1b[1mesp32s3-devkitc1-N8R8\x1b[0m" 91 ); 92 row!( 93 out, 94 "36", 95 "", 96 "Kernel", 97 "\x1b[1membassy 0.7\x1b[0m / esp-hal 1.0" 98 ); 99 row!( 100 out, 101 "33", 102 icons::NF_FA_DATABASE, 103 "Built", 104 "\x1b[1m{}\x1b[0m {}", 105 app_desc.date(), 106 app_desc.time() 107 ); 108 // Partition info removed — FlashStorage can only be created once and is 109 // owned by the boot code. Display booted partition via a different mechanism 110 // when available. 111 112 let (d, h, m, s) = ( 113 secs / 86400, 114 (secs % 86400) / 3600, 115 (secs % 3600) / 60, 116 secs % 60, 117 ); 118 match (d, h) { 119 (1.., _) => row!( 120 out, 121 "34", 122 "", 123 "Uptime", 124 "\x1b[1m{}\x1b[0m days, \x1b[1m{}\x1b[0m hours, \x1b[1m{}\x1b[0m mins, \x1b[1m{}\x1b[0m secs", 125 d, 126 h, 127 m, 128 s 129 ), 130 (_, 1..) => row!( 131 out, 132 "34", 133 "", 134 "Uptime", 135 "\x1b[1m{}\x1b[0m hours, \x1b[1m{}\x1b[0m mins, \x1b[1m{}\x1b[0m secs", 136 h, 137 m, 138 s 139 ), 140 _ => row!( 141 out, 142 "34", 143 "", 144 "Uptime", 145 "\x1b[1m{}\x1b[0m mins, \x1b[1m{}\x1b[0m secs", 146 m, 147 s 148 ), 149 } 150 151 if let Some(reason) = system::reset_reason() { 152 row!( 153 out, 154 "31", 155 icons::NF_FA_BOLT, 156 "Reset", 157 "\x1b[1m{:?}\x1b[0m", 158 reason 159 ); 160 } 161 162 row!(out, "32", "", "Shell", "\x1b[1mMicroshell\x1b[0m (SSH)"); 163 row!( 164 out, 165 "31", 166 "", 167 "CPU", 168 "\x1b[1mXtensa LX7\x1b[0m ({}) @ \x1b[1m{} MHz\x1b[0m", 169 Cpu::COUNT, 170 cpu_freq_mhz 171 ); 172 row!( 173 out, 174 "36", 175 "", 176 "RAM", 177 "\x1b[1m{:.2} KiB\x1b[0m / \x1b[1m{:.2} KiB\x1b[0m (\x1b[1;32m{}%\x1b[0m)", 178 heap_used as f32 / 1024.0, 179 heap_total as f32 / 1024.0, 180 heap_pct 181 ); 182 183 if system_snapshot.storage.sd_card_size_mb > 0 { 184 row!( 185 out, 186 "32", 187 "", 188 "Disk", 189 "\x1b[1m{} MiB\x1b[0m / \x1b[1m{} MiB\x1b[0m - {} [\x1b[1m{}\x1b[0m]", 190 0, 191 system_snapshot.storage.sd_card_size_mb, 192 app::sd_card::FS_TYPE, 193 app::sd_card::DEVICE 194 ); 195 } else { 196 row!( 197 out, 198 "32", 199 icons::NF_FA_HDD, 200 "Disk", 201 "\x1b[2mnot detected\x1b[0m" 202 ); 203 } 204 205 row!( 206 out, 207 "33", 208 "", 209 "Local IP", 210 "\x1b[1m{}.{}.{}.{}\x1b[0m/24", 211 system_snapshot.network.station.ipv4_address[0], 212 system_snapshot.network.station.ipv4_address[1], 213 system_snapshot.network.station.ipv4_address[2], 214 system_snapshot.network.station.ipv4_address[3] 215 ); 216 row!( 217 out, 218 "32", 219 icons::NF_FA_WIFI, 220 "WiFi STA", 221 "{}", 222 if system_snapshot.network.station.is_connected { 223 "\x1b[32mconnected\x1b[0m" 224 } else { 225 "\x1b[31mdisconnected\x1b[0m" 226 } 227 ); 228 row!( 229 out, 230 "35", 231 "", 232 "MAC", 233 "\x1b[1m{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}\x1b[0m", 234 mac.as_bytes()[0], 235 mac.as_bytes()[1], 236 mac.as_bytes()[2], 237 mac.as_bytes()[3], 238 mac.as_bytes()[4], 239 mac.as_bytes()[5] 240 ); 241 242 let _ = write!(out, "\r\n"); 243 244 row!( 245 out, 246 "36", 247 icons::NF_FA_SERVER, 248 "Hostname", 249 "\x1b[1m{}\x1b[0m", 250 identity::hostname() 251 ); 252 row!( 253 out, 254 "34", 255 icons::NF_FA_GLOBE, 256 "NTP", 257 "\x1b[1m{}\x1b[0m", 258 app::NTP_SERVER 259 ); 260 row!( 261 out, 262 "33", 263 icons::NF_FA_PLUG, 264 "Ports", 265 "SSH:\x1b[1m{}\x1b[0m HTTP:\x1b[1m{}\x1b[0m OTA:\x1b[1m{}\x1b[0m Log:\x1b[1m{}\x1b[0m", 266 app::ssh::PORT, 267 app::http::PORT, 268 app::ota::PORT, 269 app::tcp_log::PORT 270 ); 271 row!( 272 out, 273 "35", 274 icons::NF_FA_WIFI, 275 "WiFi AP", 276 "\x1b[1m{}\x1b[0m (ch\x1b[1m{}\x1b[0m, {})", 277 system_snapshot.network.access_point.ssid, 278 system_snapshot.network.access_point.channel, 279 system_snapshot.network.access_point.auth_mode 280 ); 281 282 let _ = write!(out, "\r\n"); 283 284 row!( 285 out, 286 "36", 287 icons::NF_FA_COG, 288 "I2C Freq", 289 "\x1b[1m{}\x1b[0m kHz", 290 i2c_status.frequency_khz 291 ); 292 row!( 293 out, 294 "31", 295 icons::NF_FA_BOLT, 296 "Power GPIO", 297 "\x1b[1mGPIO{}\x1b[0m", 298 i2c_status.power_gpio 299 ); 300 row!( 301 out, 302 "34", 303 icons::NF_FA_COG, 304 "SPI (SD)", 305 "CS:\x1b[1mGPIO{}\x1b[0m MOSI:\x1b[1mGPIO{}\x1b[0m SCK:\x1b[1mGPIO{}\x1b[0m MISO:\x1b[1mGPIO{}\x1b[0m", 306 board::sd_card::CS_GPIO, 307 board::sd_card::MOSI_GPIO, 308 board::sd_card::SCK_GPIO, 309 board::sd_card::MISO_GPIO 310 ); 311 312 for bus in i2c_status.buses.iter() { 313 row!( 314 out, 315 "36", 316 icons::NF_FA_SITEMAP, 317 bus.name, 318 "SDA:\x1b[1mGPIO{}\x1b[0m SCL:\x1b[1mGPIO{}\x1b[0m", 319 bus.sda_gpio, 320 bus.scl_gpio 321 ); 322 } 323 324 let _ = write!(out, "\r\n"); 325 326 for sensor in sensor_inventory.iter() { 327 let mut val = AllocString::new(); 328 let transport = sensor.transport_summary(); 329 if let Some(address) = transport.address { 330 let _ = write!( 331 val, 332 "\x1b[1m{}\x1b[0m @ {} (\x1b[1m0x{:02X}\x1b[0m)", 333 sensor.model, transport.bus_name, address 334 ); 335 } else { 336 let _ = write!( 337 val, 338 "\x1b[1m{}\x1b[0m @ {} (slave \x1b[1m{}\x1b[0m reg \x1b[1m{}\x1b[0m)", 339 sensor.model, 340 transport.bus_name, 341 transport.slave_id.unwrap_or_default(), 342 transport.register_address.unwrap_or_default() 343 ); 344 } 345 row!(out, "35", icons::NF_FA_SIGNAL, sensor.name, "{}", val); 346 } 347 348 if carbon_dioxide.ok { 349 let _ = write!(out, "\r\n"); 350 row!( 351 out, 352 "32", 353 icons::NF_FA_LEAF, 354 "CO2", 355 "\x1b[1;32m{:.1}\x1b[0m ppm", 356 carbon_dioxide.co2_ppm 357 ); 358 row!( 359 out, 360 "31", 361 icons::NF_FA_THERMOMETER, 362 "Temperature", 363 "\x1b[1;33m{:.1}\x1b[0m\u{00b0}C", 364 carbon_dioxide.temperature 365 ); 366 row!( 367 out, 368 "34", 369 icons::NF_FA_TINT, 370 "Humidity", 371 "\x1b[1;36m{:.1}\x1b[0m%%", 372 carbon_dioxide.humidity 373 ); 374 } 375 376 let _ = write!(out, "\r\n"); 377 out 378 }