nervous: the plexus declares the phone's body and speaks it
phone_plexus leaves the test module and takes the clock. It grows `attended`, derived by sessiond rather than reported by a sensor, and the two grip gauges that nothing reports yet — the gap on the record instead of a false calm. to_json renders every field's belief, trend, locus, health and pressure, the ranked pressures, homeostatic cost and the unreachable list for the IPC surface; unknown stays null, never zero.
This commit is contained in:
parent
46876865c9
commit
f3fcdd0cc4
1 changed files with 123 additions and 37 deletions
|
|
@ -17,10 +17,12 @@
|
|||
//!
|
||||
//! Fields are named rather than struct'd, so a new afferent arrives as a
|
||||
//! builder call. The phone currently offers: proximity, motion, light,
|
||||
//! touch (iio); charge and thermal (power); and the USB-C port — attached,
|
||||
//! data role, peer display, peer input, peer network — which usb-signaller
|
||||
//! and smoo make legible. Grip, per gauge rather than squeezed to a bool,
|
||||
//! is owed.
|
||||
//! touch (iio); charge and thermal (power); `attended`, which sessiond's
|
||||
//! clock derives from those; and the USB-C port — attached, data role, peer
|
||||
//! display, peer input, peer network — which usb-signaller and smoo make
|
||||
//! legible. Grip, per gauge rather than squeezed to a bool, is declared as
|
||||
//! `grip_left` / `grip_right` and reads unknown until the device-side
|
||||
//! reporter exists — the gap made visible, not a false calm.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
|
|
@ -512,6 +514,87 @@ impl SomaticPlexus {
|
|||
.sum()
|
||||
}
|
||||
|
||||
/// The body as it speaks on the IPC surface: every field's belief, trend,
|
||||
/// locus and source health, the bounded fields' pressures ranked worst
|
||||
/// first, and the unreachable list. Unknown stays null — never zero.
|
||||
pub fn to_json(&self, now: DateTime<Utc>) -> serde_json::Value {
|
||||
let fields: serde_json::Map<String, serde_json::Value> = self
|
||||
.fields
|
||||
.iter()
|
||||
.map(|e| {
|
||||
let belief = e.belief(now);
|
||||
let locus = match &e.locus {
|
||||
Locus::Inner { regulator } => serde_json::json!({
|
||||
"side": "inner",
|
||||
"regulator": regulator,
|
||||
}),
|
||||
Locus::Boundary => serde_json::json!({ "side": "boundary" }),
|
||||
};
|
||||
let name = e.name.clone();
|
||||
let value = serde_json::json!({
|
||||
"value": belief.value,
|
||||
"confidence": belief.confidence_at(now),
|
||||
"trend": trend_str(belief.trend),
|
||||
"locus": locus,
|
||||
"health": self.source_health(&name),
|
||||
"pressure": self.allostatic_pressure(&name, now),
|
||||
});
|
||||
(name, value)
|
||||
})
|
||||
.collect();
|
||||
let pressures: Vec<serde_json::Value> = self
|
||||
.pressures(now)
|
||||
.into_iter()
|
||||
.map(|(name, p)| serde_json::json!([name, p]))
|
||||
.collect();
|
||||
serde_json::json!({
|
||||
"fields": fields,
|
||||
"pressures": pressures,
|
||||
"homeostatic_cost": self.homeostatic_cost(now),
|
||||
"unreachable": self.unreachable(),
|
||||
})
|
||||
}
|
||||
|
||||
/// The phone's afferents as they stand — sessiond's field set, built
|
||||
/// here so the tests and the daemon read one list.
|
||||
///
|
||||
/// iio at the boundary; charge and thermal inside it with their verbs;
|
||||
/// `attended` derived by sessiond's clock rather than reported by a
|
||||
/// sensor; and the two grip gauges, which nothing reports yet.
|
||||
pub fn phone_plexus(now: DateTime<Utc>) -> SomaticPlexus {
|
||||
SomaticPlexus::new(now)
|
||||
.with_boundary_field("proximity", Duration::from_secs(30), now)
|
||||
.with_boundary_field("motion", Duration::from_secs(60), now)
|
||||
.with_boundary_field("light", Duration::from_secs(120), now)
|
||||
.with_boundary_field("touch", Duration::from_secs(10), now)
|
||||
.with_boundary_field("attended", Duration::from_secs(20), now)
|
||||
.with_inner_level(
|
||||
"charge",
|
||||
Duration::from_secs(300),
|
||||
Some("doze"),
|
||||
Some(Viability::floor(0.2)),
|
||||
)
|
||||
.with_inner_level(
|
||||
"thermal",
|
||||
Duration::from_secs(120),
|
||||
Some("doze"),
|
||||
Some(Viability::ceiling(0.8)),
|
||||
)
|
||||
.with_inner_level(
|
||||
"port_mode",
|
||||
Duration::from_secs(600),
|
||||
Some("set_usb_mode"),
|
||||
None,
|
||||
)
|
||||
.with_boundary_level("grip_left", Duration::from_secs(30))
|
||||
.with_boundary_level("grip_right", Duration::from_secs(30))
|
||||
.with_expected("proximity")
|
||||
.with_expected("light")
|
||||
.with_expected("charge")
|
||||
.with_silent_after(Duration::from_secs(90))
|
||||
.with_absent_after(Duration::from_secs(300))
|
||||
}
|
||||
|
||||
fn evaluate_health(&mut self, now: DateTime<Utc>) {
|
||||
let newly_silent: Vec<(String, u64)> = self
|
||||
.last_seen
|
||||
|
|
@ -648,6 +731,16 @@ impl SomaticPlexus {
|
|||
}
|
||||
}
|
||||
|
||||
fn trend_str(t: Trend) -> &'static str {
|
||||
match t {
|
||||
Trend::FallingFast => "falling_fast",
|
||||
Trend::Falling => "falling",
|
||||
Trend::Stable => "stable",
|
||||
Trend::Rising => "rising",
|
||||
Trend::RisingFast => "rising_fast",
|
||||
}
|
||||
}
|
||||
|
||||
impl NervousNode for SomaticPlexus {
|
||||
type Input = SensorReading;
|
||||
type Output = f32;
|
||||
|
|
@ -707,38 +800,9 @@ mod tests {
|
|||
t0() + chrono::Duration::seconds(secs)
|
||||
}
|
||||
|
||||
/// The phone's afferents as they stand: iio at the boundary, power and
|
||||
/// port inside it.
|
||||
/// The production field set, on the test clock.
|
||||
fn phone_plexus() -> SomaticPlexus {
|
||||
let now = t0();
|
||||
SomaticPlexus::new(now)
|
||||
.with_boundary_field("proximity", Duration::from_secs(30), now)
|
||||
.with_boundary_field("motion", Duration::from_secs(60), now)
|
||||
.with_boundary_field("light", Duration::from_secs(120), now)
|
||||
.with_boundary_field("touch", Duration::from_secs(10), now)
|
||||
.with_inner_level(
|
||||
"charge",
|
||||
Duration::from_secs(300),
|
||||
Some("doze"),
|
||||
Some(Viability::floor(0.2)),
|
||||
)
|
||||
.with_inner_level(
|
||||
"thermal",
|
||||
Duration::from_secs(120),
|
||||
Some("doze"),
|
||||
Some(Viability::ceiling(0.8)),
|
||||
)
|
||||
.with_inner_level(
|
||||
"port_mode",
|
||||
Duration::from_secs(600),
|
||||
Some("set_usb_mode"),
|
||||
None,
|
||||
)
|
||||
.with_expected("proximity")
|
||||
.with_expected("light")
|
||||
.with_expected("charge")
|
||||
.with_silent_after(Duration::from_secs(90))
|
||||
.with_absent_after(Duration::from_secs(300))
|
||||
super::phone_plexus(t0())
|
||||
}
|
||||
|
||||
fn read(source: &str, amount: f32) -> SensorReading {
|
||||
|
|
@ -1131,7 +1195,7 @@ mod tests {
|
|||
p.ingest(read("motion", 0.3), t0());
|
||||
|
||||
let beliefs = p.beliefs(after(1));
|
||||
assert_eq!(beliefs.len(), 7);
|
||||
assert_eq!(beliefs.len(), 10);
|
||||
assert!(beliefs[0].is_known());
|
||||
assert!(beliefs[0].value.unwrap() > 0.3);
|
||||
assert!(beliefs[1].is_known());
|
||||
|
|
@ -1151,6 +1215,28 @@ mod tests {
|
|||
let mut p = phone_plexus();
|
||||
p.ingest(read("usb_peer", 1.0), t0());
|
||||
assert_eq!(p.source_health("usb_peer"), SourceHealth::Live);
|
||||
assert_eq!(p.fields.len(), 7, "no field is conjured for it");
|
||||
assert_eq!(p.fields.len(), 10, "no field is conjured for it");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_json_carries_unknown_as_null_not_zero() {
|
||||
let p = phone_plexus();
|
||||
let body = p.to_json(after(1));
|
||||
let light = &body["fields"]["light"];
|
||||
assert_eq!(
|
||||
light["value"],
|
||||
serde_json::Value::Null,
|
||||
"never spoke must read as unknown, not calm"
|
||||
);
|
||||
assert_eq!(
|
||||
body["fields"]["attended"]["trend"].as_str(),
|
||||
Some("stable")
|
||||
);
|
||||
assert_eq!(
|
||||
body["fields"]["charge"]["locus"]["side"].as_str(),
|
||||
Some("inner")
|
||||
);
|
||||
assert!(body["pressures"].as_array().unwrap().is_empty());
|
||||
assert!(body["unreachable"].as_array().unwrap().is_empty());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue