| Format | Enum value | Self-describing? | Library used |
|---|---|---|---|
| JSON | PlotJugglerMessageFormat.JSON |
Yes | Jackson (ObjectMapper) |
| CBOR | PlotJugglerMessageFormat.CBOR |
Yes | Jackson (jackson-dataformat-cbor) |
| MessagePack | PlotJugglerMessageFormat.MESSAGEPACK |
Yes | Jackson (jackson-dataformat-msgpack) |
| BSON | PlotJugglerMessageFormat.BSON |
Yes | Jackson (bson4jackson) |
| Protobuf 🚧 | PlotJugglerMessageFormat.PROTOBUF |
No - needs a shared schema | protobuf-java, schema in proto/ |
These four formats are all self-describing: no schema needs to be shared with PlotJuggler ahead of
time, since the structure is inferable from the bytes themselves. They are implemented identically, each
as a thin wrapper around a format-specific Jackson ObjectMapper that reflects over whatever Java object
is handed to it. On the wire, every message has the same shape regardless of format: a JSON object (or its
CBOR/MessagePack/BSON equivalent) keyed by channel name, wrapping a timestamp and the payload:
{ "x": { "timestamp": 1721470000.123, "payload": 1.23 } }Pick whichever format your PlotJuggler installation/parser expects - JSON is the most broadly compatible and human-readable; CBOR/MessagePack/BSON trade that off for smaller payloads.
Still in testing/development. The schema design below (per-concept messages wrapped in a tagged envelope) is functionally implemented and unit-verified on the Java side, but has not yet been fully validated end-to-end against a real PlotJuggler "protobuf" Message Parser. Treat it as experimental and prefer JSON/CBOR/MessagePack/BSON for production use until this note is removed.
Protobuf is different: it is not self-describing, so PlotJuggler's "protobuf" Message Parser must be
given the exact schema used to produce the bytes. This adapter ships that schema as a set of .proto
files in the top-level proto/ folder (outside src/, since it's a shared contract for PlotJuggler to
load, not Java source code):
| File | Message | Mirrors |
|---|---|---|
proto/state.proto |
StateProto |
the whole-state channel's default payload (DigitalTwinStatePropertiesDto) |
proto/property.proto |
PropertyProto |
a property channel's default payload (DigitalTwinStatePropertyDto) |
proto/event.proto |
EventProto |
an event channel's default payload (DigitalTwinStateEventNotificationDto) |
proto/relationship.proto |
RelationshipProto |
a relationship channel's default payload (DigitalTwinStateRelationshipInstanceDto) |
proto/scalar_value.proto |
ScalarValueProto |
shared leaf value type (see below) |
proto/envelope.proto |
ChannelMessageProto |
the actual wire format - see below |
Every property/event/state value can be of a different concrete type (a double, a string, a boolean...),
so ScalarValueProto is a small oneof of double/string/bool/int64, with a JSON-text fallback
field for anything else (nested objects/arrays, or a custom payload function's non-scalar result).
Since MQTT/UDP/WebSocket connections can all carry more than one channel type over the same physical
connection or topic (UDP/WebSocket always do; MQTT does too in single-topic mode), every Protobuf message
is wrapped in ChannelMessageProto: a channel name plus a oneof of StateProto/PropertyProto/
EventProto/RelationshipProto, so the receiver can always tell which concept a given message carries.
To use it: select PlotJugglerMessageFormat.PROTOBUF when building the configuration, then in
PlotJuggler's "protobuf" Message Parser configuration dialog, add proto/ as an include folder, load
proto/envelope.proto, and select ChannelMessageProto as the message type.
Custom payload functions that return the default DTOs above are converted directly; a custom payload
returning some other shape is converted best-effort via Jackson (e.g. a Map becomes a set of
ScalarValueProto entries), falling back to the JSON-text leaf field when a value isn't a plain scalar.