#include "fgc/ui/UiSnapshot.h" #include #include #include namespace fgc { const char* axisStateLabel(AxisState s) { switch (s) { case AxisState::Boot: return "BOOT"; case AxisState::Reset: return "RESET"; case AxisState::Homing: return "HOMING"; case AxisState::Ready: return "READY"; case AxisState::Error: return "ERROR"; case AxisState::NeedConfig: return "NEEDCFG"; case AxisState::Unknown: default: return "----"; } } UiColor axisStateColor(AxisState s) { switch (s) { case AxisState::Ready: case AxisState::Homing: return UiColor::Cyan; case AxisState::Error: return UiColor::Red; // Not red: nothing is broken, the firmware is waiting to be told what it // is driving. Yellow so it reads as "needs you", not as a fault. case AxisState::NeedConfig: return UiColor::Yellow; case AxisState::Boot: case AxisState::Reset: return UiColor::Dim; case AxisState::Unknown: default: return UiColor::Default; } } std::string formatImuMount(const ImuMount& m) { if (m.identity()) return ""; char b[64]; std::snprintf(b, sizeof(b), "x %.1f y %.1f z %.1f", m.x_deg, m.y_deg, m.z_deg); return b; } std::string formatDegrees(double deg) { char buf[32]; std::snprintf(buf, sizeof(buf), "%.1f\xC2\xB0", deg); // UTF-8 degree sign return buf; } std::string formatTimeAgo(long long now_ms, long long then_ms) { if (then_ms <= 0) return "\xE2\x80\x94"; // em dash: never long long d = now_ms - then_ms; if (d < 0) d = 0; long long s = d / 1000; char buf[32]; if (s < 60) std::snprintf(buf, sizeof(buf), "%llds ago", s); else if (s < 3600) std::snprintf(buf, sizeof(buf), "%lldm ago", s / 60); else std::snprintf(buf, sizeof(buf), "%lldh ago", s / 3600); return buf; } bool backgroundAvailable(const char* tmux_env) { // An empty TMUX is not "in tmux": the variable exists but names no server, // so detach-client would have nothing to talk to. return tmux_env != nullptr && *tmux_env != '\0'; } std::string topicLeaf(const std::string& topic) { const auto slash = topic.rfind('/'); if (slash == std::string::npos) return topic; return topic.substr(slash + 1); } std::string formatClockMs(long long ms) { if (ms <= 0) return "\xE2\x80\x94"; // em dash: never const std::time_t secs = static_cast(ms / 1000); std::tm tm{}; localtime_r(&secs, &tm); char buf[32]; std::snprintf(buf, sizeof(buf), "%02d:%02d:%02d.%03d", tm.tm_hour, tm.tm_min, tm.tm_sec, static_cast(ms % 1000)); return buf; } SensorsView pendingSensorsView() { SensorsView v; // Xsens MTi-28 AHRS: drift-free Euler orientation. The device's internal // temperature is deliberately NOT here — it is diagnostic detail shown only // in the expanded view, so the sole temperature on the main window is the // SHT41's ambient one below. v.imu.title = "IMU (orientation)"; v.imu.status = "pending"; v.imu.fields.push_back({"Roll", "--.-", "\xC2\xB0", false}); v.imu.fields.push_back({"Pitch", "--.-", "\xC2\xB0", false}); v.imu.fields.push_back({"Yaw", "--.-", "\xC2\xB0", false}); // SHT41 temperature & humidity (Adafruit, I2C on the LattePanda's own bus): // ±0.2 °C, ±1.8 %RH (ambient). v.env.title = "SHT41 (ambient)"; v.env.status = "pending"; v.env.fields.push_back({"Temp", "--.-", "\xC2\xB0""C", false}); v.env.fields.push_back({"Humid", "--", "%RH", false}); return v; } } // namespace fgc