chore: cut 0.3.0 — new tuning crate, retuned lifecycle constants
- Adds an in-repo `tuning/` crate that solves the four-knob LED-threshold tuning problem as a 4-objective Pareto search using published `heuropt` 0.8 (NSGA-III + a-posteriori weighted ranking), replacing `scripts/tune_runtime.py`'s single-composite-score grid. `just tune` runs it; the crate is its own workspace root with a local `.cargo/config.toml` overriding the firmware's inherited `thumbv6m-none-eabi` build target so it can use `std`. - Retunes the shipping defaults from the new Pareto front: `RUN_DURATION` 4h00m → 3h51m, `YELLOW_AT` 30 → 22, `RED_AT` 25 → 11, `FAST_RED_AT` 20 → 4 (LED thresholds in minutes-remaining). Across 1,000 simulated workdays the new combination averages 26 minutes of lunch sleep and lands in the 12:15–12:45 sweet spot on ~57 % of days, with zero mean work-time failure and ~2 min/day of after-hours waste. - Bumps `config.device_release` 0x0200 → 0x0300 to match firmware version 0.3.0. - README "Why four hours…" → "Why these timings…", rewritten for the new methodology with the actual run statistics. `src/config.rs` module-level + lifecycle/phase comments updated accordingly. - Picks up a small `cargo fmt` drift in `src/chart.rs` and `src/led.rs` that had crept in under the 0.2.0 module split. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+1
-2
@@ -14,8 +14,7 @@ use crate::config::{
|
||||
};
|
||||
use crate::kbd::{KbdHid, send_kbd, wake_with_keyboard};
|
||||
use crate::led::{
|
||||
Neo, blink_fast_red, boot_sweep, breathe_color, fade_to_green, paint, pulse_blue,
|
||||
pulse_white,
|
||||
Neo, blink_fast_red, boot_sweep, breathe_color, fade_to_green, paint, pulse_blue, pulse_white,
|
||||
};
|
||||
use crate::mouse::{
|
||||
MouseHid, animate_final_spiral, animate_spinner, animate_warning_5, animate_warning_10,
|
||||
|
||||
+13
-15
@@ -1,20 +1,19 @@
|
||||
//! Compile-time tuning constants — timing, geometry, brightness.
|
||||
//!
|
||||
//! Every magic number lives here so the rest of the firmware reads as
|
||||
//! pure behaviour. Several values are joint-tuned by `scripts/tune_runtime.py`
|
||||
//! — see the README's "Why four hours…" section for the rationale.
|
||||
//! pure behaviour. Several values are joint-tuned by the `tuning/` crate
|
||||
//! — see the README's "Why these timings…" section for the rationale.
|
||||
|
||||
use embassy_time::Duration as EDuration;
|
||||
use hsmc::Duration;
|
||||
|
||||
// ── Lifecycle timing (statechart Durations) ────────────────────────
|
||||
// 4h00m: optimum from the 4-D Monte Carlo (RUN_DURATION × YELLOW_AT ×
|
||||
// RED_AT × FAST_RED_AT) over a typical office workday distribution
|
||||
// with a per-minute press-on-warning user model. Lands the screen-sleep
|
||||
// in the 12:15–12:45 sweet spot on ~52 % of days and somewhere in
|
||||
// lunch on ~74 %. See the README's "Why four hours…" section and
|
||||
// `scripts/tune_runtime.py` for the simulation.
|
||||
pub(crate) const RUN_DURATION: Duration = Duration::from_hours(4);
|
||||
// 3h51m: a-posteriori pick from a 4-objective NSGA-III Pareto search
|
||||
// over (RUN_DURATION, YELLOW_AT, RED_AT, FAST_RED_AT) — minimize work-
|
||||
// time failures, maximize lunch sleep, minimize button presses, minimize
|
||||
// after-hours waste. See the README's "Why these timings?" section and
|
||||
// `tuning/` for the search.
|
||||
pub(crate) const RUN_DURATION: Duration = Duration::from_mins(3 * 60 + 51);
|
||||
pub(crate) const SHUTDOWN_LEAD: Duration = Duration::from_secs(30);
|
||||
pub(crate) const RUN_BEFORE_SHUTDOWN: Duration = RUN_DURATION.saturating_sub(SHUTDOWN_LEAD);
|
||||
pub(crate) const SHUTDOWN_ANIM_BUDGET: Duration = Duration::from_secs(5);
|
||||
@@ -23,12 +22,11 @@ pub(crate) const JIGGLE_PERIOD: Duration = Duration::from_secs(270);
|
||||
pub(crate) const FLASH_DURATION: Duration = Duration::from_millis(100);
|
||||
|
||||
// Phase boundaries — compared against time *remaining* in Active.
|
||||
// Joint optimum from `scripts/tune_runtime.py`. Yellow and red are
|
||||
// kept deliberately short (5 min each); the long phase is fast-red.
|
||||
// See the README's "Why four hours…" section for the rationale.
|
||||
pub(crate) const YELLOW_AT: EDuration = EDuration::from_secs(30 * 60);
|
||||
pub(crate) const RED_AT: EDuration = EDuration::from_secs(25 * 60);
|
||||
pub(crate) const FAST_RED_AT: EDuration = EDuration::from_secs(20 * 60);
|
||||
// Joint pick from the NSGA-III Pareto search; see the README's
|
||||
// "Why these timings…" section for the rationale.
|
||||
pub(crate) const YELLOW_AT: EDuration = EDuration::from_secs(22 * 60);
|
||||
pub(crate) const RED_AT: EDuration = EDuration::from_secs(11 * 60);
|
||||
pub(crate) const FAST_RED_AT: EDuration = EDuration::from_secs(4 * 60);
|
||||
|
||||
// LED breathing math
|
||||
pub(crate) const LED_TICK: EDuration = EDuration::from_millis(20);
|
||||
|
||||
+14
-4
@@ -15,8 +15,8 @@ use smart_leds::RGB8;
|
||||
|
||||
use crate::chart::Ev;
|
||||
use crate::config::{
|
||||
BOOT_SWEEP_STEP, BREATHE_FLOOR, BREATHE_PEAK, FAST_RED_AT, FAST_RED_PERIOD, LED_TICK,
|
||||
RED_AT, RED_PERIOD, RUN_BEFORE_SHUTDOWN, SETTLING_PULSE_PERIOD, SLOW_GREEN_PERIOD,
|
||||
BOOT_SWEEP_STEP, BREATHE_FLOOR, BREATHE_PEAK, FAST_RED_AT, FAST_RED_PERIOD, LED_TICK, RED_AT,
|
||||
RED_PERIOD, RUN_BEFORE_SHUTDOWN, SETTLING_PULSE_PERIOD, SLOW_GREEN_PERIOD,
|
||||
SPINNER_FADE_DURATION, WAKING_PULSE_PERIOD, YELLOW_AT, YELLOW_PERIOD,
|
||||
};
|
||||
|
||||
@@ -68,7 +68,12 @@ pub(crate) async fn blink_fast_red(neo: &mut Neo) -> Ev {
|
||||
pub(crate) async fn pulse_blue(neo: &mut Neo) -> Ev {
|
||||
let start = Instant::now();
|
||||
loop {
|
||||
let level = sin_breath(WAKING_PULSE_PERIOD, start.elapsed(), BREATHE_FLOOR, BREATHE_PEAK);
|
||||
let level = sin_breath(
|
||||
WAKING_PULSE_PERIOD,
|
||||
start.elapsed(),
|
||||
BREATHE_FLOOR,
|
||||
BREATHE_PEAK,
|
||||
);
|
||||
paint(neo, 0, 0, level).await;
|
||||
Timer::after(LED_TICK).await;
|
||||
}
|
||||
@@ -77,7 +82,12 @@ pub(crate) async fn pulse_blue(neo: &mut Neo) -> Ev {
|
||||
pub(crate) async fn pulse_white(neo: &mut Neo) -> Ev {
|
||||
let start = Instant::now();
|
||||
loop {
|
||||
let level = sin_breath(SETTLING_PULSE_PERIOD, start.elapsed(), BREATHE_FLOOR, BREATHE_PEAK);
|
||||
let level = sin_breath(
|
||||
SETTLING_PULSE_PERIOD,
|
||||
start.elapsed(),
|
||||
BREATHE_FLOOR,
|
||||
BREATHE_PEAK,
|
||||
);
|
||||
paint(neo, level, level, level).await;
|
||||
Timer::after(LED_TICK).await;
|
||||
}
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ async fn main(spawner: Spawner) {
|
||||
#[cfg(feature = "defmt")]
|
||||
defmt::info!("usb serial: {}", serial);
|
||||
config.serial_number = Some(serial);
|
||||
config.device_release = 0x0200; // matches firmware version 0.2.0
|
||||
config.device_release = 0x0300; // matches firmware version 0.3.0
|
||||
config.max_power = 100;
|
||||
config.max_packet_size_0 = 64;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user