Internal training material. Sign in with your SymX address and the access code you were given.
Access is limited to symx.ai addresses. This material references internal systems, customer fleets and licensed third party data, and it is not to be forwarded outside SymX.
A body of knowledge. What the bus is, how Linux carries it, what SymX has built on it, and how a single frame on a wire becomes a work order inside X.EAM.
| Who this is for | An engineer who will read, change or depend on machine data acquisition at SymX. Firmware, platform, data science and technically minded product roles. |
| Assumed knowledge | Comfortable with Linux on the command line, and able to read Java and C. No prior CAN or J1939 experience is assumed. |
| Time | About four hours to read, plus two to three hours for the practical exercise in section 9.4. |
| Completion bar | Six of the eight knowledge checks correct, and the section 9.4 exercise completed with numbers reported. |
| Access to request on day one | Jira projects FW, TAB and FS. Confluence spaces hardware and TECH. The symboticware GitHub organisation. A briefing on the handling rules for the licensed SAE signal definition annex, which must never leave the building. |
Read it in order the first time. Sections 1 to 4 are the protocol, taught from the wire up. Section 5 is the EAM product primer, and it is the reason the protocol matters commercially. Sections 6 to 9 are what SymX has actually built and what is broken in it. Knowledge checks are placed at the end of each block. Answer before you look. The bar at the bottom of the page tracks how you are doing.
Every Jira key, Confluence page and repository named here is linked. Read Confluence in date order. The corpus contains several explicit self corrections, and reading out of order means absorbing a retracted conclusion as current.
A modern haul truck is between six and twenty computers that happen to share a chassis. Engine, transmission, brakes, suspension, tyre monitoring, payload scale, operator display and aftertreatment each run their own controller, and they have to agree with each other several hundred times a second.
They agree over a Controller Area Network. Everything SymX sells on a mobile machine begins by listening to that conversation, and almost every hard problem in the product line traces back to how faithfully we hear it.
Two things follow from that, and they shape every engineering decision in this manual.
Two wires, CAN_H and CAN_L, carrying a differential signal, terminated at 120 ohms at each end. Every node sees every frame. There is no addressing at the link layer and no master node. Mining equipment runs almost universally at 250 kbit/s, occasionally 500 kbit/s on newer machines.
Differential signalling is why it survives on a machine with a 600 amp starter motor and a diesel engine bolted a metre away. Noise hits both wires equally and the receiver reads the difference.
Two nodes that start transmitting at the same instant do not collide and retry. They arbitrate, bit by bit, while transmitting. A dominant zero physically overwrites a recessive one, so a node that sent a recessive bit and reads back a dominant one knows it lost, stops, and retries later. The frame with the numerically lowest identifier wins.
Three consequences that matter operationally:
| Field | Width | What it does |
|---|---|---|
| Identifier | 11 bit standard, 29 bit extended | Priority, and in J1939 the entire routing scheme. J1939 is always 29 bit. |
| Control bits | 3 bits standard, 5 bits extended | Standard frames carry RTR, IDE and r0. Extended frames carry SRR, IDE, RTR, r1 and r0. J1939 is always extended and never uses remote frames. |
| DLC | 4 bits | Data length, zero to eight bytes. The eight byte ceiling is the entire reason J1939 needs a transport protocol. |
| Data | 0 to 8 bytes | The payload. |
| CRC | 15 bits | Link layer integrity, checked in hardware. |
| ACK | 2 bits | Any node that received the frame cleanly pulls this slot dominant. |
The controller checks the CRC in hardware, so a frame corrupted on the twisted pair is discarded before userspace ever sees it. The CAN CRC-15 has Hamming distance 6 and a residual undetected error probability around 4.7 × 10-11 per frame, which is extremely good and is not zero.
Read the scope carefully, because this is where the fact is usually over claimed. The link layer CRC protects the wire and nothing after it. On this platform a frame then crosses an SPI link to the CAN controller, a JNI boundary and a Java decoder, and none of that is covered. So the correct working rule is: corruption is almost never the twisted pair, and it can absolutely be the SPI bus, the driver, the decode or the configuration.
That single fact has redirected two separate investigations at SymX. It is also the logic behind a negative result worth protecting: when disabling SPI DMA on the radio bus made no difference to payload corruption, the reading was that correctable payloads prove the corruption is not on air, because interference does not repair itself to a valid checksum. See FW-5459.
Every node runs a transmit error counter and a receive error counter. At or below 127 on both, the node is error active and signals errors aggressively. Once either counter passes 127 the node becomes error passive and signals quietly. Only the transmit counter causes bus off, at 256 and above. A node sitting at a receive count of 250 is error passive and will never go bus off, so chasing a climbing receive counter as an imminent bus off is a wasted afternoon.
Bus off is also not necessarily permanent. CAN defines automatic recovery after 128 occurrences of eleven consecutive recessive bits, and SocketCAN exposes that as restart-ms. A device with a restart interval configured cycles in and out of bus off, which is a completely different diagnostic signature from a node that has stopped and stayed stopped.
Check ip -details -statistics link show can0 before you believe anything about a quiet bus. A bus off interface looks exactly like a parked machine. One real example from the fleet: a driver level transmit echo accounting leak logged can_put_echo_skb: BUG! echo_skb is occupied! 113 times in 14 minutes on a device whose bus showed zero transmit and zero receive errors. The bus was clean. The driver was not. The fix is upstream in the renamed mcp251xfd driver, which covers the MCP2517FD and MCP2518FD parts. Note the spelling, searching for the older form finds nothing.
A decoded engine temperature reads 1,235 degrees. Electrical noise from the alternator is suspected. What does the frame format tell you?
SocketCAN applies the Linux networking stack to CAN. An interface appears as a network device and you open a socket against it with PF_CAN, much as you would open a UDP socket. Four protocols sit on top of it.
| Protocol | What it gives you | Used at SymX |
|---|---|---|
CAN_RAW | Raw frames in and out, with kernel side filters | Yes. The legacy path and raw capture. Enabled per port with canraw.enabled. |
CAN_BCM | Broadcast manager, cyclic transmit and receive filtering in kernel | No |
CAN_ISOTP | ISO 15765-2 segmentation, the transport behind OBD diagnostics | No |
CAN_J1939 | Kernel J1939: address claiming, PGN addressing, transport protocol reassembly | Yes. The new path, and the point of the migration. |
The legacy SymX path implements J1939 in Java. It opens a raw socket, reads frames, and reimplements address claiming, transport reassembly and PGN routing in userspace. The kernel stack does all of it in C, in kernel, exercised by everyone running Linux on a vehicle.
Moving to CAN_J1939 deletes a large amount of code SymX maintains and replaces it with code SymX does not. It also delivers multi frame reassembly at no cost, which matters because a great deal of the interesting diagnostic and identity content is longer than eight bytes and therefore invisible to a raw frame reader.
Design context: Symbot 6 J1939 stack, current state and integration gaps, Confluence 3695673345, space hardware.
| Command | Use |
|---|---|
candump can0, candump -l can0 | Watch and log. Every capture fixture in the repositories came from this. |
canplayer -I file.log | Replay a capture into an interface. |
cansend | Transmit. Treat as a loaded weapon on a customer bus. |
ip link add dev vcan0 type vcan | A virtual bus with no hardware at all. Most of your work should happen here. |
ip -details -statistics link show | State and error counters. |
The long standing belief that the SymX native library only builds for arm64 is false. One line, gcc -shared -fPIC against the JDK 8 headers over j1939_socket_jni.c and can.c, builds it for amd64. That is what FW-5473 establishes, and it means the real stack can run on a laptop against recorded captures instead of queueing for a bench board.
The measured difference across one defect fix, run through that harness: batches from 199 to 993,288, datapoints from 1,614 to 8,117,818, DM1 records from 2 to 13,777.
A replay is not a bus. canplayer on one bench device hits sendto: No buffer space available and lands 618 of roughly 5,046 frames. Size your conclusions to what actually arrived.
Not every board has vcan. One of the few boards carrying the full native surface runs a kernel without the vcan module, so loopback on a real interface is the substitute there.
What does CAN_J1939 give you that CAN_RAW does not?
SAE J1939 is what turns a CAN bus into a vehicle network. It defines how the 29 bit identifier is carved up, how nodes claim addresses, how long messages are carried, and how faults are reported.
The 29 bits decompose into priority (3), extended data page and data page (2), PDU format (8), PDU specific (8) and source address (8). PDU format below 240 is PDU1, the message is addressed, and PDU specific carries the destination address. PDU format 240 and above is PDU2, the message is broadcast, and PDU specific becomes a group extension.
The Parameter Group Number is 18 bits: extended data page, data page, PDU format, and then PDU specific or group extension. The extended data page is part of it, and it is routinely dropped by people who read a summary.
The second half matters more. For PDU1 messages the PDU specific byte must be zeroed when forming the PGN. The destination address is not part of the PGN. Code that keeps it generates up to 256 distinct PGNs for what is one message, and the symptom is a PGN table that fills with near duplicates.
In working language: PGN is what message this is. Source address is which controller sent it. SPN is which signal inside it. Those three, plus the value and a timestamp, are the identity a reading needs to be useful to anything downstream. Section 7.3 explains why their absence is the largest structural problem in the current code.
A Suspect Parameter Number names a signal. A Failure Mode Identifier names how it failed. A diagnostic trouble code is the pair, plus the source address of the controller that raised it, plus an occurrence count. The purchased SAE Digital Annex defines 32,053 SPNs and 32 FMIs.
| FMI class | FMIs | Meaning |
|---|---|---|
| Range high | 0, 15, 16 | Data valid but above normal, three severity bands |
| Range low | 1, 17, 18 | Data valid but below normal, three severity bands |
| Signal quality | 2, 8, 9, 10 | Erratic, abnormal frequency, abnormal update rate, abnormal rate of change |
| Electrical | 3, 4, 5, 6 | Voltage high, voltage low, current low or open circuit, current high or grounded |
| Mechanical | 7 | Mechanical system not responding properly or out of adjustment. The second half carries a real diagnostic distinction and is often dropped. |
| Device and instruction | 12, 14 | Bad intelligent device or component, and special instruction. FMI 12 is common on Tier 4 aftertreatment controllers. |
| Calibration and drift | 13, 20, 21 | Out of calibration, drifted high, drifted low |
| Network | 19 | Received network data in error |
| Unknown | 11, 31 | Failure mode not identifiable, condition exists |
dtc[2] >> 5, never dtc[2] & 0x07.Index from the four byte DTC, not from the frame. A DM1 frame begins with two lamp bytes, so if you index the frame instead, byte 2 is the SPN least significant byte and the rule inverts. This is the most common way the trap is re-introduced after being fixed.
The packing above is SPN conversion method version 4. Methods 1 to 3 exist on older Cummins and Caterpillar controllers, which are plausible on these fleets, and they pack differently. Bit 3 of the FMI byte is the conversion method flag. A decoder that hard codes version 4 silently mis-decodes those controllers rather than failing visibly.
Both packing traps are covered by tests in the firmware pack. Do not re-derive them from a forum post.
Anything longer than eight bytes is segmented. Broadcast uses BAM, a single announcement followed by numbered data packets with no flow control. Point to point uses request to send and clear to send with acknowledgement. The kernel stack reassembles both. This is why the new path sees content the legacy path structurally cannot.
Every node claims an address by broadcasting a 64 bit NAME, and the numerically lower NAME wins a contest. Addresses 254 and 255 are reserved as null and global.
A stray cansend is the obvious risk. The larger one is an address claim. If a SymX claim collides with an OEM controller and wins, the losing node is forced to the null address and effectively leaves the bus. On a machine carrying safety relevant traffic that is a serious event, and it is caused by a device that was only meant to be listening.
The SymX name generator asserts manufacturer code 1514 and function 129 in industry group 0. Both are unverified, and the combination is also structurally suspect: function values from 128 upward are defined per industry group and vehicle system, so a function of 129 in industry group 0, which is Global, is self contradictory. Mining mobile equipment normally sits in industry group 2 or 3. Resolve this before any customer bus deployment.
Two identity messages matter more than any other pair in this manual, because they are what section 5 is built on. PGN 65259 carries component identification: make, model, serial number and unit number, delimited by asterisks. PGN 65242 carries software identification. Both are requested after address claim and are normally published on change rather than polled.
A fault queue needs to be sorted so the most serious faults reach a planner first. What do you sort on?
Everything above is protocol. This section is the reason SymX pays anyone to care about it.
EAM is the system a heavy industrial operator uses to decide what to maintain, when, at what cost, and to prove afterwards that it did. It holds the asset register, the maintenance plans, the work orders, the spares and the failure history. In a mine it is the system a reliability engineer, a maintenance planner and a finance controller all argue in front of.
| System | Who runs it | What it assumes |
|---|---|---|
| IBM Maximo | Large operators. The default in mining and utilities | That the asset hierarchy and failure codes already exist and are maintained by people |
| SAP Plant Maintenance | Operators already standardised on SAP | The same, with the register tied to finance master data |
| Hexagon EAM | Mining and infrastructure | Sells a consulting engagement to build the register |
| RPMGlobal AMT | Mining maintenance and lifecycle costing | That the register exists. It models cost on top of one |
A mine builds its equipment register by hand. Someone walks the yard with a clipboard, types serial numbers into a spreadsheet, and it is wrong within a month because components get swapped and nobody updates the sheet.
SymX already collects enough machine data to generate that register automatically, keep it current, and detect when a component is physically replaced. Maximo assumes the register exists. Hexagon sells a project to build one. RPMGlobal models cost on top of one. None of them generates it. That is the product thesis, and the CAN stack is what makes it either true or a slide.
The component replacement record deserves its own sentence. When a serial number on the bus changes, the old component row is closed and a new one opened, which produces a dated replacement event. A swap must never update a row in place, because updating erases the fact that a component was ever replaced, and that fact is the single most valuable row in the register. It is also the one maintenance record SymX can supply that an EAM cannot generate for itself.
| ISO 14224:2016 | ISO 55001:2024 | |
|---|---|---|
| What it is | Collection and exchange of reliability and maintenance data for equipment. Third edition, September 2016, corrected October 2016, 272 pages | Asset management, management systems, requirements. Second edition |
| Structure | Clauses 1 to 9. Annex A and Annex B are both normative, covering the equipment class taxonomy with boundary definitions, and the interpretation and notation of failure and maintenance parameters. Annexes C, D, E and F are informative | Transition deadline 31 July 2027. Certificates against the 2014 edition are invalid after that date |
| Certification | No certification scheme exists | Certifiable, and operators hold certificates |
Say structured to ISO 14224 or ISO 14224 aligned. Never say certified. There is no such certification and a reliability engineer will know it in the first meeting.
Mobile mining equipment is not an ISO 14224 equipment class. Annex A covers process plant: pumps, compressors, turbines, vessels, valves. Because Annex A is normative, that is a firm statement about scope rather than a soft one, which makes the SymX position stronger. Apply the method, never claim the equipment coverage. Claim the method and the ground is solid. Claim the coverage and it is wrong in a way that is trivially checked.
One commercial detail from the 2024 revision. The clause on externally provided processes and products was widened to name technologies, which places a supplier like SymX explicitly inside a certified operator's control scope. The data and information clause was rewritten to add quality improvement, a clause on knowledge was added, and the improvement clause moved from preventive to predictive action, widened to frequency, impact and the optimal intervention point. The standard moved toward what SymX sells.
Before quoting clause numbers or the transition date to a customer, check them against the standard itself. A certification manager will verify these first, and clause numbering moved between editions. Buy the standards. The same argument applies that justified buying the signal definition annex, which caught two signals defined wrongly before they shipped.
Maximo, SAP PM and Hexagon EAM all want mode, mechanism and cause. None of them wants an SPN. The bus can seed failure mode and detection method. Mechanism and cause require a work order close out by a person, and no telemetry system can supply them. Saying that plainly earns credibility. Implying otherwise gets the whole register taken apart by a reliability engineer.
The seeding rule in use, which is SymX judgement and not a conformance claim: range high to HIO, range low to LOO, signal quality to AIR, electrical to AIR or BRD depending on the component, mechanical to BRD, drift to ERO, calibration to AIR, unknown left unset. Network errors are not an equipment failure and must never seed one.
An asset register is a tree. A machine has subunits, a subunit has maintainable items, and a maintainable item is the thing that gets replaced and carries a serial number. Two SymX codebases describe that tree, and the decision already taken is that they share a join key rather than a translation table.
The Equipment Knowledge Base uses short ISO component codes: ENG, TRN, COOL, FUE, AFT, STR, OPS, TYR, BRK, ELE. Every component definition in the backend now carries an isoComponentCode column, so the crosswalk is a column and never becomes a table. The asset tag scheme follows: KZ-HT-001.ENG names the engine of haul truck 001 at that site.
Aftertreatment, AFT, exists in the taxonomy with four maintainable items, diesel particulate filter, selective catalytic reduction, dosing and oxidation catalyst, and they ship with zero signals attached. A component in the tree with no signals is a coverage conversation with a customer. A component the schema cannot express at all is a rebuild. On a Tier 4 fleet, aftertreatment is a large share of real faults.
| Layer in the Equipment Knowledge Base | Rows |
|---|---|
| Makes and models | 315 and 1,360 |
| Digital Annex SPNs, with unit, scaling, data range and operational range | 32,053 |
| PGNs, and SPN to PGN links | 3,196 and 16,339 |
| SLOT definitions, the threshold source | 424 |
| FMIs and source addresses | 32 and 263 |
| Manufacturer codes | 1,504 |
spn_component_map, curated SPN to ISO 14224 component | 92 |
| ISO 14224 failure modes, causes, priority bands | 17, 14, P1 to P4 with response times |
Promoting the hand curated SymX signals against the authoritative definitions caught two that were wrong.
| SPN | SymX had | Annex says | If it had shipped |
|---|---|---|---|
| 515 | Nominal friction, percent torque, scale 1 | Engine's desired operating speed, 2 bytes, 0.125 rpm per bit | A torque trend that was actually an rpm trend. Plausible on a dashboard. False. |
| 519 | Engine's desired operating speed, scale 0.125 | Desired operating speed asymmetry adjustment, 1 byte, 1 per bit | Off by eight times and mislabelled. |
The Annex is licensed material. Every Annex row is marked non redistributable and the public view and export manifest exclude them, so nothing SAE owns leaves the building. Customers receive only the SymX authored layer. Treat that fence as load bearing, it is a commercial exposure.
Thresholds in the DTC master model are derived from the Digital Annex declared measurement range: high severe at 100 percent of range, high moderate at 90, high least at 80, mirrored on the low side. They are provisional commissioning bands and never OEM alarm setpoints, which are calibrated per model and per configuration and are licensed content SymX does not hold.
Electrical FMIs 3, 4, 5, 6 and 19 carry no threshold at all, because a shorted circuit has no engineering unit setpoint. FMIs 2, 13 and 31 carry none either, because the controller raises them rather than a limit SymX sets.
A customer asks whether SymX can populate failure mode, mechanism and cause from telemetry. What is the correct answer?
This is the diagram to keep in your head. Nine stages, from a voltage on a twisted pair to a row a maintenance planner acts on. Every defect in section 8 breaks one specific stage, and knowing which stage tells you who is affected.
The same stream feeds more than the register. Position, machine state and cycle timing go to X.Fleet and X.Dispatch. Pressure, temperature and leak models go to X.Tires. Component identity and spares go to X.Parts.
| Stage | What happens |
|---|---|
| 3 to 4 | A stalled subscription delivered exactly 256 frames and then nothing, so storage stayed empty while the interface looked healthy. |
| 5 | Little endian extraction read the wrong bits, so most signals were dropped or corrupted while engine speed happened to look correct. |
| 5 | Out of range values were clamped to the declared limit, so an unplugged sensor reported a clean maximum and models trained on it. |
| 7 | The reading carries no SPN, PGN or source address, so nothing can join a signal to a component except by parsing a display string. |
| 8 | Component identity reaches only 7 devices of 291, which is 2.4 percent, and the register cannot be sold at that number. |
Which stage failing makes every downstream stage impossible, no matter how good the decode is?
The fleet default is the legacy userspace agent, j1939.useNewJ1939Agent=false. The new path runs kernel sockets through the native library and JNI into the agent, then DBC decode, then the controller, then the datawarehouse. Nearly every open defect belongs to the new path, because the new path is the one that has not yet run in anger.
| Path | What lives there |
|---|---|
pulse-daemon /opis/devices/can/ | J1939Agent.java new path, J1939.java legacy, J1939DbcFile.java, J1939NameGenerator.java |
pulse-daemon /opis/io/can/ | DbcSignal.java, the second copy of J1939DbcFile, CanProcessor.java, J1939Socket |
pulse-daemon /opis/common/dataservice/services/ | ServiceBuilder.java, J1939DataService.java |
symlibs /socketcan/src/main/c/ | can.c, j1939_socket_jni.c, eleven Java_*_J1939Socket_* natives |
pulse-daemon /daemon/src/main/conf/ | can0.j1939.dbc, can1.j1939.dbc, customJ1939.dbc, j1939DiagnosticSpns.csv |
SymBotSoftwareAcceptanceTest /candumps/ | Replay fixtures, including a Kress hauler capture and a five minute DTC slice |
| meta-symbot | Yocto layer. Pins the native library revision and owns what ships in the image |
The decode primitive returns DataPoint(name, value), where name is a concatenated string such as J1939EngineOilPressure. There is no SPN, no PGN, no source address and no component in the object.
That single line is stage 7 of the diagram failing in code. It is why predictive maintenance work had nothing to predict against, and it is why the asset register cannot attribute a fault without parsing a display name.
The fix is additive: widen the object to carry spn, pgn, sourceAddress, unit and quality as nullable fields, leaving the 573 existing construction sites untouched and keeping identity out of the wire format. Tracked as FW-5471.
The second half of the same problem is clamping. J1939 reserves sentinel values for error and not available, and the current code returns the declared minimum or maximum instead of flagging them.
0xFE for error and 0xFF for not available are correct for single byte parameters only. For two byte parameters the reserved ranges are 0xFE00 to 0xFEFF for error and 0xFF00 to 0xFFFF for not available, and four byte parameters scale the same way. Engine speed, the worked example above, is two bytes. A decoder implementing the single byte rule passes 0xFEFF through as a valid 8,191 rpm, which is precisely the plausible and wrong number this manual opens by warning about.
So an unplugged sensor reports a clean maximum, and every anomaly model trained on this historian has been trained partly on manufactured maxima. The replacement is a quality enum with the raw value preserved. One subtlety already learned painfully: a null valued reading throws inside the distinct value filter, so sentinels keep their raw value and carry the flag instead.
Why is clamping an out of range value worse than dropping the reading entirely?
Read these as case studies. Each one is a class of mistake that will recur. The table lists them so the set is visible at a glance, and each row expands below.
| Case | The class of mistake |
|---|---|
| Delivery stopped at exactly 256 frames | A blocking source owns its worker, so anything scheduled onto that worker never runs |
| Extraction only worked for the signal everybody checks | A defect hidden by a coincidence in the one test case in common use |
| Fault codes went negative, and others were aliased | Signed byte handling and operator precedence. One is caught by eye, one is not |
| A reply from an OEM controller killed the subsystem | Trusting the shape of data from a boundary you do not own |
| A request that never fired | A naming convention duplicated across two files with no shared source |
| A data file that declared impossible minimums | Treating a data file as configuration rather than as code |
| A method 59 registrations from not compiling | A hard platform limit reached by accretion, invisible until it is hit |
| An interface that comes up uninvited | A service whose enable flag is not actually consulted |
The frame parser was called exactly 256 times, all within one second of startup, and never again for the life of the process. 256 is the configured prefetch on that chain. It is worth saying plainly that 256 is not an RxJava default, the library default buffer size is 128, so if you go looking for the constant you will find it in the operator arguments and not in the framework. The replenishment request is scheduled onto a worker that is permanently owned by an infinite emitter loop, so it queues behind that loop and never executes. The socket reader keeps reading, the buffer overflows at bus rate forever, and not one row is ever stored.
The lesson: a blocking source that never returns from subscribe owns its worker. Any scheduling that assumes the worker will free up is a deadlock in disguise. It was also silent because the flow was subscribed without an error handler, so a cancelled flow left no trace in the log. FW-5468.
Little endian signal extraction treated the start bit as a most significant bit offset. Only signals where the start bit equalled half the remaining width decoded correctly. Engine speed satisfied that by coincidence and looked perfect, which hid the defect for a long time. On a real multi ECU bus the legacy agent produced 19 to 28 tags while the new agent produced 11 to 12, with fuel rate pinned at the declared maximum.
The lesson: when a decoder works for the signal you always check, check a signal you never check. Also, the unit tests in both modules were asserting the buggy offsets, so the test suite was defending the defect. FW-5501.
The DM2 handler omitted the byte mask on two payload bytes, so any SPN whose low byte reached 0x80 or above was published as a negative number. Six real SPNs were being discarded this way: 168, 651, 656, 663, 2773 and 4240. Separately, an operator precedence error in the DM1 handler, a shift written as << 11 - 5, compiled as a shift by 6 and aliased any SPN above 65535 onto a different valid looking SPN.
The lesson: both produce a number that looks like a fault code. A negative one is caught by eye. An aliased one is not, and it is the one that would have reached a customer control room. FW-5518, FW-5520.
Both component identification parsers walk an asterisk delimited payload with an unbounded counter into a fixed four element array. A controller whose PGN 65259 reply carries a fifth field throws an array index exception out of the subscription callback, and a reactive subscriber that throws is cancelled with nothing to resubscribe it. The device stops decoding J1939 entirely, permanently, until the daemon restarts.
The lesson: this one is triggered by whatever OEM controller is on the customer bus, not by SymX configuration, and it hits the legacy path the production fleet runs today. A conforming reply never trips it, which is why no bench ever caught it. Parse defensively at every boundary you do not own. FW-5469.
The PGN request resolver does an exact name match. Configuration carries the prefixed form, the DBC stores the unprefixed form, so every request group resolves to an empty set. All on request signals, payload percent, axle weights, service distance and steering angle, are permanently dark on the new path. Transmit counters grow only at the diagnostic polling rate, which is the signature to look for.
The lesson: a naming convention that exists in two files with no shared source is a defect waiting for a release. FW-5241.
174 signals carry a negative offset and declare a minimum of zero. Actual engine percent torque is defined with an offset of minus 125 and a declared range starting at zero, while the real signal ranges minus 125 to 125. Sibling signals with the same offset are declared correctly, so this is inconsistency rather than design. A replay of one fleet capture produced 281,709 clamped readings of that one signal, every one reported to cloud as zero percent.
The lesson: the data file is code. It needs a lint in CI exactly as much as the decoder needs a test. FW-5480.
The signal population method in the legacy agent is 62,858 bytes against a hard limit of 65,535. Roughly 59 more registrations and the agent the entire fleet runs stops compiling. There are proposals in flight to add about 1,500 Komatsu codes and a Caterpillar proprietary set behind that.
The same size is why coverage tooling has never been able to instrument the class, so the reported coverage on the fleet default agent has always been zero, which is false, and nobody questioned it because it looked like a tooling artefact. FW-5521.
The interface init service brings up both CAN ports regardless of the enable flag, and re-asserts on every daemon restart rather than only at boot, so every over the air update re-opens it. On one device that measured as roughly 90 percent of a core across the SPI thread and the controller interrupt, and 12 degrees on the modem, with no userspace listener attached. FW-5171.
Why did the PGN 65259 parser defect survive every bench test?
Four levels, in the order to reach for them.
| Level | What it is | When it is enough |
|---|---|---|
| 1. Unit, off target | Plain tests on a laptop. Now includes the native library, built for amd64 | Never on its own for a decode change |
| 2. Capture replay through vcan | The real stack against a recorded bus, no hardware | This is the level most SymX defects should have been caught at and were not |
| 3. Hardware in loop, bench board | A real board, a simulator or a recorded bus played into it | Minimum bar before anything is reported as working |
| 4. One real machine | A customer or field machine, full shift | Minimum bar before anything ships to a fleet |
The toggle that selects the new J1939 agent appears nowhere in the acceptance suite. That is how a change which killed J1939 entirely passed CI and reached a bench undetected. The fix adds a stack argument to acceptance with three preconditions that fail loudly: J1939 symbols present in the native library, the kernel module loaded, and the per port DBC present. FW-5474, FW-5524.
There is a second trap inside that. CAN_RAW_FILTER_MAX is 512, and it is a CAN_RAW limit. It does not apply to a CAN_J1939 socket, which filters through a different path. Loading the full DBC onto the raw path exceeds that ceiling and the port goes silent with no error, so the per port file must be present with no fallback to the full one. Do not carry the number across to the kernel J1939 path, which has no such ceiling.
ip link set can0 type can listen-only on means the node never pulls the acknowledge slot dominant. Two listen only nodes on a bench bus will never pass a frame between them, and a single real controller transmitting into a bus where nothing acknowledges goes error passive and then bus off. Since listen only is the correct default on a customer machine, this trap belongs to the bench and it will cost a day if it is not known.Measure resistance across CAN_H and CAN_L with the bus powered down. Roughly 60 ohms is correct, because the two 120 ohm terminators are in parallel. Around 120 ohms means a terminator is missing. Around 40 means somebody has added a third. Stub lengths and ground offset on a machine with a 600 amp starter are the other two physical causes of a bus that behaves marginally, and they are worth ruling out before any decode investigation starts.
A raw socket port suddenly carries no traffic and logs nothing. The per port DBC is missing and the full one was loaded instead. What happened?
Do this before reading section 10. It proves your access, it proves the toolchain, and it is the fastest way to turn everything above into something you have actually done.
pulse-daemon, symlibs and SymBotSoftwareAcceptanceTest.gcc -shared -fPIC line against the JDK 8 headers over j1939_socket_jni.c and can.c. Confirm with nm -D that the J1939 symbols are present and non zero.ip link add dev vcan0 type vcan then ip link set up vcan0./candumps/ into it with canplayer, and watch it with candump vcan0.What good looks like: you can state what you ran, on what capture, and what the three numbers were. If a number differs from the baseline, you have a hypothesis about why. That is the reporting standard for everything else you will do here.
The ECU MID translation table (3750428674) and the recording visualizer (3743318022) sit in a personal Confluence space, so browsing the team spaces will not find them. Both are load bearing for CAN work and both should be moved to hardware.
| Term | What it means here |
|---|---|
| Bus off | A node whose transmit error counter reached 256 and has stopped transmitting. May auto recover if a restart interval is configured. |
| BAM | Broadcast Announce Message. The J1939 transport mode for broadcasting a payload longer than eight bytes, with no flow control. |
| CDL | Cat Data Link. Caterpillar's legacy machine bus. Carries no SPN, so signals are identified by tag name. |
| DBC | A signal database file. It declares, for each message, where each signal sits inside the payload and the arithmetic that turns raw bits into an engineering value. At SymX it is loaded at boot, so it is data and not code, which is why it needs a lint. |
| DM1, DM2 | Diagnostic Message 1 and 2. Active faults and previously active faults. Each carries lamp state plus a list of SPN and FMI pairs. |
| Digital Annex | The licensed SAE spreadsheet defining every standard SPN with its scaling, offset, unit and range. 32,053 signals. Licensed material that must not be redistributed. |
| FMI | Failure Mode Identifier. How a signal failed. Thirty two values, not ordered by severity. |
| JNI | Java Native Interface. The boundary where Java calls into the C libraries that talk to the kernel and the hardware. |
| PGN | Parameter Group Number. Which message this is. Eighteen bits. |
| SLOT | Scaling, Limit, Offset and Transfer function. The Digital Annex definition that supplies a signal's declared range, and therefore the source of provisional thresholds. |
| Source address | Which controller on the bus sent the message. Claimed at startup, and part of a reading's identity. |
| SPN | Suspect Parameter Number. Which signal. The standard identifier for a measured quantity. |
| Transport protocol | The J1939 mechanism for carrying payloads longer than eight bytes, either broadcast or point to point with acknowledgement. |
| vcan | A virtual CAN interface provided by the Linux kernel. A bus with no hardware, used for replay and development. |
This manual was independently reviewed before release on two axes, instructional design and embedded systems technical accuracy. Both reviews returned pass with corrections, and every required correction from both reviewers has been applied in this revision.
Instructional review. Reviewed in full against a professional technical onboarding standard for learning architecture, cognitive load, retrieval practice, transfer, reference value and HTML accessibility. Verdict pass with corrections. Required corrections applied: terms defined before first use and a glossary added, content that existed only inside a diagram restated in prose and tables, a visible summary added above the collapsed case studies, front matter added covering audience, prerequisites, time, access to request and the completion bar, a mandatory practical exercise added, superseded reference pages marked inline, contents lists reconciled with the section numbering, the knowledge ledger given the names it exists to carry, quiz placement and two weak distractors corrected, and the placeholder certification block replaced with this one.
Embedded systems review. Reviewed by a principal embedded systems engineer against CAN, SAE J1939, SocketCAN, sub GHz radio, Yocto and Android device owner domain knowledge. Verdict pass with corrections. Corrections applied: error counter thresholds and the fact that only the transmit counter causes bus off, automatic bus off recovery, the scope of the hardware CRC claim, extended frame control bit count, the eighteen bit PGN assembly and the requirement to zero the PDU specific byte for addressed messages, the two missing failure mode identifiers, diagnostic message indexing from the fault record and the existence of earlier conversion methods, the width dependence of the reserved sentinel ranges, correct attribution of the 512 entry socket filter limit to raw sockets, the origin of the 256 prefetch value, the distinction between step and slew directives in time synchronisation, the regional regulatory position of the 433.92 MHz band, the strength of an eight bit checksum used for erasure resolution, the full checksum parameters, the correct driver name, and the normative status of the equipment class annex.
Verified arithmetic. The signal number 190 read big endian yields 48,640, confirmed. 160,633 seconds is 44.62 hours, confirmed. The code size headroom of 2,677 bytes divided across roughly 59 further registrations implies an average of about 45 bytes each, which is credible for the current method body and is an average rather than a bound.