1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
//! The whole pamoja device SDK in one crate.
//!
//! pamoja is one memory-safe Rust core with a crate per capability, so a build
//! carries only the crates it names. This crate is the other way in: every
//! capability sits behind a feature, all on by default, so `cargo add pamoja` is
//! the whole framework, the way `npm install pamoja`, `pip install pamoja`, and
//! `dotnet add package Pamoja` are in the bindings.
//!
//! Each module re-exports the crate of the same name: `pamoja::codec` is
//! `pamoja-codec`, `pamoja::mqtt` is `pamoja-mqtt`, and `pamoja::core` is
//! `pamoja-core`, the traits every capability implements. The types, the
//! documentation, and the examples are those of the crate, so code moves between
//! `use pamoja::codec::CborCodec` and `use pamoja_codec::CborCodec` with no other change.
//!
//! ```toml
//! [dependencies]
//! pamoja = "0.1"
//! ```
//!
//! A build that needs only some capabilities names them, and takes on only
//! their dependencies:
//!
//! ```toml
//! [dependencies]
//! pamoja = { version = "0.1", default-features = false, features = ["std", "codec", "security"] }
//! ```
//!
//! # Example
//!
//! A reading taken off a wire, smoothed, packed for a metered link, and signed so
//! the gateway that receives it can tell which device sent it, with nothing plugged
//! in:
//!
//! ```
//! use pamoja::codec::{decode_deltas, encode_deltas};
//! use pamoja::kit::Smoother;
//! use pamoja::security::{DeviceIdentity, PublicIdentity};
//! use pamoja::sensors::ds18b20::{temperature_from_celsius, Resolution, Scratchpad};
//!
//! // A stand-in for the thermometer. On a running node these nine bytes arrive from
//! // the 1-Wire bus; here the library builds what a part at 25.0625 C would send.
//! let off_the_bus = Scratchpad::new(
//! temperature_from_celsius(25.0625, Resolution::Bits12),
//! Resolution::Bits12,
//! 75,
//! -10,
//! )
//! .to_bytes();
//!
//! // The part checksums every read, so a value mangled on a long run is an error
//! // rather than a plausible temperature a couple of degrees off.
//! let celsius = Scratchpad::parse(&off_the_bus)
//! .expect("the checksum matches")
//! .temperature_celsius();
//! assert_eq!(celsius, 25.0625);
//!
//! // Readings jitter, so smooth them and send a batch rather than one at a time.
//! let mut smoother = Smoother::new(0.5);
//! let batch: Vec<i64> = [celsius, celsius + 0.5, celsius + 0.4]
//! .into_iter()
//! .map(|sample| (smoother.update(sample) * 100.0).round() as i64)
//! .collect();
//! let packed = encode_deltas(&batch);
//! assert!(packed.len() < batch.len() * 8);
//!
//! // Sign the batch. The signature travels with the payload as one message, so a
//! // gateway holding only the public key gets the payload back once it checks out.
//! let device = DeviceIdentity::from_seed(&[7u8; 32]);
//! let message = device.sign_message(&packed);
//!
//! let known = PublicIdentity::from_bytes(&device.public().to_bytes())?;
//! let payload = known.verify_message(&message)?;
//! assert_eq!(decode_deltas(payload).expect("a valid batch"), batch);
//! # Ok::<(), pamoja::core::Error>(())
//! ```
//!
//! # Features
//!
//! One feature per capability, named as its crate is without the prefix, and all
//! on by default:
//!
//! | Feature | Module | Crate |
//! | --- | --- | --- |
//! | (always) | `pamoja::core` | [pamoja-core](https://docs.rs/pamoja-core) |
//! | `security` | `pamoja::security` | [pamoja-security](https://docs.rs/pamoja-security) |
//! | `codec` | `pamoja::codec` | [pamoja-codec](https://docs.rs/pamoja-codec) |
//! | `kit` | `pamoja::kit` | [pamoja-kit](https://docs.rs/pamoja-kit) |
//! | `serial` | `pamoja::serial` | [pamoja-serial](https://docs.rs/pamoja-serial) |
//! | `modbus` | `pamoja::modbus` | [pamoja-modbus](https://docs.rs/pamoja-modbus) |
//! | `can` | `pamoja::can` | [pamoja-can](https://docs.rs/pamoja-can) |
//! | `gpio` | `pamoja::gpio` | [pamoja-gpio](https://docs.rs/pamoja-gpio) |
//! | `sensors` | `pamoja::sensors` | [pamoja-sensors](https://docs.rs/pamoja-sensors) |
//! | `actuators` | `pamoja::actuators` | [pamoja-actuators](https://docs.rs/pamoja-actuators) |
//! | `lora` | `pamoja::lora` | [pamoja-lora](https://docs.rs/pamoja-lora) |
//! | `lorawan` | `pamoja::lorawan` | [pamoja-lorawan](https://docs.rs/pamoja-lorawan) |
//! | `mesh` | `pamoja::mesh` | [pamoja-mesh](https://docs.rs/pamoja-mesh) |
//! | `routing` | `pamoja::routing` | [pamoja-routing](https://docs.rs/pamoja-routing) |
//! | `mavlink` | `pamoja::mavlink` | [pamoja-mavlink](https://docs.rs/pamoja-mavlink) |
//! | `audit` | `pamoja::audit` | [pamoja-audit](https://docs.rs/pamoja-audit) |
//! | `session` | `pamoja::session` | [pamoja-session](https://docs.rs/pamoja-session) |
//! | `update` | `pamoja::update` | [pamoja-update](https://docs.rs/pamoja-update) |
//! | `power` | `pamoja::power` | [pamoja-power](https://docs.rs/pamoja-power) |
//! | `telemetry` | `pamoja::telemetry` | [pamoja-telemetry](https://docs.rs/pamoja-telemetry) |
//! | `mqtt` | `pamoja::mqtt` | [pamoja-mqtt](https://docs.rs/pamoja-mqtt) |
//! | `coap` | `pamoja::coap` | [pamoja-coap](https://docs.rs/pamoja-coap) |
//! | `loopback` | `pamoja::loopback` | [pamoja-loopback](https://docs.rs/pamoja-loopback) |
//! | `sync` | `pamoja::sync` | [pamoja-sync](https://docs.rs/pamoja-sync) |
//! | `ladder` | `pamoja::ladder` | [pamoja-ladder](https://docs.rs/pamoja-ladder) |
//! | `bus` | `pamoja::bus` | [pamoja-bus](https://docs.rs/pamoja-bus) |
//! | `sim` | `pamoja::sim` | [pamoja-sim](https://docs.rs/pamoja-sim) |
//! | `profile` | `pamoja::profile` | [pamoja-profile](https://docs.rs/pamoja-profile) |
//! | `ros2` | `pamoja::ros2` | [pamoja-ros2](https://docs.rs/pamoja-ros2) |
//! | `zenoh` | `pamoja::zenoh` | [pamoja-zenoh](https://docs.rs/pamoja-zenoh) |
//! | `dashboard` (off by default) | `pamoja::dashboard` | [pamoja-dashboard](https://docs.rs/pamoja-dashboard) |
//!
//! Six of those capabilities' chapters hold more than one capability, and each has a
//! feature that turns on exactly its own, so a build can name a domain instead of listing
//! its parts. They are checked against the capability map, so a new capability cannot fall
//! out of its group:
//!
//! | Group feature | Turns on |
//! | --- | --- |
//! | `field-io` | `serial`, `modbus`, `can`, `gpio` |
//! | `sensing` | `sensors`, `actuators` |
//! | `radio` | `lora`, `lorawan`, `mesh`, `routing` |
//! | `trust` | `audit`, `session`, `update`, `power`, `telemetry` |
//! | `transports` | `mqtt`, `coap`, `loopback`, `sync`, `ladder`, `bus`, `sim` |
//! | `profiles` | `profile`, `ros2`, `zenoh` |
//!
//! ```toml
//! [dependencies]
//! pamoja = { version = "0.1", default-features = false, features = ["std", "field-io"] }
//! ```
//!
//! `std`, on by default, turns on the standard-library layer of the crates that
//! have one (`pamoja-core`, `pamoja-lora`, `pamoja-mavlink`) and implies `alloc`,
//! which adds the owned channel plans, tables, and message shapes of `pamoja-lora`,
//! `pamoja-mesh`, `pamoja-routing`, and `pamoja-mavlink`. With both off and only
//! `no_std` capabilities named, the crate builds for a bare-metal target; CI
//! compiles it for `thumbv7em-none-eabihf`. The crates keep their finer switches
//! (the LoRa region set, the kit's helper groups, the MAVLink serial driver), so
//! depend on the crate itself when you need one of those. `dashboard` adds the
//! fleet dashboard, a web server, and is off by default.
pub use pamoja_core as core;
pub use pamoja_actuators as actuators;
pub use pamoja_audit as audit;
pub use pamoja_bus as bus;
pub use pamoja_can as can;
pub use pamoja_coap as coap;
pub use pamoja_codec as codec;
pub use pamoja_dashboard as dashboard;
pub use pamoja_gpio as gpio;
pub use pamoja_kit as kit;
pub use pamoja_ladder as ladder;
pub use pamoja_loopback as loopback;
pub use pamoja_lora as lora;
pub use pamoja_lorawan as lorawan;
pub use pamoja_mavlink as mavlink;
pub use pamoja_mesh as mesh;
pub use pamoja_modbus as modbus;
pub use pamoja_mqtt as mqtt;
pub use pamoja_power as power;
pub use pamoja_profile as profile;
pub use pamoja_ros2 as ros2;
pub use pamoja_routing as routing;
pub use pamoja_security as security;
pub use pamoja_sensors as sensors;
pub use pamoja_serial as serial;
pub use pamoja_session as session;
pub use pamoja_sim as sim;
pub use pamoja_sync as sync;
pub use pamoja_telemetry as telemetry;
pub use pamoja_update as update;
pub use pamoja_zenoh as zenoh;