ROXSIE integration: PLC alarms into ROS 2 faults - #569
Draft
mfaferek93 wants to merge 8 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.
Hand-kept bit maps drift from the PLC program, which is the spreadsheet problem again. One source of truth means the generator emits the map.
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, published cyclically. 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_Alarmand mirrors 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 came from. 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 data block 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 of the 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.
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:
ros2_medkit_fault_detectionalready evaluates thresholds and status words for PLC tags and can take a metric the same way.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).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 a confirmed fault come with the implementation, once we agree on the contract.Checklist