Skip to content

fix(j1939): decode by source address (#13), split shared messages, stop 11-bit ID cross-matching - #14

Merged
dinacaran merged 3 commits into
mainfrom
claude/issue-13-review-0988cd
Sep 30, 2026
Merged

dinacaran merged 3 commits into
mainfrom
claude/issue-13-review-0988cd

Conversation

@dinacaran

@dinacaran dinacaran commented Sep 25, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #13.

Problem

#13: when a DBC defines a J1939 PGN at several source addresses (DM1_239 at SA 0xEF, DM1_243 at SA 0xF3), the decoder still added every message with that PGN as a fallback candidate and used the first one. DM1 frames from every other ECU (VCU 0x13, OBC 0xE6, …) decoded into xxx_239 as well, so one node's DTC signal mixed in DTCs from other nodes. MF4 files had the same bug, because asammdf does its own PGN matching.

Found while testing #13: when a DBC defines a message once at a placeholder SA and several ECUs send it, those ECUs were merged into one series. The bulk loaders insert one frame ID at a time, so the merged series held one back-to-back sweep of the recording per sender, out of time order. In a log, xxx:Switch comes from four nodes: two send real switch states and two send 3 (not available). Zoomed out it drew a solid box between 0 and 3. Zoomed in, pyqtgraph's binary-search clipping picked one sweep and showed a flat 3.

Found in the same lookup: the decoder also indexed and looked up every ID masked to 11 bits, so standard and extended IDs cross-matched. Extended frame 0x18FECA13 got the candidates [Std 0x213, DM1], and the loader used the standard message. Standard frame 0x2EF decoded as the extended message 0x18FECAEF.

Changes

  1. SA-specific PGN matching (DBCDecoder.candidates_for). If a PGN is defined at more than one SA, a frame only matches the message for its own SA. An SA the database doesn't define stays undecoded. A PGN defined at a single SA keeps the any-SA fallback, because that SA is usually a placeholder. The vectorized decoder and the debug inspector now call this one method instead of keeping their own copies of the rule.
  2. One series per sender. If frames from more than one SA decode into the same message, each sender gets its own series, named like CxVS [SA 0x17]. A single sender keeps the database name.
  3. Time order. SignalStore flags any series whose bulk insert starts before its last sample. sort_merged_series() reorders only those series after loading. All three bulk load paths call it, so one sender on two priorities still loads as one ordered series.
  4. MF4. The reader reads the frame ID from each asammdf group comment. It drops groups the decoder would not match, which applies [BUG] J1939 Decoding ignores Source Address (SA), causing signal overlap on PGN FECA (DM1) #13 to MF4. It also applies the same per-SA split instead of merging groups back together.
  5. No 11-bit cross-matching. Messages are indexed by ID type, so a standard frame only matches standard messages and an extended frame only extended ones. An ID that still carries the DBC's 0x80000000 extended flag keeps matching. The candidate cache key now includes the ID type. The bulk ASC/BLF loader still infers the type from the ID (anything above 0x7FF counts as extended). That covers J1939, but an extended frame with an ID of 0x7FF or below is still treated as standard there, as it was before this PR.

Testing

  • tests/test_dbc_decoder.py: 8 decoder tests for [BUG] J1939 Decoding ignores Source Address (SA), causing signal overlap on PGN FECA (DM1) #13 (undefined SAs stay undecoded, a frame matches its own SA's message, priority is ignored, the single-SA fallback is kept, the vectorized decoder delegates).
  • tests/test_j1939_senders.py (new): 8 end-to-end LoadWorker tests on generated ASC and MF4 files: split per sender, single sender keeps its name, [BUG] J1939 Decoding ignores Source Address (SA), causing signal overlap on PGN FECA (DM1) #13 on both formats, one sender on two priorities stays in time order, and the store sort.
  • tests/test_dbc_decoder.py: 4 tests for ID-type matching. Three of them failed before the fix (extended matching standard on the low bits, standard matching extended, and one ID sent as both types). The fourth checks that each type still matches its own messages.
  • Defect injection: reverting the decoder and loaders, dropping the loader sort, skipping the MF4 group handling, disabling the split, or disabling the store's out-of-order flag each fails the tests aimed at it.
  • Full suite: 604 passed.
  • Checked by hand on the J1939.MF4 with J1939.dbc: now loads as [SA 0x00], [SA 0x17], [SA 0x21] and [SA 0x31], each with 1958 samples in time order.

Note

In logs where a message now splits, saved configs or formulas that name the merged series will no longer find it. Nothing renames old references to the new names.

🤖 Generated with Claude Code

dinacaran and others added 3 commits September 25, 2026 16:16
When a database defines a J1939 PGN at more than one source address
(e.g. DM1_239 at SA 0xEF and DM1_243 at SA 0xF3), a frame of that PGN
now only decodes into the message for its own SA. Frames from an SA the
database does not define stay undecoded instead of borrowing the first
node's message, which merged DM1 from every ECU into one signal.

A PGN the database defines at a single SA keeps the any-SA fallback,
since that SA is usually a placeholder.

The candidate lookup moves to DBCDecoder.candidates_for(), which the
vectorized decoder and the debug inspector now call instead of keeping
their own copies of the rule.

Refs #13

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…order

A database often defines a J1939 message once at a placeholder source
address while several ECUs send it. In a truck log, CCVS came from four
nodes: two with real brake/clutch switch states and two sending 3 (not
available). All four decoded into one series, and because the bulk
loaders insert one frame ID at a time, the series held four back-to-back
sweeps of the recording instead of one time-ordered trace. Zoomed out,
the plot drew a solid box between 0 and 3; zoomed in, pyqtgraph's
binary-search clipping picked one sweep and showed a flat 3.

- A message that frames from more than one source address decode into
  now gets one series per sender, named "CCVS [SA 0x17]". A message
  with a single sender keeps its database name.
- SignalStore flags any series whose bulk insert starts before its last
  sample, and sort_merged_series() reorders only those series once
  loading finishes. All three bulk load paths call it, so one sender on
  two priorities still loads as one time-ordered series.
- The MF4 reader applies the same rules to asammdf's extraction.
  asammdf does its own J1939 matching and gives each frame ID its own
  group. The reader now reads the frame ID from each group's comment and
  drops groups our decoder would not match, which brings the #13 rule to
  MF4 files. It also splits multi-sender messages the same way instead
  of merging the groups back together.

Refs #13

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The decoder indexed every message under its ID masked to 11 bits and
looked every frame up the same way, so the two ID types cross-matched:
extended J1939 frame 0x18FECA13 became a candidate for a standard
message 0x213 (and, listed first, won over its own DM1 message), and a
standard frame 0x2EF decoded as the extended message 0x18FECAEF.

Messages are now indexed by ID type. A standard frame only matches
standard messages and an extended frame only extended ones; an ID that
still carries the DBC's extended flag bit (0x80000000) keeps matching.
The per-frame candidate cache is keyed on the ID type too, so a
standard and an extended frame with the same ID no longer share an
entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dinacaran
dinacaran merged commit 01af10f into main Sep 30, 2026
3 of 4 checks passed
@dinacaran
dinacaran deleted the claude/issue-13-review-0988cd branch October 4, 2026 15:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] J1939 Decoding ignores Source Address (SA), causing signal overlap on PGN FECA (DM1)

1 participant