A channel is a logical, transport-agnostic named destination for one specific kind of Digital Twin data. Four channel types exist, one per kind of data:
| Channel class | Input to the payload function | Sent... | Default DTO (see dto package) |
|---|---|---|---|
StateOutgoingChannel |
DigitalTwinState |
once per state-update cycle, if configured (at most one instance) | DigitalTwinStatePropertiesDto { properties } |
PropertyOutgoingChannel<T> |
DigitalTwinStateProperty<T> |
whenever the matching property key changes | DigitalTwinStatePropertyDto { value } |
RelationshipInstanceOutgoingChannel<T> |
DigitalTwinStateRelationshipInstance<T> |
whenever a relationship instance for the matching relationship name is created/removed | DigitalTwinStateRelationshipInstanceDto { targetId, instanceKey, metadata } |
EventOutgoingChannel<T> |
DigitalTwinStateEventNotification<T> |
whenever an event notification for the matching event key arrives | DigitalTwinStateEventNotificationDto { body } |
You never instantiate channel classes directly: PlotJugglerDigitalAdapterConfigurationBuilder does it for
you via withStateChannel(...), addPropertyChannel(...), addRelationshipChannel(...) and
addEventChannel(...).
Every add*Channel(...)/withStateChannel(...) builder method comes in two flavors:
- a default overload (just a channel name, or a key + channel name) that forwards the lean, built-in DTO from the table above;
- a custom overload taking an extra
Function<InputType, Object>("payload function"), letting you reshape the outgoing payload however you like before it's sent.
| Channel | Custom function signature |
|---|---|
withStateChannel(name, fn) |
Function<DigitalTwinState, Object> |
addPropertyChannel(key, name, fn) |
Function<DigitalTwinStateProperty<T>, Object> |
addRelationshipChannel(key, name, fn) |
Function<DigitalTwinStateRelationshipInstance<T>, Object> |
addEventChannel(key, name, fn) |
Function<DigitalTwinStateEventNotification<T>, Object> |
This is especially useful when the raw value isn't directly plottable: PlotJuggler plots numeric time
series, so an event carrying a string or an otherwise non-numeric value needs to be mapped to a number
first. UdpCustomFunctionTestMain (src/test/java/.../UdpCustomFunctionTestMain.java) demonstrates
exactly this, mapping the test fixture's rotating textual "message" event and boolean "alarm" event to
plain integers before sending:
// "message" cycles through a fixed list of strings - map it to its index in that list
private static final List<String> MESSAGE_VALUES = Arrays.asList("nominal", "warming-up", "steady-state", "cooling-down");
private static final Function<DigitalTwinStateEventNotification<String>, Object> messageToIndexFunction =
notification -> MESSAGE_VALUES.indexOf(notification.getBody());
// "alarm" is a boolean - map it to 1/0
private static final Function<DigitalTwinStateEventNotification<Boolean>, Object> alarmToIntFunction =
notification -> notification.getBody() ? 1 : 0;
PlotJugglerDigitalAdapterConfiguration configuration = PlotJugglerDigitalAdapterConfiguration
.builder(new UdpConnectionConfiguration(host, port), PlotJugglerMessageFormat.JSON)
.addEventChannel(SignalGeneratorPhysicalAdapter.MESSAGE_EVENT_KEY, "event_message", messageToIndexFunction)
.addEventChannel(SignalGeneratorPhysicalAdapter.ALARM_EVENT_KEY, "event_alarm", alarmToIntFunction)
.build();The same pattern applies to properties, relationships and the whole-state channel: a payload function can
extract a single field, combine multiple values, look up an enum's ordinal, or return any other Object -
whatever ends up plottable (or otherwise useful) once it reaches PlotJuggler.