Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,21 @@ The project follows Semantic Versioning once the first firmware release is tagge

### Fixed

- the ready dashboard **reports the receiver as it is now, not as it was at boot**.
`ready_snapshot` read the health live and correctly, but `refresh_ready` was only
reached from boot, a settings save and a track change, and the receiver acquires its
first fix after the boot sequence has already drawn the screen. With 16 satellites and a
24.9 Hz fix stream the dashboard still showed what was true at 2.8 seconds. It now
refreshes from the display cadence, gated on the dashboard being the active screen and
on a fingerprint of the drawn state having changed - an idle dashboard costs a struct
comparison, because a repaint costs about 40 ms against a 25 ms service period (#213)
- Race mode no longer claims **`lap times pending GNSS`** whatever the receiver is doing.
Honest while nothing consumed fixes; since the timing runtime was wired it was both
wrong and the most alarming thing the screen could say while everything worked. The note
now distinguishes no circuit armed, waiting for a fix, and lap times live
- `logging_available` and `session_active` on the dashboard are read rather than
hardcoded to `false`

- the **GPS session trigger now starts a session**. Choosing it armed the device and showed
`WAITING FOR THE LINE`, and nothing could ever start the session: only a manual START and
IMU launch detection reached `begin_session`. The mechanism existed - `gate_automation`
Expand Down
77 changes: 72 additions & 5 deletions firmware/main/screen_router.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,7 @@ class ScreenRouter {
}
last_display_service_us_ = now_us;
service_imu();
service_ready_dashboard();
service_roller();
if (capturing_) {
// Drawn at the display's cadence; the controller above is fed every tick.
Expand Down Expand Up @@ -1589,9 +1590,22 @@ class ScreenRouter {
trackday_->set_mode_note("TRACK DAY - lap times available in Review");
break;
case ui::DeviceMode::race:
// Race wants lap times and a delta alongside the countdown. Neither exists
// without GNSS, so it borrows the countdown and says so.
trackday_->set_mode_note("RACE - lap times pending GNSS");
// Race wants lap times and a delta alongside the countdown, and both need a
// circuit armed and a receiver producing fixes. This used to be the fixed
// string "lap times pending GNSS", which was honest while nothing consumed
// fixes and became the most alarming thing the screen could say once laps
// worked. Each of the three states gets its own words, so a driver can tell
// which of them they are in.
if (!track_armed_) {
trackday_->set_mode_note("RACE - no circuit armed for lap times");
}
else if (gnss_pipeline_.health(esp_timer_get_time()) !=
domain::GnssHealth::good) {
trackday_->set_mode_note("RACE - waiting for a fix");
}
else {
trackday_->set_mode_note("RACE - lap times live");
}
break;
case ui::DeviceMode::g_only:
trackday_->set_mode_note("G-ONLY - session timer");
Expand Down Expand Up @@ -1700,8 +1714,10 @@ class ScreenRouter {
snapshot.storage = storage::mounted() ? ui::Readiness::ready
: ui::Readiness::unavailable;
snapshot.imu = imu::running() ? ui::Readiness::ready : ui::Readiness::unavailable;
snapshot.logging_available = false;
snapshot.session_active = false;
// Both were hardcoded. Logging is available once there is somewhere to write, and
// whether a session is running is something the router already tracks.
snapshot.logging_available = storage::mounted();
snapshot.session_active = session_was_active_;

if (track_armed_) {
std::strncpy(snapshot.selected_track.data(), active_track_.name.data(),
Expand All @@ -1725,6 +1741,53 @@ class ScreenRouter {
return snapshot;
}

// Everything on the dashboard that changes without anybody touching the device.
// Compared rather than redrawn: a repaint costs about 40 ms against a 25 ms service
// period (#213), so repainting a dashboard that has not changed would spend the
// device's most expensive operation on producing an identical screen.
struct ReadyFingerprint {
domain::GnssHealth gnss_health{domain::GnssHealth::unavailable};
ui::Readiness storage{ui::Readiness::unavailable};
ui::Readiness imu{ui::Readiness::unavailable};
ui::ReadyTrackState track_state{ui::ReadyTrackState::none};
bool logging_available{false};
bool session_active{false};

[[nodiscard]] bool operator==(const ReadyFingerprint& other) const noexcept
{
return gnss_health == other.gnss_health && storage == other.storage &&
imu == other.imu && track_state == other.track_state &&
logging_available == other.logging_available &&
session_active == other.session_active;
}
};

[[nodiscard]] ReadyFingerprint ready_fingerprint() const noexcept
{
const auto snapshot = ready_snapshot();
return {snapshot.gnss_health, snapshot.storage, snapshot.imu,
snapshot.track_state, snapshot.logging_available, snapshot.session_active};
}

// The receiver acquires its first fix after the boot sequence has already drawn the
// dashboard, so without this the screen reports a receiver that was not yet working
// and never corrects itself. refresh_ready is otherwise only reached from boot, a
// settings save and a track change - none of which a driver waiting for a fix does.
void service_ready_dashboard() noexcept
{
if (ready_ == nullptr || lv_screen_active() != screen_for(ui::Destination::ready)) {
return;
}
const auto fingerprint = ready_fingerprint();
if (fingerprint == drawn_ready_fingerprint_) {
return;
}
drawn_ready_fingerprint_ = fingerprint;
refresh_ready();
// The Race note is derived from the same state, so it goes stale the same way.
describe_mode();
}

void refresh_ready() noexcept
{
if (ready_ != nullptr) {
Expand Down Expand Up @@ -2512,6 +2575,10 @@ class ScreenRouter {
// Everything that is not the GNSS poll runs at this period rather than the tick's.
static constexpr std::int64_t kDisplayServicePeriodUs = 50'000;
std::int64_t last_display_service_us_{0};
// Deliberately default-constructed rather than seeded from the first reading, so the
// first service tick after boot always draws once and corrects whatever the boot
// sequence put on screen before the receiver had a fix.
ReadyFingerprint drawn_ready_fingerprint_{};
gnss::GnssTransport* gnss_transport_{nullptr};
gnss::GnssPipeline gnss_pipeline_{};
gnss::GnssConfigurationReport gnss_configuration_{};
Expand Down