pamoja

An SDK for IoT, robotics, and drones

pamojaRev 0.2.0MIT licensepamoja.molex.cloud

Install#

pamoja is the whole framework in one package, in every language:

Shell
cargo add pamoja                 # Rust
npm install pamoja               # TypeScript and Node
pip install pamoja               # Python
dotnet add package Pamoja        # C# and .NET

That is the right default. Every capability is also a package of its own, and what you gain by picking them differs between Rust and the bindings, so it is worth knowing which before you choose. This page is about that choice; the reference lists every package in every language, and each domain has a page of its own.

Every crate, package, and binding shares one version and is released together, so any package of a release wraps the same release of every other. The changelog covers all of them in one entry.

What picking packages changes#

In Rust, it changes what gets compiled. A crate you do not name is never built, and its dependencies are never fetched, so a narrow build is genuinely smaller and carries less third-party code.

In the bindings it changes what you import, not what you download. Node, Python, and .NET each load one compiled engine that carries every capability, and every package depends on it. Choosing packages narrows the API you see, the manifest you ship, and the code your dependency scanners have to account for. It does not shrink the engine.

Neither is a workaround. Compiling only what you use is a property of a compiled language, and the deployments that need it (a microcontroller with kilobytes of flash) run Rust and could not host a Python or .NET runtime at all.

The two things called core#

A binding has one package you never name and one you sometimes do, and they are easy to confuse.

The compiled engine is @pamoja/native, pamoja-native, and Pamoja.Native. It is the built Rust library, the generated contract over it, and the plumbing every facade needs to call it: the handle type, the error every failed call raises, and string marshalling. Every package declares it, so it arrives on its own and you never install it by hand. Rust has no equivalent, because there you compile the crates.

The engine surface is @pamoja/core, pamoja-core, and Pamoja.Core, the counterpart of the pamoja-core crate. It is the runtime version and Transport, the abstraction MQTT, CoAP, and the loopback all implement. It is a capability like any other, listed first on every reference page, and most packages do not depend on it: only the transports do, because they are the ones that return a Transport. Install it when you want to hold a link behind that interface.

By domain#

Six of the ten headings hold more than one capability, and each of those is also one thing to install: a feature in Rust, and a package in every binding that brings in its capabilities and re-exports each under its own name, so a name two of them share stays unambiguous. Pick a language:

cargo add pamoja --no-default-features --features std,field-io
cargo add pamoja --no-default-features --features std,sensing
cargo add pamoja --no-default-features --features std,radio
cargo add pamoja --no-default-features --features std,trust
cargo add pamoja --no-default-features --features std,profiles

Each row opens the section of that language's reference page that lists what the domain brings in, with the install line and the API pages for every capability in it. Every capability, on its own, is on the reference.

Rust#

Shell
cargo add pamoja                        # every capability, behind a feature each
cargo add pamoja-modbus                 # or one crate on its own

From examples/tests/guides/imports.rs:

Rust
use pamoja::modbus::Adu; // the same type as pamoja_modbus::Adu
use pamoja_codec::CborCodec;

Each module of pamoja is the crate of the same name, so the two ways in share one API and one set of documentation, and code moves between them unchanged.

Naming features is what makes a build small. Measured from the resolved dependency graph, for a x86_64-unknown-linux-gnu build:

BuildWhat you writeCrates compiledFrom this workspaceExternal
Every capabilitycargo add pamoja1243490
Codecs and identitycargo add pamoja --no-default-features --features std,codec,security36432
Field I/Ocargo add pamoja --no-default-features --features std,field-io871
One capabilitycargo add pamoja --no-default-features --features std,modbus541
Bare metal, no stdcargo add pamoja --no-default-features --features modbus,sensors,lora761

The narrow builds carry one third-party crate, embedded-hal, the trait crate the bus layer is written against, and otherwise only pamoja, pamoja-core, and the capability crates. Most capability crates are no_std, so the same code runs on a gateway and on a microcontroller. The Rust reference lists every crate.

TypeScript and Node#

Shell
npm install pamoja                            # every capability
npm install @pamoja/modbus @pamoja/codec      # or only the packages you use
TypeScript
import { readHoldingRegisters } from '@pamoja/modbus'
import { toCbor, fromCbor } from '@pamoja/codec'

Every package depends on @pamoja/native, the compiled engine, prebuilt for Linux (x64, arm64), macOS (x64, arm64), and Windows (x64); npm picks the right one, and installs on anything else without a binary to load. Alpine and Windows on ARM are the two that catch people out. It is one binary carrying every capability whichever packages you install, so the choice is about the API surface and your dependency manifest, not the download. Node 16 or later.

Python#

Shell
pip install pamoja                          # every capability
pip install pamoja-modbus pamoja-codec      # or only the distributions you use
Python
from pamoja.modbus import read_holding_registers
from pamoja.codec import to_cbor, from_cbor

pamoja is a namespace package: each distribution ships one pamoja.<name> module and they merge on import. Every distribution depends on pamoja-native, the compiled engine, with wheels for the same platforms as the Node engine and for Python 3.10 and later; elsewhere pip builds it from the sdist, which needs a Rust toolchain.

C# and .NET#

Shell
dotnet add package Pamoja                        # every capability
dotnet add package Pamoja.Modbus Pamoja.Codec    # or only the packages you use
C#
using Pamoja.Modbus;
using Pamoja.Codec;

A domain package there brings in its capabilities and ships no assembly of its own, since C# has no way to re-export a namespace, so a type is named the way it is when its package is referenced directly.

Each package is one namespace of the same name. Every package depends on Pamoja.Native, which carries the native library for win-x64, linux-x64, linux-arm64, osx-x64, and osx-arm64, and targets .NET 8. Those five are the whole list: on any other runtime identifier, win-arm64 and the musl-based distributions among them, the packages restore and compile and then throw DllNotFoundException on the first call, because there is no native library to load and, unlike Python, no source build to fall back on.

Edit this page on GitHub