senml – Sensor Markup Language

This module implements a small encoder/decoder for the Sensor Markup Language (SenML, RFC 8428). SenML is a media-type for sensor measurements and device parameters: each “pack” is a list of “records”, where every record carries a name, unit, value, timestamp, and optional sum.

The implementation supports both the JSON and CBOR representations and allows packs to be nested so that a single root pack can describe a gateway fronting multiple devices. Inbound payloads can drive actuator callbacks on existing records.

CBOR encoding/decoding requires the cbor2 package.

Classes

class senml.SenmlPack(name: str, callback=None)

Represents a SenML pack – a collection of SenmlRecord instances and optionally other child SenmlPack objects. When a pack only contains records it represents a single device; when it contains other packs it acts as a gateway.

name is the SenML base name (bn) used for every record contained in this pack.

callback is invoked from from_json / from_cbor whenever an inbound record names a previously-unknown sensor; the new SenmlRecord is passed as the first argument and (for nested packs) the originating device pack is passed as device=.... It is typically used to handle actuator commands.

SenmlPack instances are iterable – iteration yields each record in insertion order – and may be used as a context manager so that on exit the pack removes itself from its parent.

name: str

The pack’s base name (bn).

base_value: int | float | None

Optional base value (bv) added to each record’s numeric value when encoding and subtracted on decoding. Setting a non-numeric value raises Exception.

base_time: int | float | None

Optional base time (bt) added to each record’s timestamp.

base_sum: int | float | None

Optional base sum (bs) added to each record’s sum field.

base_unit: str | None

Optional base unit (bu) – typically a value from SenmlUnits.

actuate

The callback supplied at construction time. May be re-assigned at runtime.

add(item: SenmlRecord | SenmlPack) None

Append item to this pack. item must be a SenmlRecord or another SenmlPack and must not already belong to a different parent; otherwise Exception is raised.

remove(item: SenmlRecord | SenmlPack) None

Remove item from this pack. Exception is raised if item is not a child of this pack.

clear() None

Remove every record/sub-pack from this pack and detach them from their parent reference.

from_json(data: str) None

Parse a SenML/JSON document and merge the records into this pack. Records that already exist (matched by name) trigger SenmlRecord.do_actuate(); new records are appended and the pack-level callback is invoked.

to_json() str

Render the pack and its children to a SenML/JSON string.

from_cbor(data: bytes) None

Parse a SenML/CBOR byte string and merge the records into this pack.

to_cbor() bytes

Render the pack and its children to a SenML/CBOR byte string.

do_actuate(raw: dict, naming_map: dict, device: SenmlPack | None = None) None

Internal helper invoked while parsing inbound data when no existing record matches an entry. Adds a new SenmlRecord to device (or to this pack) and forwards it to the callback.

class senml.SenmlRecord(name: str, **kwargs)

Represents a single measurement inside a SenmlPack.

name is the SenML record name (n).

The following keyword arguments are accepted:

  • valuebool, int, float, str or bytearray. Other types raise Exception.

  • time – numeric timestamp (t).

  • unit – a unit string, typically a member of SenmlUnits.

  • sum – numeric integrated sum (s).

  • update_time – maximum time before the sensor will provide a fresh reading (ut).

  • callback – function invoked when an inbound payload updates this record. It receives the SenmlRecord as its only argument.

SenmlRecord may be used as a context manager so that on exit it removes itself from its parent pack.

name: str

Record name (n).

value

The current value. Re-assigning checks the type; only bool, numbers, str and bytearray are accepted. To control the rendered precision of a float value, round before assignment, e.g. record.value = round(x, 2).

unit: str | None

Unit string (u).

time: int | float | None

Timestamp associated with this measurement (t).

update_time: int | float | None

Maximum time before the sensor will provide an updated reading (ut).

sum: int | float | None

Integrated sum field (s).

actuate

The callback supplied at construction time. May be re-assigned at runtime.

do_actuate(raw: dict, naming_map: dict) None

Update this record from a raw inbound SenML dictionary and, if present, invoke the actuate callback.

class senml.SenmlBase

Common base class shared by SenmlPack and SenmlRecord. It exposes no public API of its own; it exists so SenmlPack.add() can validate that an item belongs to the SenML hierarchy.

class senml.SenmlUnits

Enumeration object whose attributes are the SenML unit symbols defined by RFC 8428. Each attribute resolves to the unit’s string code, suitable for assignment to SenmlRecord.unit or SenmlPack.base_unit.

Examples of available unit attributes:

  • SENML_UNIT_METER -> "m"

  • SENML_UNIT_KILOGRAM -> "kg"

  • SENML_UNIT_SECOND -> "s"

  • SENML_UNIT_AMPERE -> "A"

  • SENML_UNIT_KELVIN -> "K"

  • SENML_UNIT_DEGREES_CELSIUS -> "Cel"

  • SENML_UNIT_HERTZ -> "Hz"

  • SENML_UNIT_VOLT -> "V"

  • SENML_UNIT_WATT -> "W"

  • SENML_UNIT_PASCAL -> "Pa"

  • SENML_UNIT_LUX -> "lx"

  • SENML_UNIT_RELATIVE_HUMIDITY -> "%RH"

  • SENML_UNIT_VELOCITY -> "m/s"

  • SENML_UNIT_ACCELERATION -> "m/s2"

  • SENML_UNIT_DEGREES_LATITUDE -> "lat"

  • SENML_UNIT_DEGREES_LONGITUDE -> "lon"

  • SENML_UNIT_BIT_PER_SECOND -> "bit/s"

  • SENML_UNIT_COUNTER -> "count"

  • SENML_UNIT_RATIO -> "//"

  • SENML_UNIT_BPM -> "beat/min"

See the SenML unit registry in RFC 8428 Section 12.1 for the complete list.

Examples

from senml import SenmlPack, SenmlRecord, SenmlUnits

pack = SenmlPack("urn:dev:mac:0024befffe804ff1")
temp = SenmlRecord("temperature", unit=SenmlUnits.SENML_UNIT_DEGREES_CELSIUS, value=23.4)
hum = SenmlRecord("humidity", unit=SenmlUnits.SENML_UNIT_RELATIVE_HUMIDITY, value=51.2)
pack.add(temp)
pack.add(hum)
print(pack.to_json())