Skip to main content

pamoja_kit/
lib.rs

1#![cfg_attr(not(test), no_std)]
2
3//! Goal-named helper math for the pamoja SDK.
4//!
5//! The hardest part of building something real on a cheap sensor is rarely reading
6//! the sensor; it is turning that reading into a decision that holds up in the
7//! field. `pamoja-kit` is the layer that closes that gap for people who are not
8//! signal-processing engineers. Each helper is named for the goal rather than the
9//! technique, ships with correct defaults, and documents the real algorithm one
10//! layer down, so it teaches as it abstracts.
11//!
12//! The first helpers cover the jobs the cookbook leans on most:
13//!
14//! - [`Smoother`] - smooth a noisy reading (exponential moving average).
15//! - [`Kalman`] - settle to a steady value from a noisy sensor (one-dimensional Kalman).
16//! - [`Complementary`] - fuse a fast rate with a slow absolute reading (complementary filter).
17//! - [`Median`] - reject spikes with a rolling median (robust to a lone bad reading).
18//! - [`Debounce`] - clean a chattering on/off signal into one event (counter debounce).
19//! - [`Calibration`] - turn a raw reading into real units (two-point linear map).
20//! - [`deadband`] - ignore small wiggle around a setpoint so an actuator does not chatter.
21//! - [`Thermostat`] - keep a reading near a setpoint (on/off control with
22//!   hysteresis).
23//! - [`Trigger`] - fire once when a reading crosses a line, and not again until it has
24//!   come back (a threshold with hysteresis that reports its edges).
25//! - [`Pid`] - hold a value at a target with a smooth, proportional command (PID control).
26//! - [`Ramp`] - ease a command toward a target at a limited rate (slew-rate limiter).
27//! - [`Depletion`] - warn before a falling level runs out (linear extrapolation).
28//! - [`Surge`] - warn when a reading changes dangerously fast (first difference).
29//! - [`Trend`] - tell whether a value is rising or falling and how fast (least-squares slope).
30//! - [`Anomaly`] - flag a reading that departs from its recent history (three-sigma rule).
31//! - [`Window`] - keep a rolling window of recent readings and read their spread
32//!   (min, max, range, mean, population variance).
33//! - [`units`] - convert a reading to the unit a person reads (Celsius and Fahrenheit,
34//!   pascals to hPa/kPa/psi, ratio and percent).
35//! - [`DiffDrive`] - wheel speeds for a two-wheeled differential chassis, and back.
36//! - [`Ackermann`], [`SkidSteer`], [`Mecanum`] - wheel kinematics for the car-like, skid-steer,
37//!   and omnidirectional chassis (behind `robotics`).
38//! - [`TwoLinkArm`] / [`forward_kinematics`] - manipulator kinematics: the planar two-link inverse,
39//!   and Denavit-Hartenberg forward kinematics for any serial arm (behind `robotics`).
40//! - [`Odometry`] - dead-reckon a [`Pose`] from a body motion or wheel deltas, exact-arc (behind
41//!   `robotics`).
42//! - [`WaypointFollower`] / [`obstacle_stop`] - steer toward a waypoint and stop for an obstacle
43//!   (behind `robotics` and `geo`).
44//! - [`SafetyGate`] - the gate every motion command passes through: emergency [`EStop`], deadman
45//!   [`Watchdog`], and bounded motion ([`Limits`]) (behind `robotics`).
46//! - [`ServoMap`] / [`Esc`] / [`Quadrature`] / [`QuadratureScale`] - servo and ESC pulse widths
47//!   and quadrature-encoder decoding (behind `robotics`).
48//! - [`Twist`] / [`Pose`] - the shared body-velocity and world-pose types the robotics helpers use
49//!   (behind `robotics`).
50//! - [`Coordinate`] / [`Geofence`] - great-circle distance and bearing, and leaving a safe
51//!   area (behind the default `geo` feature).
52//! - [`imu`] - roll and pitch from a three-axis accelerometer (behind the `imu` feature).
53//! - [`weather`] - the dew point from temperature and humidity (behind the `weather` feature).
54//!
55//! The `geo`, `imu`, `weather`, and `robotics` features each pull in `libm` for `no_std` float
56//! math and can be turned off on the most constrained targets; the waypoint guidance needs both
57//! `robotics` and `geo`.
58//!
59//! The crate is `no_std` and allocation-free, so the same helpers run on a
60//! microcontroller and on a server.
61//!
62//! # Readings that are not numbers
63//!
64//! A sensor that fails often reports NaN, and one NaN or infinity folded into an average, an
65//! integral, or a window stays there. So every helper that keeps state ignores a reading that
66//! is not a finite number: its state does not change, a helper that answers with a value
67//! answers with the one it already held, and one that reports events, such as [`Trigger`],
68//! [`Surge`], and [`Depletion`], reports none. [`Anomaly`] is the exception, since such a
69//! reading is the clearest anomaly there is, and flags it.
70//!
71//! The motion helpers fail safe instead: a range that is not a number is an obstacle, a lost
72//! fix or heading stops the waypoint follower, and a time step that is not a number stops
73//! the [`SafetyGate`]. A helper that ignores a reading holds its last decision, so a
74//! thermostat that was heating keeps heating while its probe reads NaN; a node that must fail
75//! safe checks its readings itself. Helpers that keep no state, such as [`deadband`] and
76//! [`Calibration::apply`], return NaN for NaN, as arithmetic does.
77//!
78//! # Examples
79//!
80//! Compose two helpers to hold a noisy fridge probe near 4 C:
81//!
82//! ```
83//! use pamoja_kit::{Smoother, Thermostat};
84//!
85//! let mut probe = Smoother::new(0.5);
86//! let mut fridge = Thermostat::cooling(4.0, 0.5);
87//!
88//! // A warm, noisy reading is smoothed, then drives the cooler on.
89//! let cooler_on = fridge.update(probe.update(7.8));
90//! assert!(cooler_on);
91//! ```
92
93mod anomaly;
94mod calibration;
95mod complementary;
96mod debounce;
97mod depletion;
98mod drive;
99mod kalman;
100mod median;
101mod pid;
102mod ramp;
103mod shape;
104mod smoothing;
105mod surge;
106mod thermostat;
107mod trend;
108mod trigger;
109mod window;
110
111pub mod units;
112
113#[cfg(feature = "geo")]
114mod geo;
115
116#[cfg(feature = "imu")]
117pub mod imu;
118
119#[cfg(feature = "weather")]
120pub mod weather;
121
122#[cfg(feature = "robotics")]
123mod arm;
124#[cfg(feature = "robotics")]
125mod chassis;
126#[cfg(feature = "robotics")]
127mod drivers;
128#[cfg(feature = "robotics")]
129mod motion;
130#[cfg(all(feature = "robotics", feature = "geo"))]
131mod navigate;
132#[cfg(feature = "robotics")]
133mod odometry;
134#[cfg(feature = "robotics")]
135mod safety;
136
137pub use anomaly::Anomaly;
138pub use calibration::Calibration;
139pub use complementary::Complementary;
140pub use debounce::Debounce;
141pub use depletion::Depletion;
142pub use drive::DiffDrive;
143pub use kalman::Kalman;
144pub use median::Median;
145pub use pid::Pid;
146pub use ramp::Ramp;
147pub use shape::deadband;
148pub use smoothing::Smoother;
149pub use surge::Surge;
150pub use thermostat::Thermostat;
151pub use trend::Trend;
152pub use trigger::{Edge, Trigger};
153pub use window::Window;
154
155#[cfg(feature = "geo")]
156pub use geo::{Boundary, Coordinate, Geofence};
157
158#[cfg(feature = "robotics")]
159pub use arm::{forward_kinematics, DhParameters, Elbow, Transform, TwoLinkArm};
160#[cfg(feature = "robotics")]
161pub use chassis::{Ackermann, Mecanum, SkidSteer, WheelSpeeds};
162#[cfg(feature = "robotics")]
163pub use drivers::{Esc, Quadrature, QuadratureScale, ServoMap};
164#[cfg(feature = "robotics")]
165pub use motion::{Pose, Twist};
166#[cfg(all(feature = "robotics", feature = "geo"))]
167pub use navigate::{obstacle_stop, Guidance, WaypointFollower};
168#[cfg(feature = "robotics")]
169pub use odometry::Odometry;
170#[cfg(feature = "robotics")]
171pub use safety::{EStop, Limits, SafetyGate, Watchdog};