# pamoja
The whole pamoja device SDK in one crate: every capability behind a feature, all on by default, for IoT, robotics, and drones.
<a href="https://pamoja.molex.cloud/docs/reference/rust/pamoja/index.html"><img height="28" alt="API reference" src="https://raw.githubusercontent.com/molexxxx/pamoja/main/.github/badges/btn-api.svg"></a>
<a href="https://crates.io/crates/pamoja"><img height="28" alt="crates.io" src="https://raw.githubusercontent.com/molexxxx/pamoja/main/.github/badges/btn-cratesio.svg"></a>
<a href="https://docs.rs/pamoja"><img height="28" alt="docs.rs" src="https://raw.githubusercontent.com/molexxxx/pamoja/main/.github/badges/btn-docsrs.svg"></a>
<a href="https://www.npmjs.com/package/pamoja"><img height="28" alt="npm" src="https://raw.githubusercontent.com/molexxxx/pamoja/main/.github/badges/btn-npm.svg"></a>
<a href="https://pypi.org/project/pamoja/"><img height="28" alt="PyPI" src="https://raw.githubusercontent.com/molexxxx/pamoja/main/.github/badges/btn-pypi.svg"></a>
<a href="https://www.nuget.org/packages/Pamoja"><img height="28" alt="NuGet" src="https://raw.githubusercontent.com/molexxxx/pamoja/main/.github/badges/btn-nuget.svg"></a>
The full API reference is [on the site](https://pamoja.molex.cloud/docs/reference/rust/pamoja/index.html), and the copy for each published version is on [docs.rs](https://docs.rs/pamoja).
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:
```rust
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);
```
# Features
One feature per capability, named as its crate is without the prefix, and all
on by default:
| (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:
| `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.
## License
MIT - part of the [pamoja](https://github.com/molexxxx/pamoja) workspace: one memory-safe Rust core with bindings for every language.