ROXSIE integration: PLC alarms into ROS 2 faults - #569
Draft
mfaferek93 wants to merge 6 commits into
Draft
Conversation
Alarm events out of a PLC alarm system become structured faults; aggregate state goes back as four fixed values, no fault list in the PLC.
Names the code generator this interface targets first and lists the obligations of any producer: observe the alarm system, latch values at trigger time, timestamp at the source, re-publish pending after restart.
Renames the package and the two types, and rewrites the docs around the generated node as the producer instead of a hypothetical vendor bridge.
Shorter sentences, direct statements, no rhetorical framing.
…l faults The audit log ships disabled by default, so an entry is written only when it is on. The four status values aggregate every active fault in scope, not just the ones the PLC reported.
The transport is cyclic and a picture survives a restart, so AlarmList holds every alarm active or unacknowledged and the bridge diffs it. Adds the PLC priority scale and the producer, drops the invented severity, and states what each of the two fill paths can populate.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pull Request
Summary
Alarms in the PLC show up in ROS 2 as faults, with the values latched when the alarm fired.
Back the other way, the PLC gets one condition it can act on. Draft, because the message
definitions are what we want to agree on first. The bridge comes after that.
Context
ROXSIE already generates the PLC blocks and a ROS 2 package for the data you declare in the
YAML config, exchanged over shared memory on the host. That covers process values. It does
not cover alarms, because alarms live in the CPU message system and not in a data block.
So on a machine with a SIMATIC PLC and ROS 2 you still have two separate lists of what is
wrong. To join them today you either read alarm bits over a protocol and rebuild their
meaning from an engineering export, or someone copies the alarms by hand into a second list
on the panel. Both drift.
A picture, not a stream
AlarmListcarries every alarm that is active or waiting for acknowledgement, publishedcyclically. Two reasons.
The transport is cyclic anyway. The PLC-side node reads a data block at a fixed rate, so a
picture is what it naturally has.
And a picture survives a restart. Whoever restarts, the next cycle carries the full truth
again. An event stream loses whatever happened while it was away, and the consumer cannot
tell an empty stream from a healthy machine. That gap is not hypothetical: reading alarms
over OPC UA has the same hole, because no table of pending alarms is served on reconnect.
The bridge compares consecutive pictures. An entry that appears becomes a fault, an entry
that disappears heals it. Debounce, freeze-frame capture and the audit trail are the same
ones every other fault source goes through.
Two ways to fill the list
From bits. The customer declares alarm bits in a data block and ROXSIE carries them like
any other declared data. Nothing new is needed on the generator side, so this works today.
alarm_class,priority,producer,plc_timestampandassociated_valuesstay empty,and the freeze-frame comes from the process topics the deployment already carries.
Acknowledgement is a bit, so either side can set it.
From the CPU alarm system. Generated code reads the alarm system with
Get_Alarmandmirrors it into the data block. Still no OPC UA and no license. This fills everything: the
event time from the CPU clock, the alarm class, the priority the plant already engineers
with, and the values latched when the alarm fired.
It also picks up alarms nobody declared. The CPU message system carries system diagnostics,
ProDiag, GRAPH, motion and security alarms, and
producersays which one an entry camefrom. A module fault or a drive alarm arrives without any extra engineering. That is the
strongest argument for this path.
The trade is acknowledgement. The CPU owns the acknowledged state and has no per-alarm
acknowledge instruction, so setting it for a single alarm needs the panel or OPC UA. We
mirror it rather than write it.
One cost worth naming:
Get_Alarmreads from a queue, one entry at a time, while the datablock has to hold a picture. The generated code has to keep that picture itself. That is
real work, not a flag.
flowchart TB subgraph back["2. One condition back into the PLC"] direction TB B1["FaultManager"] B2["ros2_medkit_roxsie_bridge"] B3["ROXSIE generated node"] B4["Consumed status block"] B5["Machine logic<br>signal column"] B1 --> B2 B2 ==>|DiagnosticsStatus| B3 B3 --> B4 B4 --> B5 end subgraph out["1. Alarms out of the PLC"] direction TB A1["CPU alarm system<br>or alarm bits"] A2["ROXSIE generated node"] A3["ros2_medkit_roxsie_bridge"] A4["FaultManager<br>debounce, freeze-frame, audit"] A5["SOVD gateway, REST + SSE"] A6["Panel page, dashboards,<br>maintenance clients"] A1 --> A2 A2 ==>|AlarmList| A3 A3 --> A4 A4 --> A5 A5 -->|HTTPS| A6 end linkStyle 1,5 stroke:#00966b,stroke-width:2.5pxThe generated node talks to the PLC over shared memory on the same host, but publishes on
the ROS 2 graph over the network. So the diagnostics side does not have to run on the
controller.
What goes back into the PLC
One value and a heartbeat.
DiagnosticsStatussays what the diagnostics side is asking ofthe machine: nothing, something is abnormal, someone has to act, or stop.
It does not say what to light up. Machines already have a state machine, and where PackML is
used, a block that turns machine state into signal column bits according to ISA TR88.00.02.
Driving the lamp ourselves would put two writers on it. We hand over a condition and let the
machine's own logic decide what it means for the lamp, for an interlock, or for a state
transition.
The condition covers every active fault in scope, not only the ones that came from the PLC.
A navigation fault on the robot moves the same value, which is how a problem on the ROS 2
side reaches an operator who never looks at a screen.
Messages
Alarm
alarm_iduint32sourcestringalarm_classstringpriorityuint8producerstringuser_program,system_diagnostics,prodiag,graph,motion,securityactiveboolacknowledgedboolplc_timestampbuiltin_interfaces/Timeassociated_valuesfloat64[]Two flags rather than one enum, because an alarm that is gone but unacknowledged is a real
state and stays in the list. Those are the same two bits the CPU alarm system tracks.
Alarm text does not travel. It belongs to the project, changes with the project, and is
resolved on the ROS 2 side from a table that ships with the deployment.
AlarmList
stampbuiltin_interfaces/TimealarmsAlarm[]DiagnosticsStatus
heartbeatuint16conditionuint8OK,ABNORMAL,ACTION_REQUIRED,STOPclass_bitmaskuint16scopestringWhen something breaks
The first row matters most. If our layer dies, the plant has to see it.
What comes after
This covers alarms from the PLC and one condition back. Two extensions follow, both shaped
by what a deployment needs rather than by what is interesting to build.
OpenTelemetry as another fault source. A cell is more than a PLC and a robot. The switch
that flaps, the controller that runs hot, the compute in another building that this machine
depends on - none of them speak the PLC alarm system, and most already speak OpenTelemetry.
Every one of those vendors ships its own API, and asking each of them for an integration does
not scale; asking all of them for OpenTelemetry does. An OTLP receiver next to this bridge
leaves three things to settle:
Metrics are the more interesting one, because
ros2_medkit_fault_detectionalreadyevaluates thresholds and status words for PLC tags and can take a metric the same way.
every host lands in one unattached pile.
the condition that reaches the signal column.
OpenTelemetry as an export. The same faults served over REST can be emitted as
OpenTelemetry, and the per-scope condition as a metric. Two renderings of one state, not two
sources of truth. Teams that already run an observability pipeline add a source rather than
integrate an API.
Neither exists today. There is a hook where an exporter plugs in and nothing behind it.
Building it against a concrete consumer beats guessing at a generic one.
Open questions
alarm_idunique per CPU, or only per block? If only per block, the key has to be(source, alarm_id).already reads, or a separate export?
generated, and a plant can declare hundreds of alarms. If the ceiling is low, the fill has
to prioritise and say that it truncated.
acknowledgement from the ROS 2 side stays a bit-path feature.
more and brings strings back.
Issue
Type
Testing
Message definitions only, no runtime code yet.
colcon build --packages-select ros2_medkit_roxsie_msgssucceeds on jazzy and the types introspect throughros2 interface show. Bridge unit tests and an integration test that drives an alarm picture through to aconfirmed fault come with the implementation, once we agree on the contract.
Checklist