sensord: stop claiming the accelerometer
Measured on the phone, idle, screen on, same session, one flag apart: monitor-sensor --proximity --light --accel iio-sensor-proxy 15.3% monitor-sensor --proximity --light iio-sensor-proxy 1.1% Claiming the accelerometer makes iio-sensor-proxy poll the IIO device continuously; nothing else here does. Fourteen points of a core, forever, for the reading §4 weights least (+0.3) and calls a weak signal — and one the keepalive had to actively decay, because monitor-sensor speaks only on orientation change, so a stationary phone paid the full poll cost to report nothing. The parser, the motion decay and MOTION_WINDOW go with it; all three existed only to turn orientation into a motion edge. The 2026-07-27 finding behind the decay is kept in comment — 165 Moving(true) against 8 Moving(false) on one boot — because whatever reports motion next owes a decay too. This does not say accel is unwanted. It says a subprocess holding a continuous claim is the wrong way to get it; TASK-36's SLPI batching is the right one. Restore the source together with that, or the cost returns.
This commit is contained in:
parent
f320a0221d
commit
6b67512c4f
1 changed files with 51 additions and 78 deletions
|
|
@ -202,34 +202,13 @@ fn parse_light(line: &str, state: &mut ParserState) -> Option<serde_json::Value>
|
|||
Some(serde_json::json!({ "changing": changing }))
|
||||
}
|
||||
|
||||
/// `Accelerometer orientation changed: normal`
|
||||
///
|
||||
/// The wire carries `Moving(bool)`. iio-sensor-proxy reports orientation, not
|
||||
/// motion, so movement is inferred from the orientation changing at all — which
|
||||
/// is a weak signal and is exactly why §4 gives accel 0.3 and not more. A phone
|
||||
/// being carried face-up in a steady hand reports nothing.
|
||||
fn parse_accel(line: &str, state: &mut ParserState) -> Option<serde_json::Value> {
|
||||
let orientation = line.rsplit(':').next()?.trim().to_string();
|
||||
let changed = match &state.last_orientation {
|
||||
Some(prev) => *prev != orientation,
|
||||
None => false,
|
||||
};
|
||||
state.last_orientation = Some(orientation);
|
||||
if changed {
|
||||
state.last_orientation_change = Some(Instant::now());
|
||||
}
|
||||
Some(serde_json::json!({ "moving": accel_moving(state, Instant::now()) }))
|
||||
}
|
||||
|
||||
/// Whether the device counts as moving right now: an orientation change inside
|
||||
/// `MOTION_WINDOW`. Shared by the parser and the keepalive so the decay has
|
||||
/// exactly one definition.
|
||||
fn accel_moving(state: &ParserState, now: Instant) -> bool {
|
||||
match state.last_orientation_change {
|
||||
Some(t) => now.duration_since(t) < MOTION_WINDOW,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
// The accelerometer parser and its motion decay lived here. They went with the
|
||||
// accel claim itself — see SOURCES for the measurement — because both existed
|
||||
// only to turn iio-sensor-proxy's *orientation* into a motion edge, and there
|
||||
// is no orientation stream when the source is not claimed. The inference was
|
||||
// always weak (a phone carried face-up in a steady hand reported nothing),
|
||||
// which is why §4 weighted it 0.3. TASK-36's SLPI batching reports motion
|
||||
// directly and does not need any of this.
|
||||
|
||||
/// Per-source memory the parsers need to turn levels into edges.
|
||||
#[derive(Default)]
|
||||
|
|
@ -242,14 +221,9 @@ struct ParserState {
|
|||
/// moment the level falls back inside the band.
|
||||
pending_since: Option<Instant>,
|
||||
pending_brightening: bool,
|
||||
last_orientation: Option<String>,
|
||||
/// When orientation last changed. `MOTION_WINDOW` decays from here, and the
|
||||
/// keepalive reads it to clear a stale `moving: true`.
|
||||
last_orientation_change: Option<Instant>,
|
||||
}
|
||||
|
||||
/// Shared so the keepalive thread can decay motion; the read loop owns the
|
||||
/// writes.
|
||||
/// Shared between the read loop and the keepalive snapshot.
|
||||
type Parser = Arc<Mutex<ParserState>>;
|
||||
|
||||
/// How much lux must move before it counts as "changing", measured against the
|
||||
|
|
@ -278,19 +252,15 @@ const LIGHT_CHANGE_LUX: f64 = 15.0;
|
|||
const LIGHT_BRIGHTENING_DEBOUNCE: Duration = Duration::from_millis(2000);
|
||||
const LIGHT_DARKENING_DEBOUNCE: Duration = Duration::from_millis(4000);
|
||||
|
||||
/// How long after an orientation change the device still counts as moving.
|
||||
///
|
||||
/// iio-sensor-proxy emits a line only when orientation CHANGES, so "moving"
|
||||
/// inferred from an edge can never fall back to false on its own — measured on
|
||||
/// device 2026-07-27, boot 0 carried 165 `Moving(true)` against 8
|
||||
/// `Moving(false)`, all the falses at startup. The keepalive then re-sent a
|
||||
/// stuck `true` every 30 s, pinning accel's +0.3 into `observed_confidence` for
|
||||
/// the life of the process and holding the machine permanently at or above the
|
||||
/// 0.3 "suppress DPMS wake" band.
|
||||
///
|
||||
/// So motion decays. An orientation change means moving for this long, and the
|
||||
/// keepalive clears it once the window passes.
|
||||
const MOTION_WINDOW: Duration = Duration::from_secs(3);
|
||||
// MOTION_WINDOW lived here: how long after an orientation change the device
|
||||
// still counted as moving. Kept in the history rather than the binary, because
|
||||
// the finding behind it survives the source being dropped and TASK-36 will need
|
||||
// it again — measured on device 2026-07-27, boot 0 carried 165 `Moving(true)`
|
||||
// against 8 `Moving(false)`, all the falses at startup. An edge-derived
|
||||
// "moving" can never fall back to false on its own, so the keepalive re-sent a
|
||||
// stuck `true` every 30 s, pinning accel's +0.3 into `observed_confidence` for
|
||||
// the life of the process and holding the machine at or above the 0.3
|
||||
// "suppress DPMS wake" band. Whatever reports motion next owes a decay.
|
||||
|
||||
/// No source may report more often than this.
|
||||
///
|
||||
|
|
@ -319,42 +289,45 @@ const SOURCES: &[Source] = &[
|
|||
banner_marker: "Has ambient light sensor",
|
||||
parse: parse_light,
|
||||
},
|
||||
Source {
|
||||
// "accelerometer" on the wire, not "accel". SensorSource derives its
|
||||
// JSON from snake_case variant names; `as_str()`'s shorter "accel" is
|
||||
// the trail/health key only, and sending it here earns a refusal.
|
||||
name: "accelerometer",
|
||||
flag: "--accel",
|
||||
change_marker: "Accelerometer orientation changed: ",
|
||||
banner_marker: "Has accelerometer",
|
||||
parse: parse_accel,
|
||||
},
|
||||
// The accelerometer is NOT claimed, and that is a power decision.
|
||||
//
|
||||
// Measured on the phone 2026-08-04, idle, screen on, same session, one flag
|
||||
// apart:
|
||||
//
|
||||
// monitor-sensor --proximity --light --accel iio-sensor-proxy 15.3%
|
||||
// monitor-sensor --proximity --light iio-sensor-proxy 1.1%
|
||||
//
|
||||
// Claiming the accelerometer makes iio-sensor-proxy poll the IIO device
|
||||
// continuously; nothing else here does. So ~14 points of a core were being
|
||||
// spent, forever, on the one reading §4 gives the least weight (+0.3) and
|
||||
// that §9's own note calls a weak signal — and which the keepalive had to
|
||||
// actively decay because monitor-sensor only speaks on orientation change,
|
||||
// meaning a stationary phone paid the full poll cost to report nothing.
|
||||
//
|
||||
// This is the cheap half of TASK-15. It does not say accel is unwanted: it
|
||||
// says a subprocess holding a continuous claim is the wrong way to get it.
|
||||
// TASK-36's SLPI batching is the right one — the sensor hub already
|
||||
// aggregates motion and can report on an interval instead of being polled.
|
||||
// Restore this entry only together with that, or the cost comes back.
|
||||
//
|
||||
// "accelerometer" was the name on the wire, not "accel": SensorSource
|
||||
// derives its JSON from snake_case variant names, and the shorter form is
|
||||
// the trail/health key only. Noted here so a future restore does not
|
||||
// rediscover the refusal.
|
||||
];
|
||||
|
||||
/// Re-send every source's last reading forever. This is the half that makes
|
||||
/// silence meaningful; see the module docs.
|
||||
fn spawn_keepalive(sock: PathBuf, last: LastSeen, parser: Parser) {
|
||||
fn spawn_keepalive(sock: PathBuf, last: LastSeen) {
|
||||
std::thread::spawn(move || loop {
|
||||
std::thread::sleep(KEEPALIVE);
|
||||
// Decay motion before snapshotting. monitor-sensor will not emit
|
||||
// another accelerometer line until the orientation changes AGAIN, so if
|
||||
// the keepalive did not re-derive this the last `moving: true` would be
|
||||
// re-sent forever and accel's +0.3 would never leave the confidence sum.
|
||||
{
|
||||
let state = match parser.lock() {
|
||||
Ok(g) => g,
|
||||
Err(p) => p.into_inner(),
|
||||
};
|
||||
let moving = accel_moving(&state, Instant::now());
|
||||
drop(state);
|
||||
let mut guard = match last.lock() {
|
||||
Ok(g) => g,
|
||||
Err(p) => p.into_inner(),
|
||||
};
|
||||
if let Some(v) = guard.get_mut("accelerometer") {
|
||||
*v = serde_json::json!({ "moving": moving });
|
||||
}
|
||||
}
|
||||
// The motion decay that used to run here went with the accelerometer
|
||||
// claim (see SOURCES). It existed because monitor-sensor only speaks on
|
||||
// orientation change, so a stale `moving: true` would otherwise be
|
||||
// re-sent forever and pin accel's +0.3 into the confidence sum. With
|
||||
// the source unclaimed there is no accelerometer entry to decay, and
|
||||
// leaving the decay in would have been a loop deriving a value nothing
|
||||
// reports. It comes back with the source, not before.
|
||||
let snapshot: Vec<(&'static str, serde_json::Value)> = {
|
||||
let guard = match last.lock() {
|
||||
Ok(g) => g,
|
||||
|
|
@ -384,7 +357,7 @@ fn main() {
|
|||
// reset the lux reference or re-arm motion, or every SLPI blip would look
|
||||
// like a fresh change.
|
||||
let parser: Parser = Arc::new(Mutex::new(ParserState::default()));
|
||||
spawn_keepalive(sock.clone(), Arc::clone(&last), Arc::clone(&parser));
|
||||
spawn_keepalive(sock.clone(), Arc::clone(&last));
|
||||
|
||||
// One monitor-sensor for every source, respawned if it dies. Restarting the
|
||||
// process is the recovery path for a sensor stack that came back after an
|
||||
|
|
|
|||
Loading…
Reference in a new issue